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_DELETED,
REACTION_REVOKED,
POLL_VOTED
POLL_VOTED,
POLL_CLOSED
}
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.PASSWORD_REMOVED
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.REACTION
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_revoked" -> REACTION_REVOKED
"poll_voted" -> POLL_VOTED
"poll_closed" -> POLL_CLOSED
else -> DUMMY
}
}
@ -227,6 +229,7 @@ class EnumSystemMessageTypeConverter : StringBasedTypeConverter<ChatMessage.Syst
REACTION_DELETED -> return "reaction_deleted"
REACTION_REVOKED -> return "reaction_revoked"
POLL_VOTED -> return "poll_voted"
POLL_CLOSED -> return "poll_closed"
else -> return ""
}
}

View File

@ -1,11 +1,13 @@
package com.nextcloud.talk.polls.adapters
import android.annotation.SuppressLint
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import com.nextcloud.talk.databinding.PollResultItemBinding
class PollResultViewHolder(
private val binding: PollResultItemBinding
private val binding: PollResultItemBinding,
private val showDetails: Boolean
) : RecyclerView.ViewHolder(binding.root) {
@SuppressLint("SetTextI18n")
@ -14,5 +16,11 @@ class PollResultViewHolder(
binding.pollOptionText.text = pollResultItem.pollOption
binding.pollOptionPercentText.text = pollResultItem.pollPercent.toString() + "%"
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
class PollResultsAdapter(
private val clickListener: PollResultItemClickListener
private val clickListener: PollResultItemClickListener,
private val showDetails: Boolean
) : RecyclerView.Adapter<PollResultViewHolder>() {
internal var list: MutableList<PollResultItem> = ArrayList<PollResultItem>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PollResultViewHolder {
val itemBinding = PollResultItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return PollResultViewHolder(itemBinding)
return PollResultViewHolder(itemBinding, showDetails)
}
override fun onBindViewHolder(holder: PollResultViewHolder, position: Int) {

View File

@ -5,10 +5,6 @@ import io.reactivex.Observable
interface PollRepository {
fun getPoll(roomToken: String, pollId: String): Observable<Poll>?
fun vote(roomToken: String, pollId: String, option: Int): Observable<Poll>?
fun createPoll(
roomToken: String,
question: String,
@ -16,4 +12,10 @@ interface PollRepository {
resultMode: Int,
maxVotes: Int
): 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!!) }
}
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 {
private fun mapToPoll(pollResponse: PollResponse): Poll {

View File

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

View File

@ -77,26 +77,23 @@ class PollResultsFragment(
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
adapter = PollResultsAdapter(this)
_binding?.pollResultsList?.adapter = adapter
_binding?.pollResultsList?.layoutManager = LinearLayoutManager(context)
parentViewModel.viewState.observe(viewLifecycleOwner) { state ->
if (state is PollMainViewModel.PollVotedState &&
state.poll.resultMode == Poll.RESULT_MODE_PUBLIC
) {
if (state is PollMainViewModel.PollResultState) {
initAdapter(state.showDetails)
initPollResults(state.poll)
initAmountVotersInfo(state)
initEditButton(state)
} else if (state is PollMainViewModel.PollUnvotedState &&
state.poll.status == Poll.STATUS_CLOSED
) {
Log.d(TAG, "show results also if self never voted")
initEditButton(state.showEditButton)
initCloseButton(state.showCloseButton)
}
}
}
private fun initAdapter(showDetails: Boolean) {
adapter = PollResultsAdapter(this, showDetails)
_binding?.pollResultsList?.adapter = adapter
_binding?.pollResultsList?.layoutManager = LinearLayoutManager(context)
}
private fun initPollResults(poll: Poll) {
if (poll.details != null) {
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(
resources.getString(R.string.polls_amount_voters),
state.poll.numVoters
)
}
private fun initEditButton(state: PollMainViewModel.PollVotedState) {
if (state.poll.status == Poll.STATUS_OPEN && state.poll.resultMode == Poll.RESULT_MODE_PUBLIC) {
// private fun initEditButton(state: PollMainViewModel.PollResultState) {
// 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?.setOnClickListener {
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) {
Log.d(TAG, "click..")
}

View File

@ -71,7 +71,7 @@ class PollVoteFragment(
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
parentViewModel.viewState.observe(viewLifecycleOwner) { state ->
if (state is PollMainViewModel.PollUnvotedState) {
if (state is PollMainViewModel.PollVoteState) {
val poll = state.poll
binding.radioGroup.removeAllViews()
poll.options?.map { option ->
@ -80,7 +80,7 @@ class PollVoteFragment(
radioButton.id = index
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")
// TODO: other text for submit button
}

View File

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

View File

@ -48,6 +48,18 @@
app:layout_constraintTop_toBottomOf="@+id/poll_results_list_wrapper"
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
android:id="@+id/edit_vote_button"
android:layout_width="wrap_content"

View File

@ -30,4 +30,19 @@
app:layout_constraintTop_toBottomOf="@+id/poll_option_text"
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>