add UI logic + close poll system message

add close poll button (wip/temporarily?)
add "close poll" system message
add UI related logic to PollMainViewModel
add placeholder for pollDetails in UI

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
This commit is contained in:
Marcel Hibbe 2022-06-22 13:33:14 +02:00 committed by Andy Scherzinger (Rebase PR Action)
parent ea0b7d07cc
commit b504af1cd9
12 changed files with 131 additions and 33 deletions

View File

@ -491,7 +491,8 @@ data class ChatMessage(
REACTION, REACTION,
REACTION_DELETED, REACTION_DELETED,
REACTION_REVOKED, REACTION_REVOKED,
POLL_VOTED POLL_VOTED,
POLL_CLOSED
} }
companion object { companion object {

View File

@ -65,6 +65,7 @@ import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.MODERAT
import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.OBJECT_SHARED import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.OBJECT_SHARED
import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.PASSWORD_REMOVED import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.PASSWORD_REMOVED
import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.PASSWORD_SET import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.PASSWORD_SET
import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.POLL_CLOSED
import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.POLL_VOTED import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.POLL_VOTED
import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.REACTION import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.REACTION
import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.REACTION_DELETED import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.REACTION_DELETED
@ -169,6 +170,7 @@ class EnumSystemMessageTypeConverter : StringBasedTypeConverter<ChatMessage.Syst
"reaction_deleted" -> REACTION_DELETED "reaction_deleted" -> REACTION_DELETED
"reaction_revoked" -> REACTION_REVOKED "reaction_revoked" -> REACTION_REVOKED
"poll_voted" -> POLL_VOTED "poll_voted" -> POLL_VOTED
"poll_closed" -> POLL_CLOSED
else -> DUMMY else -> DUMMY
} }
} }
@ -227,6 +229,7 @@ class EnumSystemMessageTypeConverter : StringBasedTypeConverter<ChatMessage.Syst
REACTION_DELETED -> return "reaction_deleted" REACTION_DELETED -> return "reaction_deleted"
REACTION_REVOKED -> return "reaction_revoked" REACTION_REVOKED -> return "reaction_revoked"
POLL_VOTED -> return "poll_voted" POLL_VOTED -> return "poll_voted"
POLL_CLOSED -> return "poll_closed"
else -> return "" else -> return ""
} }
} }

View File

@ -1,11 +1,13 @@
package com.nextcloud.talk.polls.adapters package com.nextcloud.talk.polls.adapters
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.view.View
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.nextcloud.talk.databinding.PollResultItemBinding import com.nextcloud.talk.databinding.PollResultItemBinding
class PollResultViewHolder( class PollResultViewHolder(
private val binding: PollResultItemBinding private val binding: PollResultItemBinding,
private val showDetails: Boolean
) : RecyclerView.ViewHolder(binding.root) { ) : RecyclerView.ViewHolder(binding.root) {
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
@ -14,5 +16,11 @@ class PollResultViewHolder(
binding.pollOptionText.text = pollResultItem.pollOption binding.pollOptionText.text = pollResultItem.pollOption
binding.pollOptionPercentText.text = pollResultItem.pollPercent.toString() + "%" binding.pollOptionPercentText.text = pollResultItem.pollPercent.toString() + "%"
binding.pollOptionBar.progress = pollResultItem.pollPercent binding.pollOptionBar.progress = pollResultItem.pollPercent
if (showDetails) {
binding.pollOptionDetail.visibility = View.VISIBLE
} else {
binding.pollOptionDetail.visibility = View.GONE
}
} }
} }

View File

@ -6,13 +6,14 @@ import androidx.recyclerview.widget.RecyclerView
import com.nextcloud.talk.databinding.PollResultItemBinding import com.nextcloud.talk.databinding.PollResultItemBinding
class PollResultsAdapter( class PollResultsAdapter(
private val clickListener: PollResultItemClickListener private val clickListener: PollResultItemClickListener,
private val showDetails: Boolean
) : RecyclerView.Adapter<PollResultViewHolder>() { ) : RecyclerView.Adapter<PollResultViewHolder>() {
internal var list: MutableList<PollResultItem> = ArrayList<PollResultItem>() internal var list: MutableList<PollResultItem> = ArrayList<PollResultItem>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PollResultViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PollResultViewHolder {
val itemBinding = PollResultItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) val itemBinding = PollResultItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return PollResultViewHolder(itemBinding) return PollResultViewHolder(itemBinding, showDetails)
} }
override fun onBindViewHolder(holder: PollResultViewHolder, position: Int) { override fun onBindViewHolder(holder: PollResultViewHolder, position: Int) {

View File

@ -5,10 +5,6 @@ import io.reactivex.Observable
interface PollRepository { interface PollRepository {
fun getPoll(roomToken: String, pollId: String): Observable<Poll>?
fun vote(roomToken: String, pollId: String, option: Int): Observable<Poll>?
fun createPoll( fun createPoll(
roomToken: String, roomToken: String,
question: String, question: String,
@ -16,4 +12,10 @@ interface PollRepository {
resultMode: Int, resultMode: Int,
maxVotes: Int maxVotes: Int
): Observable<Poll>? ): Observable<Poll>?
fun getPoll(roomToken: String, pollId: String): Observable<Poll>?
fun vote(roomToken: String, pollId: String, option: Int): Observable<Poll>?
fun closePoll(roomToken: String, pollId: String): Observable<Poll>?
} }

View File

@ -102,6 +102,18 @@ class PollRepositoryImpl(private val ncApi: NcApi, private val currentUserProvid
).map { mapToPoll(it.ocs?.data!!) } ).map { mapToPoll(it.ocs?.data!!) }
} }
override fun closePoll(roomToken: String, pollId: String): Observable<Poll> {
return ncApi.closePoll(
credentials,
ApiUtils.getUrlForPoll(
currentUserProvider.currentUser?.baseUrl,
roomToken,
pollId
),
).map { mapToPoll(it.ocs?.data!!) }
}
companion object { companion object {
private fun mapToPoll(pollResponse: PollResponse): Poll { private fun mapToPoll(pollResponse: PollResponse): Poll {

View File

@ -60,7 +60,7 @@ class PollMainDialogFragment(
when (state) { when (state) {
PollMainViewModel.InitialState -> {} PollMainViewModel.InitialState -> {}
is PollMainViewModel.PollVotedState -> { is PollMainViewModel.PollResultState -> {
if (state.poll.resultMode == Poll.RESULT_MODE_HIDDEN) { if (state.poll.resultMode == Poll.RESULT_MODE_HIDDEN) {
showVoteFragment() showVoteFragment()
} else { } else {
@ -68,7 +68,7 @@ class PollMainDialogFragment(
} }
} }
is PollMainViewModel.PollUnvotedState -> { is PollMainViewModel.PollVoteState -> {
if (state.poll.status == Poll.STATUS_CLOSED) { if (state.poll.status == Poll.STATUS_CLOSED) {
showResultsFragment() showResultsFragment()
} else { } else {

View File

@ -77,26 +77,23 @@ class PollResultsFragment(
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
adapter = PollResultsAdapter(this)
_binding?.pollResultsList?.adapter = adapter
_binding?.pollResultsList?.layoutManager = LinearLayoutManager(context)
parentViewModel.viewState.observe(viewLifecycleOwner) { state -> parentViewModel.viewState.observe(viewLifecycleOwner) { state ->
if (state is PollMainViewModel.PollVotedState && if (state is PollMainViewModel.PollResultState) {
state.poll.resultMode == Poll.RESULT_MODE_PUBLIC initAdapter(state.showDetails)
) {
initPollResults(state.poll) initPollResults(state.poll)
initAmountVotersInfo(state) initAmountVotersInfo(state)
initEditButton(state) initEditButton(state.showEditButton)
} else if (state is PollMainViewModel.PollUnvotedState && initCloseButton(state.showCloseButton)
state.poll.status == Poll.STATUS_CLOSED
) {
Log.d(TAG, "show results also if self never voted")
} }
} }
} }
private fun initAdapter(showDetails: Boolean) {
adapter = PollResultsAdapter(this, showDetails)
_binding?.pollResultsList?.adapter = adapter
_binding?.pollResultsList?.layoutManager = LinearLayoutManager(context)
}
private fun initPollResults(poll: Poll) { private fun initPollResults(poll: Poll) {
if (poll.details != null) { if (poll.details != null) {
val votersAmount = poll.details.size val votersAmount = poll.details.size
@ -128,15 +125,26 @@ class PollResultsFragment(
} }
} }
private fun initAmountVotersInfo(state: PollMainViewModel.PollVotedState) { private fun initAmountVotersInfo(state: PollMainViewModel.PollResultState) {
_binding?.pollAmountVoters?.text = String.format( _binding?.pollAmountVoters?.text = String.format(
resources.getString(R.string.polls_amount_voters), resources.getString(R.string.polls_amount_voters),
state.poll.numVoters state.poll.numVoters
) )
} }
private fun initEditButton(state: PollMainViewModel.PollVotedState) { // private fun initEditButton(state: PollMainViewModel.PollResultState) {
if (state.poll.status == Poll.STATUS_OPEN && state.poll.resultMode == Poll.RESULT_MODE_PUBLIC) { // if (state.poll.status == Poll.STATUS_OPEN && state.poll.resultMode == Poll.RESULT_MODE_PUBLIC) {
// _binding?.editVoteButton?.visibility = View.VISIBLE
// _binding?.editVoteButton?.setOnClickListener {
// parentViewModel.edit()
// }
// } else {
// _binding?.editVoteButton?.visibility = View.GONE
// }
// }
private fun initEditButton(showEditButton: Boolean) {
if (showEditButton) {
_binding?.editVoteButton?.visibility = View.VISIBLE _binding?.editVoteButton?.visibility = View.VISIBLE
_binding?.editVoteButton?.setOnClickListener { _binding?.editVoteButton?.setOnClickListener {
parentViewModel.edit() parentViewModel.edit()
@ -146,6 +154,17 @@ class PollResultsFragment(
} }
} }
private fun initCloseButton(showCloseButton: Boolean) {
if (showCloseButton) {
_binding?.closeVoteButton?.visibility = View.VISIBLE
_binding?.closeVoteButton?.setOnClickListener {
parentViewModel.closePoll()
}
} else {
_binding?.closeVoteButton?.visibility = View.GONE
}
}
override fun onClick(pollResultItem: PollResultItem) { override fun onClick(pollResultItem: PollResultItem) {
Log.d(TAG, "click..") Log.d(TAG, "click..")
} }

View File

@ -71,7 +71,7 @@ class PollVoteFragment(
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
parentViewModel.viewState.observe(viewLifecycleOwner) { state -> parentViewModel.viewState.observe(viewLifecycleOwner) { state ->
if (state is PollMainViewModel.PollUnvotedState) { if (state is PollMainViewModel.PollVoteState) {
val poll = state.poll val poll = state.poll
binding.radioGroup.removeAllViews() binding.radioGroup.removeAllViews()
poll.options?.map { option -> poll.options?.map { option ->
@ -80,7 +80,7 @@ class PollVoteFragment(
radioButton.id = index radioButton.id = index
binding.radioGroup.addView(radioButton) binding.radioGroup.addView(radioButton)
} }
} else if (state is PollMainViewModel.PollVotedState && state.poll.resultMode == Poll.RESULT_MODE_HIDDEN) { } else if (state is PollMainViewModel.PollResultState && state.poll.resultMode == Poll.RESULT_MODE_HIDDEN) {
Log.d(TAG, "show vote screen also for resultMode hidden poll when already voted") Log.d(TAG, "show vote screen also for resultMode hidden poll when already voted")
// TODO: other text for submit button // TODO: other text for submit button
} }

View File

@ -6,6 +6,7 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import com.nextcloud.talk.polls.model.Poll import com.nextcloud.talk.polls.model.Poll
import com.nextcloud.talk.polls.repositories.PollRepository import com.nextcloud.talk.polls.repositories.PollRepository
import com.nextcloud.talk.utils.database.user.UserUtils
import io.reactivex.Observer import io.reactivex.Observer
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
@ -25,6 +26,9 @@ import javax.inject.Inject
*/ */
class PollMainViewModel @Inject constructor(private val repository: PollRepository) : ViewModel() { class PollMainViewModel @Inject constructor(private val repository: PollRepository) : ViewModel() {
@Inject
lateinit var userUtils: UserUtils
private lateinit var roomToken: String private lateinit var roomToken: String
private lateinit var pollId: String private lateinit var pollId: String
@ -32,8 +36,13 @@ class PollMainViewModel @Inject constructor(private val repository: PollReposito
sealed interface ViewState sealed interface ViewState
object InitialState : ViewState object InitialState : ViewState
open class PollUnvotedState(val poll: Poll) : ViewState open class PollVoteState(val poll: Poll) : ViewState
open class PollVotedState(val poll: Poll) : ViewState open class PollResultState(
val poll: Poll,
val showDetails: Boolean,
val showEditButton: Boolean,
val showCloseButton: Boolean
) : ViewState
private val _viewState: MutableLiveData<ViewState> = MutableLiveData(InitialState) private val _viewState: MutableLiveData<ViewState> = MutableLiveData(InitialState)
val viewState: LiveData<ViewState> val viewState: LiveData<ViewState>
@ -65,6 +74,14 @@ class PollMainViewModel @Inject constructor(private val repository: PollReposito
?.subscribe(PollObserver()) ?.subscribe(PollObserver())
} }
fun closePoll() {
repository.closePoll(roomToken, pollId)
?.doOnSubscribe { disposable = it }
?.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(PollObserver())
}
override fun onCleared() { override fun onCleared() {
super.onCleared() super.onCleared()
disposable?.dispose() disposable?.dispose()
@ -86,16 +103,24 @@ class PollMainViewModel @Inject constructor(private val repository: PollReposito
override fun onComplete() { override fun onComplete() {
if (editPoll) { if (editPoll) {
_viewState.value = PollUnvotedState(poll) _viewState.value = PollVoteState(poll)
editPoll = false editPoll = false
} else if (poll.votedSelf.isNullOrEmpty()) { } else if (poll.votedSelf.isNullOrEmpty()) {
_viewState.value = PollUnvotedState(poll) _viewState.value = PollVoteState(poll)
} else { } else {
_viewState.value = PollVotedState(poll) val showEditButton = poll.status == Poll.STATUS_OPEN && poll.resultMode == Poll.RESULT_MODE_PUBLIC
val showDetails = poll.status == Poll.STATUS_CLOSED && poll.resultMode == Poll.RESULT_MODE_PUBLIC
val showCloseButton = poll.status == Poll.STATUS_OPEN && isPollCreatedByCurrentUser(poll)
_viewState.value = PollResultState(poll, showDetails, showEditButton, showCloseButton)
} }
} }
} }
fun isPollCreatedByCurrentUser(poll: Poll): Boolean {
return userUtils.currentUser?.userId == poll.actorId
}
companion object { companion object {
private val TAG = PollMainViewModel::class.java.simpleName private val TAG = PollMainViewModel::class.java.simpleName
} }

View File

@ -48,6 +48,18 @@
app:layout_constraintTop_toBottomOf="@+id/poll_results_list_wrapper" app:layout_constraintTop_toBottomOf="@+id/poll_results_list_wrapper"
tools:text="Poll results - 93 votes" /> tools:text="Poll results - 93 votes" />
<com.google.android.material.button.MaterialButton
android:id="@+id/close_vote_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Close"
android:theme="@style/Button.Primary"
app:cornerRadius="@dimen/button_corner_radius"
app:layout_constraintEnd_toStartOf="@+id/edit_vote_button"
app:layout_constraintTop_toBottomOf="@+id/poll_results_list_wrapper" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/edit_vote_button" android:id="@+id/edit_vote_button"
android:layout_width="wrap_content" android:layout_width="wrap_content"

View File

@ -30,4 +30,19 @@
app:layout_constraintTop_toBottomOf="@+id/poll_option_text" app:layout_constraintTop_toBottomOf="@+id/poll_option_text"
style="?android:attr/progressBarStyleHorizontal" /> style="?android:attr/progressBarStyleHorizontal" />
<LinearLayout
android:id="@+id/poll_option_detail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintStart_toStartOf="@+id/poll_option_text"
app:layout_constraintTop_toBottomOf="@+id/poll_option_bar">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="here the details..." />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>