wip: Poll view model work

Signed-off-by: Álvaro Brey <alvaro.brey@nextcloud.com>
This commit is contained in:
Álvaro Brey 2022-06-07 17:21:10 +02:00 committed by Andy Scherzinger (Rebase PR Action)
parent 01f18d7eca
commit 42324419cd
17 changed files with 388 additions and 117 deletions

View File

@ -40,7 +40,7 @@ import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.databinding.ItemCustomIncomingPollMessageBinding
import com.nextcloud.talk.models.json.chat.ChatMessage
import com.nextcloud.talk.polls.ui.PollVoteDialogFragment
import com.nextcloud.talk.polls.ui.PollMainDialogFragment
import com.nextcloud.talk.ui.bottom.sheet.ProfileBottomSheet
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.DisplayUtils
@ -100,14 +100,14 @@ class IncomingPollMessageViewHolder(incomingView: View, payload: Any) : MessageH
}
private fun setPollPreview(message: ChatMessage) {
var pollId: Int? = null
var pollId: String? = null
var pollName: String? = null
if (message.messageParameters != null && message.messageParameters!!.size > 0) {
for (key in message.messageParameters!!.keys) {
val individualHashMap: Map<String?, String?> = message.messageParameters!![key]!!
if (individualHashMap["type"] == "talk-poll") {
pollId = Integer.parseInt(individualHashMap["id"])
pollId = individualHashMap["id"]
pollName = individualHashMap["name"].toString()
}
}
@ -120,7 +120,7 @@ class IncomingPollMessageViewHolder(incomingView: View, payload: Any) : MessageH
val roomToken = "???????????????????????????"
binding.bubble.setOnClickListener {
val pollVoteDialog = PollVoteDialogFragment.newInstance(
val pollVoteDialog = PollMainDialogFragment.newInstance(
message.activeUser!!, roomToken, pollId,
pollName
)
@ -130,6 +130,7 @@ class IncomingPollMessageViewHolder(incomingView: View, payload: Any) : MessageH
)
}
// TODO get poll from api
// wait for https://github.com/nextcloud/spreed/pull/7306#issuecomment-1145819317
// val credentials = ApiUtils.getCredentials(message.activeUser?.username, message.activeUser?.token)

View File

@ -24,6 +24,8 @@
package com.nextcloud.talk.dagger.modules
import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.polls.repositories.PollRepository
import com.nextcloud.talk.polls.repositories.PollRepositoryImpl
import com.nextcloud.talk.data.source.local.TalkDatabase
import com.nextcloud.talk.data.storage.ArbitraryStoragesRepository
import com.nextcloud.talk.data.storage.ArbitraryStoragesRepositoryImpl
@ -52,6 +54,11 @@ class RepositoryModule {
return UnifiedSearchRepositoryImpl(ncApi, userProvider)
}
@Provides
fun provideDialogPollRepository(ncApi: NcApi, userProvider: CurrentUserProvider): PollRepository {
return PollRepositoryImpl(ncApi, userProvider)
}
@Provides
fun provideRemoteFileBrowserItemsRepository(okHttpClient: OkHttpClient, userProvider: CurrentUserProvider):
RemoteFileBrowserItemsRepository {

View File

@ -25,6 +25,8 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.nextcloud.talk.remotefilebrowser.viewmodels.RemoteFileBrowserItemsViewModel
import com.nextcloud.talk.messagesearch.MessageSearchViewModel
import com.nextcloud.talk.polls.viewmodels.PollViewModel
import com.nextcloud.talk.polls.viewmodels.PollVoteViewModel
import com.nextcloud.talk.shareditems.viewmodels.SharedItemsViewModel
import dagger.Binds
import dagger.MapKey
@ -61,6 +63,16 @@ abstract class ViewModelModule {
@ViewModelKey(MessageSearchViewModel::class)
abstract fun messageSearchViewModel(viewModel: MessageSearchViewModel): ViewModel
@Binds
@IntoMap
@ViewModelKey(PollViewModel::class)
abstract fun pollViewModel(viewModel: PollViewModel): ViewModel
@Binds
@IntoMap
@ViewModelKey(PollVoteViewModel::class)
abstract fun pollVoteViewModel(viewModel: PollVoteViewModel): ViewModel
@Binds
@IntoMap
@ViewModelKey(RemoteFileBrowserItemsViewModel::class)

View File

@ -0,0 +1,20 @@
package com.nextcloud.talk.polls.model
import com.nextcloud.talk.polls.repositories.model.PollDetails
data class Poll(
val id: String,
val question: String?,
val options: List<String>?,
val votes: List<Int>?,
val actorType: String?,
val actorId: String?,
val actorDisplayName: String?,
val status: Int,
val resultMode: Int,
val maxVotes: Int,
val votedSelf: List<Int>?,
val numVoters: Int,
// TODO PollDetails needs own model class
val details: List<PollDetails>?
)

View File

@ -1,4 +0,0 @@
package com.nextcloud.talk.polls.model
class PollModel {
}

View File

@ -1,12 +0,0 @@
package com.nextcloud.talk.polls.repositories
interface DialogPollRepository {
data class Parameters(
val userName: String,
val userToken: String,
val baseUrl: String,
val roomToken: String,
val pollId: Int
)
}

View File

@ -0,0 +1,9 @@
package com.nextcloud.talk.polls.repositories
import com.nextcloud.talk.polls.model.Poll
import io.reactivex.Observable
interface PollRepository {
fun getPoll(roomToken: String, pollId: String): Observable<Poll>
}

View File

@ -0,0 +1,52 @@
/*
* Nextcloud Talk application
*
* @author Álvaro Brey
* Copyright (C) 2022 Álvaro Brey
* Copyright (C) 2022 Nextcloud GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.polls.repositories
import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.polls.model.Poll
import com.nextcloud.talk.utils.database.user.CurrentUserProvider
import io.reactivex.Observable
class PollRepositoryImpl(private val api: NcApi, private val currentUserProvider: CurrentUserProvider) :
PollRepository {
override fun getPoll(roomToken: String, pollId: String): Observable<Poll> {
// TODO actual api call
return Observable.just(
Poll(
id = "aaa",
question = "what if?",
options = listOf("yes", "no", "maybe", "I don't know"),
votes = listOf(0, 0, 0, 0),
actorType = "",
actorId = "",
actorDisplayName = "",
status = 0,
resultMode = 0,
maxVotes = 1,
votedSelf = listOf(0, 0, 0, 0),
numVoters = 0,
details = emptyList()
)
)
}
}

View File

@ -20,7 +20,7 @@ data class PollDetails(
@JsonField(name = ["optionId"])
var optionId: Int? = 0,
) : Parcelable {
) : Parcelable {
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
constructor() : this(null, null, null, 0)
}

View File

@ -28,7 +28,7 @@ import kotlinx.android.parcel.Parcelize
@JsonObject
data class PollOCS(
@JsonField(name = ["data"])
var data: Poll?
var data: PollResponse?
) : Parcelable {
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
constructor() : this(null)

View File

@ -26,7 +26,7 @@ import kotlinx.android.parcel.Parcelize
@Parcelize
@JsonObject
data class Poll(
data class PollResponse(
@JsonField(name = ["id"])
var id: Int = 0,
@ -66,7 +66,7 @@ data class Poll(
@JsonField(name = ["details"])
var details: ArrayList<PollDetails>? = null,
) : Parcelable {
) : Parcelable {
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
constructor() : this(0, null, null, null, null, null, null, 0, 0, 0, null)
}

View File

@ -8,54 +8,68 @@ import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.ViewModelProvider
import autodagger.AutoInjector
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.databinding.DialogPollVoteBinding
import com.nextcloud.talk.databinding.DialogPollMainBinding
import com.nextcloud.talk.models.database.UserEntity
import com.nextcloud.talk.polls.viewmodels.PollViewModel
var user: UserEntity? = null
var pollId: Int? = null
var roomToken: String? = null
var pollTitle: String? = null
import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
class PollVoteDialogFragment : DialogFragment() {
class PollMainDialogFragment(
private val pollId: String,
private val roomToken: String,
private val pollTitle: String
) : DialogFragment() {
private lateinit var binding: DialogPollVoteBinding
@Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
private lateinit var binding: DialogPollMainBinding
private lateinit var viewModel: PollViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
viewModel = ViewModelProvider(this, viewModelFactory)[PollViewModel::class.java]
}
@SuppressLint("InflateParams")
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
binding = DialogPollVoteBinding.inflate(LayoutInflater.from(context))
binding = DialogPollMainBinding.inflate(LayoutInflater.from(context))
return AlertDialog.Builder(requireContext())
val dialog = AlertDialog.Builder(requireContext())
.setView(binding.root)
.create()
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
return binding.root
}
@SuppressLint("DefaultLocale")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.messagePollTitle.text = pollTitle
viewModel.viewState.observe(this) { state ->
// when (state) {
// }
return dialog
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.viewState.observe(viewLifecycleOwner) { state ->
when (state) {
PollViewModel.InitialState -> {}
is PollViewModel.PollClosedState -> TODO()
is PollViewModel.PollOpenState -> {
val contentFragment = PollVoteFragment(viewModel)
val transaction = childFragmentManager.beginTransaction()
transaction.replace(binding.messagePollContentFragment.id, contentFragment)
transaction.commit()
}
}
}
viewModel.initialize(user!!, roomToken!!, pollId!!)
viewModel.initialize(roomToken, pollId)
}
/**
@ -66,16 +80,8 @@ class PollVoteDialogFragment : DialogFragment() {
fun newInstance(
userEntity: UserEntity,
roomTokenParam: String,
id: Int,
pollId: String,
name: String
): PollVoteDialogFragment {
user = userEntity // TODO replace with "putParcelable" like in SetStatusDialogFragment???
roomToken = roomTokenParam
pollId = id
pollTitle = name
val dialogFragment = PollVoteDialogFragment()
return dialogFragment
}
): PollMainDialogFragment = PollMainDialogFragment(pollId, roomTokenParam, name)
}
}

View File

@ -0,0 +1,95 @@
/*
* Nextcloud Talk application
*
* @author Álvaro Brey
* Copyright (C) 2022 Álvaro Brey
* Copyright (C) 2022 Nextcloud GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.polls.ui
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.RadioButton
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import autodagger.AutoInjector
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.databinding.DialogPollVoteBinding
import com.nextcloud.talk.polls.viewmodels.PollViewModel
import com.nextcloud.talk.polls.viewmodels.PollVoteViewModel
import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
class PollVoteFragment(private val parentViewModel: PollViewModel) : Fragment() {
@Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
lateinit var viewModel: PollVoteViewModel
var _binding: DialogPollVoteBinding? = null
val binding: DialogPollVoteBinding
get() = _binding!!
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
viewModel = ViewModelProvider(this, viewModelFactory)[PollVoteViewModel::class.java]
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = DialogPollVoteBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
parentViewModel.viewState.observe(viewLifecycleOwner) { state ->
if (state is PollViewModel.PollOpenState) {
val poll = state.poll
binding.radioGroup.removeAllViews()
poll.options?.map { option ->
RadioButton(context)
.apply { text = option }
.also {
it.setOnClickListener {
// todo
}
}
}?.forEach {
binding.radioGroup.addView(it)
}
}
}
binding.radioGroup.setOnCheckedChangeListener { group, checkedId ->
// todo set selected in viewmodel
}
// todo observe viewmodel checked, set view checked with it
// todo listen to button click, submit
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}

View File

@ -3,32 +3,58 @@ package com.nextcloud.talk.polls.viewmodels
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.nextcloud.talk.models.database.UserEntity
import com.nextcloud.talk.polls.model.PollModel
import com.nextcloud.talk.polls.repositories.DialogPollRepository
import com.nextcloud.talk.polls.model.Poll
import com.nextcloud.talk.polls.repositories.PollRepository
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import javax.inject.Inject
class PollViewModel @Inject constructor(private val repository: DialogPollRepository) : ViewModel() {
/**
* @startuml
* hide empty description
* [*] --> InitialState
* InitialState --> PollOpenState
* note left
* Open second viewmodel for child fragment
* end note
* InitialState --> PollClosedState
* @enduml
*/
class PollViewModel @Inject constructor(private val repository: PollRepository) : ViewModel() {
private lateinit var repositoryParameters: DialogPollRepository.Parameters
private lateinit var roomToken: String
private lateinit var pollId: String
sealed interface ViewState
object InitialState : ViewState
open class PollOpenState(val poll: PollModel) : ViewState
open class PollClosedState(val poll: PollModel) : ViewState
open class PollOpenState(val poll: Poll) : ViewState
open class PollClosedState(val poll: Poll) : ViewState
private val _viewState: MutableLiveData<ViewState> = MutableLiveData(InitialState)
val viewState: LiveData<ViewState>
get() = _viewState
fun initialize(userEntity: UserEntity, roomToken: String, pollId: Int) {
repositoryParameters = DialogPollRepository.Parameters(
userEntity.userId,
userEntity.token,
userEntity.baseUrl,
roomToken,
pollId
)
// loadAvailableTypes()
private var disposable: Disposable? = null
fun initialize(roomToken: String, pollId: String) {
this.roomToken = roomToken
this.pollId = pollId
loadPoll()
}
private fun loadPoll() {
disposable = repository.getPoll(roomToken, pollId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { poll ->
_viewState.value = PollOpenState(poll)
}
}
override fun onCleared() {
super.onCleared()
disposable?.dispose()
}
}

View File

@ -0,0 +1,37 @@
/*
* Nextcloud Talk application
*
* @author Álvaro Brey
* Copyright (C) 2022 Álvaro Brey
* Copyright (C) 2022 Nextcloud GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.polls.viewmodels
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import javax.inject.Inject
class PollVoteViewModel @Inject constructor() : ViewModel() {
private val _selectedOptions: MutableLiveData<List<String>> = MutableLiveData(emptyList())
val selectedOptions: LiveData<List<String>>
get() = _selectedOptions
fun selectOption(option: String) {
_selectedOptions.value = listOf(option)
}
}

View File

@ -0,0 +1,58 @@
<!--
Nextcloud Android client application
@author Marcel Hibbe
Copyright (C) 2021 Marcel Hibbe <dev@mhibbe.de>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2,
as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:background="@color/white">
<ImageView
android:id="@+id/message_poll_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:contentDescription="@null"
android:src="@drawable/ic_baseline_bar_chart_24"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tint="@color/high_emphasis_menu_icon" />
<androidx.emoji.widget.EmojiTextView
android:id="@+id/message_poll_title"
android:layout_width="257dp"
android:layout_height="55dp"
android:layout_marginStart="8dp"
android:textAlignment="viewStart"
android:textStyle="bold"
app:layout_constraintStart_toEndOf="@id/message_poll_icon"
app:layout_constraintTop_toTopOf="@+id/message_poll_icon"
tools:text="This is the poll title?" />
<FrameLayout
android:id="@+id/message_poll_content_fragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/message_poll_title"
tools:layout_height="400dp" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -20,51 +20,15 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:tools="http://schemas.android.com/tools">
<ImageView
android:id="@+id/message_poll_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:contentDescription="@null"
android:src="@drawable/ic_baseline_bar_chart_24"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tint="@color/high_emphasis_menu_icon" />
<androidx.emoji.widget.EmojiTextView
android:id="@+id/message_poll_title"
android:layout_width="257dp"
android:layout_height="55dp"
android:layout_marginStart="8dp"
android:textAlignment="viewStart"
android:textStyle="bold"
app:layout_constraintStart_toEndOf="@id/message_poll_icon"
app:layout_constraintTop_toTopOf="@+id/message_poll_icon"
tools:text="This is the poll title?" />
xmlns:tools="http://schemas.android.com/tools"
tools:background="@color/white">
<RadioGroup
android:id="@+id/radioGroup"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
android:layout_width="wrap_content"
android:layout_height="203dp"
app:layout_constraintStart_toStartOf="@+id/message_poll_icon"
app:layout_constraintTop_toBottomOf="@+id/message_poll_title">
<RadioButton
android:id="@+id/radioButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="false"
android:text="yes" />
<RadioButton
android:id="@+id/radioButton2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="false"
android:text="no" />
app:layout_constraintTop_toTopOf="parent">
</RadioGroup>
@ -72,10 +36,10 @@
android:id="@+id/setStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:cornerRadius="@dimen/button_corner_radius"
app:layout_constraintEnd_toEndOf="parent"
android:text="@string/nc_common_submit"
android:theme="@style/Button.Primary"
app:cornerRadius="@dimen/button_corner_radius"
app:layout_constraintEnd_toEndOf="@+id/message_poll_title"
app:layout_constraintTop_toBottomOf="@+id/radioGroup" />
</androidx.constraintlayout.widget.ConstraintLayout>