wip: create poll. show outgoing polls

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
This commit is contained in:
Marcel Hibbe 2022-06-16 15:34:03 +02:00 committed by Andy Scherzinger (Rebase PR Action)
parent 0388f3e3f6
commit af427f8300
23 changed files with 727 additions and 33 deletions

View file

@ -24,27 +24,35 @@ package com.nextcloud.talk.adapters.messages
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.PorterDuff
import android.os.Handler
import android.util.Log
import android.view.View
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.view.ViewCompat
import autodagger.AutoInjector
import coil.load
import com.nextcloud.talk.R
import com.nextcloud.talk.activities.MainActivity
import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.databinding.ItemCustomOutcomingPollMessageBinding
import com.nextcloud.talk.models.json.chat.ChatMessage
import com.nextcloud.talk.models.json.chat.ReadStatus
import com.nextcloud.talk.polls.repositories.model.PollOverall
import com.nextcloud.talk.polls.ui.PollMainDialogFragment
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.DisplayUtils
import com.nextcloud.talk.utils.preferences.AppPreferences
import com.stfalcon.chatkit.messages.MessageHolders
import io.reactivex.Observer
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
class OutcomingPollMessageViewHolder(outcomingView: View) : MessageHolders
.OutcomingTextMessageViewHolder<ChatMessage>(outcomingView) {
class OutcomingPollMessageViewHolder(outcomingView: View, payload: Any) : MessageHolders
.OutcomingTextMessageViewHolder<ChatMessage>(outcomingView, payload) {
private val binding: ItemCustomOutcomingPollMessageBinding =
ItemCustomOutcomingPollMessageBinding.bind(itemView)
@ -57,9 +65,11 @@ class OutcomingPollMessageViewHolder(outcomingView: View) : MessageHolders
@Inject
var appPreferences: AppPreferences? = null
lateinit var message: ChatMessage
@Inject
@JvmField
var ncApi: NcApi? = null
lateinit var handler: Handler
lateinit var message: ChatMessage
lateinit var reactionsInterface: ReactionsInterface
@ -98,6 +108,8 @@ class OutcomingPollMessageViewHolder(outcomingView: View) : MessageHolders
binding.checkMark.setContentDescription(readStatusContentDescriptionString)
setPollPreview(message)
Reaction().showReactions(message, binding.reactions, binding.messageTime.context, true)
binding.reactions.reactionsEmojiWrapper.setOnClickListener {
reactionsInterface.onClickReactions(message)
@ -108,6 +120,73 @@ class OutcomingPollMessageViewHolder(outcomingView: View) : MessageHolders
}
}
private fun setPollPreview(message: ChatMessage) {
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 = individualHashMap["id"]
pollName = individualHashMap["name"].toString()
}
}
}
if (pollId != null && pollName != null) {
binding.messagePollTitle.text = pollName
val roomToken = (payload as? MessagePayload)!!.roomToken
binding.bubble.setOnClickListener {
val pollVoteDialog = PollMainDialogFragment.newInstance(
roomToken,
pollId,
pollName
)
pollVoteDialog.show(
(binding.messagePollIcon.context as MainActivity).supportFragmentManager,
TAG
)
}
val credentials = ApiUtils.getCredentials(message.activeUser?.username, message.activeUser?.token)
ncApi!!.getPoll(
credentials,
ApiUtils.getUrlForPoll(
message.activeUser?.baseUrl,
roomToken,
pollId
)
).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Observer<PollOverall> {
override fun onSubscribe(d: Disposable) {
// unused atm
}
override fun onNext(pollOverall: PollOverall) {
if (pollOverall.ocs!!.data!!.status == 0) {
binding.messagePollSubtitle.text =
context?.resources?.getString(R.string.message_poll_tap_to_vote)
} else {
binding.messagePollSubtitle.text =
context?.resources?.getString(R.string.message_poll_tap_see_results)
}
}
override fun onError(e: Throwable) {
Log.e(TAG, "Error while fetching poll", e)
}
override fun onComplete() {
// unused atm
}
})
}
}
private fun setParentMessageDataOnMessageItem(message: ChatMessage) {
if (!message.isDeleted && message.parentMessage != null) {
val parentChatMessage = message.parentMessage

View file

@ -532,9 +532,14 @@ public interface NcApi {
Observable<PollOverall> getPoll(@Header("Authorization") String authorization,
@Url String url);
@FormUrlEncoded
@POST
Observable<PollOverall> createPoll(@Header("Authorization") String authorization,
@Url String url);
@Url String url,
@Query("question") String question,
@Field("options[]") List<String> options,
@Query("resultMode") Integer resultMode,
@Query("maxVotes") Integer maxVotes);
@FormUrlEncoded
@POST

View file

@ -142,6 +142,7 @@ import com.nextcloud.talk.models.json.conversations.RoomOverall
import com.nextcloud.talk.models.json.conversations.RoomsOverall
import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.models.json.mention.Mention
import com.nextcloud.talk.polls.ui.PollCreateDialogFragment
import com.nextcloud.talk.presenters.MentionAutocompletePresenter
import com.nextcloud.talk.remotefilebrowser.activities.RemoteFileBrowserActivity
import com.nextcloud.talk.shareditems.activities.SharedItemsActivity
@ -3138,6 +3139,16 @@ class ChatController(args: Bundle) :
}
}
fun createPoll() {
val pollVoteDialog = PollCreateDialogFragment.newInstance(
roomToken!!
)
pollVoteDialog.show(
(activity as MainActivity?)!!.supportFragmentManager,
TAG
)
}
companion object {
private const val TAG = "ChatController"
private const val CONTENT_TYPE_SYSTEM_MESSAGE: Byte = 1

View file

@ -25,8 +25,9 @@ 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.PollCreateViewModel
import com.nextcloud.talk.polls.viewmodels.PollMainViewModel
import com.nextcloud.talk.polls.viewmodels.PollResultsViewModel
import com.nextcloud.talk.polls.viewmodels.PollViewModel
import com.nextcloud.talk.polls.viewmodels.PollVoteViewModel
import com.nextcloud.talk.shareditems.viewmodels.SharedItemsViewModel
import dagger.Binds
@ -66,8 +67,8 @@ abstract class ViewModelModule {
@Binds
@IntoMap
@ViewModelKey(PollViewModel::class)
abstract fun pollViewModel(viewModel: PollViewModel): ViewModel
@ViewModelKey(PollMainViewModel::class)
abstract fun pollViewModel(viewModel: PollMainViewModel): ViewModel
@Binds
@IntoMap
@ -79,6 +80,11 @@ abstract class ViewModelModule {
@ViewModelKey(PollResultsViewModel::class)
abstract fun pollResultsViewModel(viewModel: PollResultsViewModel): ViewModel
@Binds
@IntoMap
@ViewModelKey(PollCreateViewModel::class)
abstract fun pollCreateViewModel(viewModel: PollCreateViewModel): ViewModel
@Binds
@IntoMap
@ViewModelKey(RemoteFileBrowserItemsViewModel::class)

View file

@ -0,0 +1,5 @@
package com.nextcloud.talk.polls.adapters
class PollCreateOptionItem(
var pollOption: String
)

View file

@ -0,0 +1,53 @@
package com.nextcloud.talk.polls.adapters
import android.annotation.SuppressLint
import androidx.recyclerview.widget.RecyclerView
import com.nextcloud.talk.databinding.PollCreateOptionsItemBinding
class PollCreateOptionViewHolder(
private val binding: PollCreateOptionsItemBinding
) : RecyclerView.ViewHolder(binding.root) {
@SuppressLint("SetTextI18n")
fun bind(
pollCreateOptionItem: PollCreateOptionItem,
clickListener: PollCreateOptionsItemClickListener,
position: Int
) {
// binding.root.setOnClickListener { }
binding.pollOptionDelete.setOnClickListener {
clickListener.onDeleteClick(pollCreateOptionItem, position)
}
// binding.pollOptionText.addTextChangedListener(object : TextWatcher {
// override fun afterTextChanged(s: Editable) {
// // unused atm
// }
//
// override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
// // unused atm
// }
//
// override fun onTextChanged(option: CharSequence, start: Int, before: Int, count: Int) {
// pollCreateOptionItem.pollOption = option.toString()
// }
// })
}
// fun onBind(item: SharedItem) {
// Log.d("","bbbb")
// }
//
// fun onLongClick(view: View?): Boolean {
// // moviesList.remove(getAdapterPosition())
// // notifyItemRemoved(getAdapterPosition())
//
// Log.d("", "dfdrg")
// return true
// }
//
// override fun onClick(v: View?) {
// Log.d("", "dfdrg")
// }
}

View file

@ -0,0 +1,27 @@
package com.nextcloud.talk.polls.adapters
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.nextcloud.talk.databinding.PollCreateOptionsItemBinding
class PollCreateOptionsAdapter(
private val clickListener: PollCreateOptionsItemClickListener
) : RecyclerView.Adapter<PollCreateOptionViewHolder>() {
internal var list: MutableList<PollCreateOptionItem> = ArrayList<PollCreateOptionItem>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PollCreateOptionViewHolder {
val itemBinding = PollCreateOptionsItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return PollCreateOptionViewHolder(itemBinding)
}
override fun onBindViewHolder(holder: PollCreateOptionViewHolder, position: Int) {
holder.bind(list[position], clickListener, position)
}
override fun getItemCount(): Int {
return list.size
}
}

View file

@ -0,0 +1,5 @@
package com.nextcloud.talk.polls.adapters
interface PollCreateOptionsItemClickListener {
fun onDeleteClick(pollCreateOptionItem: PollCreateOptionItem, position: Int)
}

View file

@ -8,4 +8,12 @@ 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,
options: List<String>,
resultMode: Int,
maxVotes: Int
): Observable<Poll>?
}

View file

@ -38,6 +38,24 @@ class PollRepositoryImpl(private val ncApi: NcApi, private val currentUserProvid
currentUserProvider.currentUser?.token
)
override fun createPoll(
roomToken: String, question: String, options: List<String>, resultMode: Int, maxVotes:
Int
):
Observable<Poll>? {
return ncApi.createPoll(
credentials,
ApiUtils.getUrlForPoll(
currentUserProvider.currentUser?.baseUrl,
roomToken
),
question,
options,
resultMode,
maxVotes
).map { mapToPoll(it.ocs?.data!!) }
}
override fun getPoll(roomToken: String, pollId: String): Observable<Poll> {
return ncApi.getPoll(

View file

@ -0,0 +1,155 @@
package com.nextcloud.talk.polls.ui
import android.annotation.SuppressLint
import android.app.Dialog
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import autodagger.AutoInjector
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.databinding.DialogPollCreateBinding
import com.nextcloud.talk.polls.adapters.PollCreateOptionItem
import com.nextcloud.talk.polls.adapters.PollCreateOptionsAdapter
import com.nextcloud.talk.polls.adapters.PollCreateOptionsItemClickListener
import com.nextcloud.talk.polls.viewmodels.PollCreateViewModel
import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
class PollCreateDialogFragment(
private val roomToken: String
) : DialogFragment(), PollCreateOptionsItemClickListener {
@Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
private lateinit var binding: DialogPollCreateBinding
private lateinit var viewModel: PollCreateViewModel
private var adapter: PollCreateOptionsAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
viewModel = ViewModelProvider(this, viewModelFactory)[PollCreateViewModel::class.java]
}
@SuppressLint("InflateParams")
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
binding = DialogPollCreateBinding.inflate(LayoutInflater.from(context))
val dialog = AlertDialog.Builder(requireContext())
.setView(binding.root)
.create()
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)
adapter = PollCreateOptionsAdapter(this)
binding?.pollCreateOptionsList?.adapter = adapter
binding?.pollCreateOptionsList?.layoutManager = LinearLayoutManager(context)
viewModel.initialize(roomToken)
for (i in 1..3) {
val item = PollCreateOptionItem("a")
adapter?.list?.add(item)
}
binding.pollAddOption.setOnClickListener {
val item = PollCreateOptionItem("a")
adapter?.list?.add(item)
adapter?.notifyDataSetChanged()
}
binding.pollDismiss.setOnClickListener {
dismiss()
}
binding.pollCreateQuestion.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable) {
// unused atm
}
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
// unused atm
}
override fun onTextChanged(question: CharSequence, start: Int, before: Int, count: Int) {
viewModel.question = question.toString()
}
})
// binding.option1.addTextChangedListener(object : TextWatcher {
// override fun afterTextChanged(s: Editable) {
// // unused atm
// }
//
// override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
// // unused atm
// }
//
// override fun onTextChanged(option: CharSequence, start: Int, before: Int, count: Int) {
// viewModel.options = listOf(option.toString())
// }
// })
binding.pollPrivatePollCheckbox.setOnClickListener {
viewModel.multipleAnswer = binding.pollMultipleAnswersCheckbox.isChecked
}
binding.pollMultipleAnswersCheckbox.setOnClickListener {
viewModel.multipleAnswer = binding.pollMultipleAnswersCheckbox.isChecked
}
binding.pollCreateButton.setOnClickListener {
viewModel.createPoll()
}
viewModel.viewState.observe(viewLifecycleOwner) { state ->
when (state) {
PollCreateViewModel.InitialState -> {}
is PollCreateViewModel.PollCreatedState -> {
dismiss()
}
}
}
viewModel.initialize(roomToken)
}
override fun onDeleteClick(pollCreateOptionItem: PollCreateOptionItem, position: Int) {
adapter?.list?.remove(pollCreateOptionItem)
adapter?.notifyItemRemoved(position)
}
/**
* Fragment creator
*/
companion object {
private val TAG = PollCreateDialogFragment::class.java.simpleName
@JvmStatic
fun newInstance(
roomTokenParam: String
): PollCreateDialogFragment = PollCreateDialogFragment(roomTokenParam)
}
}

View file

@ -13,7 +13,7 @@ import autodagger.AutoInjector
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.databinding.DialogPollMainBinding
import com.nextcloud.talk.polls.model.Poll
import com.nextcloud.talk.polls.viewmodels.PollViewModel
import com.nextcloud.talk.polls.viewmodels.PollMainViewModel
import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
@ -27,13 +27,13 @@ class PollMainDialogFragment(
lateinit var viewModelFactory: ViewModelProvider.Factory
private lateinit var binding: DialogPollMainBinding
private lateinit var viewModel: PollViewModel
private lateinit var viewModel: PollMainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
viewModel = ViewModelProvider(this, viewModelFactory)[PollViewModel::class.java]
viewModel = ViewModelProvider(this, viewModelFactory)[PollMainViewModel::class.java]
}
@SuppressLint("InflateParams")
@ -58,9 +58,9 @@ class PollMainDialogFragment(
viewModel.viewState.observe(viewLifecycleOwner) { state ->
when (state) {
PollViewModel.InitialState -> {}
PollMainViewModel.InitialState -> {}
is PollViewModel.PollVotedState -> {
is PollMainViewModel.PollVotedState -> {
if (state.poll.resultMode == Poll.RESULT_MODE_HIDDEN) {
showVoteFragment()
} else {
@ -68,7 +68,7 @@ class PollMainDialogFragment(
}
}
is PollViewModel.PollUnvotedState -> {
is PollMainViewModel.PollUnvotedState -> {
if (state.poll.status == Poll.STATUS_CLOSED) {
showResultsFragment()
} else {

View file

@ -37,13 +37,13 @@ import com.nextcloud.talk.polls.adapters.PollResultItem
import com.nextcloud.talk.polls.adapters.PollResultItemClickListener
import com.nextcloud.talk.polls.adapters.PollResultsAdapter
import com.nextcloud.talk.polls.model.Poll
import com.nextcloud.talk.polls.viewmodels.PollMainViewModel
import com.nextcloud.talk.polls.viewmodels.PollResultsViewModel
import com.nextcloud.talk.polls.viewmodels.PollViewModel
import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
class PollResultsFragment(
private val parentViewModel: PollViewModel,
private val parentViewModel: PollMainViewModel,
private val roomToken: String,
private val pollId: String
) : Fragment(), PollResultItemClickListener {
@ -82,14 +82,14 @@ class PollResultsFragment(
_binding?.pollResultsList?.layoutManager = LinearLayoutManager(context)
parentViewModel.viewState.observe(viewLifecycleOwner) { state ->
if (state is PollViewModel.PollVotedState &&
if (state is PollMainViewModel.PollVotedState &&
state.poll.resultMode == Poll.RESULT_MODE_PUBLIC
) {
initPollResults(state.poll)
initAmountVotersInfo(state)
initEditButton(state)
} else if (state is PollViewModel.PollUnvotedState &&
} else if (state is PollMainViewModel.PollUnvotedState &&
state.poll.status == Poll.STATUS_CLOSED
) {
Log.d(TAG, "show results also if self never voted")
@ -129,14 +129,14 @@ class PollResultsFragment(
}
}
private fun initAmountVotersInfo(state: PollViewModel.PollVotedState) {
private fun initAmountVotersInfo(state: PollMainViewModel.PollVotedState) {
_binding?.pollAmountVoters?.text = String.format(
resources.getString(R.string.polls_amount_voters),
state.poll.numVoters
)
}
private fun initEditButton(state: PollViewModel.PollVotedState) {
private fun initEditButton(state: PollMainViewModel.PollVotedState) {
if (state.poll.status == Poll.STATUS_OPEN && state.poll.resultMode == Poll.RESULT_MODE_PUBLIC) {
_binding?.editVoteButton?.visibility = View.VISIBLE
_binding?.editVoteButton?.setOnClickListener {
@ -147,6 +147,10 @@ class PollResultsFragment(
}
}
override fun onClick(pollResultItem: PollResultItem) {
Log.d(TAG, "click..")
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
@ -155,8 +159,4 @@ class PollResultsFragment(
companion object {
private val TAG = PollResultsFragment::class.java.simpleName
}
override fun onClick(pollResultItem: PollResultItem) {
Log.d(TAG, "click..")
}
}

View file

@ -33,13 +33,13 @@ import autodagger.AutoInjector
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.databinding.DialogPollVoteBinding
import com.nextcloud.talk.polls.model.Poll
import com.nextcloud.talk.polls.viewmodels.PollViewModel
import com.nextcloud.talk.polls.viewmodels.PollMainViewModel
import com.nextcloud.talk.polls.viewmodels.PollVoteViewModel
import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
class PollVoteFragment(
private val parentViewModel: PollViewModel,
private val parentViewModel: PollMainViewModel,
private val roomToken: String,
private val pollId: String
) : Fragment() {
@ -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 PollViewModel.PollUnvotedState) {
if (state is PollMainViewModel.PollUnvotedState) {
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 PollViewModel.PollVotedState && state.poll.resultMode == Poll.RESULT_MODE_HIDDEN) {
} else if (state is PollMainViewModel.PollVotedState && 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

@ -0,0 +1,84 @@
package com.nextcloud.talk.polls.viewmodels
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.nextcloud.talk.polls.model.Poll
import com.nextcloud.talk.polls.repositories.PollRepository
import io.reactivex.Observer
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import javax.inject.Inject
class PollCreateViewModel @Inject constructor(private val repository: PollRepository) : ViewModel() {
private lateinit var roomToken: String
lateinit var question: String
lateinit var options: List<String>
var privatePoll: Boolean = false
var multipleAnswer: Boolean = false
sealed interface ViewState
object InitialState : ViewState
open class PollCreatingState() : ViewState
open class PollCreatedState() : ViewState
open class PollCreationFailedState() : ViewState
private val _viewState: MutableLiveData<ViewState> = MutableLiveData(InitialState)
val viewState: LiveData<ViewState>
get() = _viewState
private var disposable: Disposable? = null
fun initialize(roomToken: String) {
this.roomToken = roomToken
}
override fun onCleared() {
super.onCleared()
disposable?.dispose()
}
fun createPoll() {
var maxVotes = 1
if (multipleAnswer) {
maxVotes = 0
}
var resultMode = 0
if (privatePoll) {
resultMode = 1
}
repository.createPoll(roomToken, question, options, resultMode, maxVotes)
?.doOnSubscribe { disposable = it }
?.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(PollObserver())
}
inner class PollObserver : Observer<Poll> {
lateinit var poll: Poll
override fun onSubscribe(d: Disposable) = Unit
override fun onNext(response: Poll) {
poll = response
}
override fun onError(e: Throwable) {
_viewState.value = PollCreationFailedState()
}
override fun onComplete() {
_viewState.value = PollCreatedState()
}
}
companion object {
private val TAG = PollCreateViewModel::class.java.simpleName
}
}

View file

@ -23,7 +23,7 @@ import javax.inject.Inject
* InitialState --> PollClosedState
* @enduml
*/
class PollViewModel @Inject constructor(private val repository: PollRepository) : ViewModel() {
class PollMainViewModel @Inject constructor(private val repository: PollRepository) : ViewModel() {
private lateinit var roomToken: String
private lateinit var pollId: String
@ -97,6 +97,6 @@ class PollViewModel @Inject constructor(private val repository: PollRepository)
}
companion object {
private val TAG = PollViewModel::class.java.simpleName
private val TAG = PollMainViewModel::class.java.simpleName
}
}

View file

@ -74,6 +74,11 @@ class AttachmentDialog(val activity: Activity, var chatController: ChatControlle
dismiss()
}
dialogAttachmentBinding.menuAttachPoll.setOnClickListener {
chatController.createPoll()
dismiss()
}
dialogAttachmentBinding.menuAttachFileFromCloud.setOnClickListener {
chatController.showBrowserScreen()
dismiss()

View file

@ -0,0 +1,10 @@
<vector android:height="24dp"
android:tint="#000000"
android:viewportHeight="24"
android:viewportWidth="24"
android:width="24dp"
xmlns:android="http://schemas.android.com/apk/res/android">
<path
android:fillColor="@android:color/white"
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z" />
</vector>

View file

@ -39,6 +39,39 @@
android:textColor="@color/medium_emphasis_text"
android:textSize="@dimen/bottom_sheet_text_size" />
<LinearLayout
android:id="@+id/menu_attach_poll"
android:layout_width="match_parent"
android:layout_height="@dimen/bottom_sheet_item_height"
android:background="?android:attr/selectableItemBackground"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingStart="@dimen/standard_padding"
android:paddingEnd="@dimen/standard_padding"
tools:ignore="UseCompoundDrawables">
<ImageView
android:id="@+id/menu_icon_attach_poll"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@null"
android:src="@drawable/ic_baseline_bar_chart_24"
app:tint="@color/high_emphasis_menu_icon" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/txt_attach_poll"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:paddingStart="@dimen/standard_double_padding"
android:paddingEnd="@dimen/zero"
android:text="@string/nc_create_poll"
android:textAlignment="viewStart"
android:textColor="@color/high_emphasis_text"
android:textSize="@dimen/bottom_sheet_text_size" />
</LinearLayout>
<LinearLayout
android:id="@+id/menu_attach_contact"
android:layout_width="match_parent"

View file

@ -0,0 +1,133 @@
<!--
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/>.
-->
<LinearLayout 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="match_parent"
android:orientation="vertical"
android:padding="@dimen/standard_padding"
tools:background="@color/white">
<!-- <androidx.emoji.widget.EmojiTextView-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:text="@string/nc_create_poll"-->
<!-- android:textStyle="bold"-->
<!-- android:layout_marginBottom="@dimen/standard_half_margin"/>-->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/colorPrimary"
android:textStyle="bold"
android:text="Question" />
<com.nextcloud.talk.utils.EmojiTextInputEditText
android:id="@+id/poll_create_question"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/colorPrimary"
android:textStyle="bold"
android:layout_marginTop="@dimen/standard_margin"
android:text="Options" />
<LinearLayout
android:id="@+id/poll_create_options_list_wrapper"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/poll_create_options_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:listitem="@layout/poll_create_options_item" />
</LinearLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/poll_add_option"
style="@style/OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/standard_half_margin"
app:icon="@drawable/ic_add_grey600_24px"
app:cornerRadius="@dimen/button_corner_radius"
app:layout_constraintEnd_toEndOf="parent"
android:text="@string/polls_add_option" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/colorPrimary"
android:textStyle="bold"
android:layout_marginTop="@dimen/standard_margin"
android:layout_marginBottom="@dimen/standard_half_margin"
android:text="Settings" />
<CheckBox
android:id="@+id/poll_private_poll_checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Private poll" />
<CheckBox
android:id="@+id/poll_multiple_answers_checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Multiple answers" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="@dimen/standard_margin"
android:gravity="end">
<com.google.android.material.button.MaterialButton
android:id="@+id/poll_dismiss"
style="@style/OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/standard_half_margin"
app:cornerRadius="@dimen/button_corner_radius"
app:layout_constraintEnd_toEndOf="parent"
android:text="@string/nc_common_dismiss" />
<com.google.android.material.button.MaterialButton
android:id="@+id/poll_create_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/standard_half_margin"
app:cornerRadius="@dimen/button_corner_radius"
app:layout_constraintEnd_toEndOf="parent"
android:text="@string/nc_create_poll"
android:theme="@style/Button.Primary" />
</LinearLayout>
</LinearLayout>

View file

@ -47,11 +47,36 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:id="@+id/message_poll_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@null"
android:src="@drawable/ic_baseline_bar_chart_24"
app:tint="@color/nc_outcoming_text_default" />
<androidx.emoji.widget.EmojiTextView
android:id="@+id/message_poll_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAlignment="viewStart"
android:textStyle="bold"
android:textColor="@color/nc_outcoming_text_default"
tools:text="This is the poll title?" />
</LinearLayout>
<TextView
android:id="@+id/message_poll_subtitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/double_margin_between_elements"
android:text="@string/message_poll_tap_to_vote"
android:textColor="@color/nc_outcoming_text_default" />
<TextView
android:id="@id/messageTime"
android:layout_width="wrap_content"
@ -59,6 +84,7 @@
android:layout_below="@id/messageText"
android:layout_marginStart="8dp"
app:layout_alignSelf="center"
android:textColor="@color/nc_outcoming_text_default"
tools:text="10:35" />
<ImageView
@ -67,6 +93,7 @@
android:layout_height="wrap_content"
android:layout_below="@id/messageTime"
android:layout_marginStart="8dp"
android:textColor="@color/nc_outcoming_text_default"
app:layout_alignSelf="center"
android:contentDescription="@null" />

View file

@ -0,0 +1,27 @@
<LinearLayout 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"
android:orientation="horizontal"
tools:background="@color/white">
<com.nextcloud.talk.utils.EmojiTextInputEditText
android:id="@+id/poll_option_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:singleLine="true" />
<com.google.android.material.button.MaterialButton
android:id="@+id/poll_option_delete"
style="@style/Widget.AppTheme.Button.IconButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:contentDescription="@string/nc_action_open_main_menu"
app:cornerRadius="@dimen/button_corner_radius"
app:icon="@drawable/ic_baseline_close_24"
app:iconTint="@color/fontAppbar" />
</LinearLayout>

View file

@ -26,6 +26,7 @@
<string name="nc_no">No</string>
<string name="nc_common_skip">Skip</string>
<string name="nc_common_set">Set</string>
<string name="nc_common_dismiss">Dismiss</string>
<string name="nc_common_error_sorry">Sorry, something went wrong!</string>
<string name="nc_common_submit">Submit</string>
@ -404,6 +405,7 @@
<!-- Upload -->
<string name="nc_add_file">Add to conversation</string>
<string name="nc_upload_picture_from_cam">Take photo</string>
<string name="nc_create_poll">Create poll</string>
<string name="nc_upload_from_cloud">Share from %1$s</string>
<string name="nc_upload_failed">Sorry, upload failed</string>
<string name="nc_upload_choose_local_files">Choose files</string>
@ -536,6 +538,7 @@
<!-- Polls -->
<string name="polls_amount_voters">Poll results - %1$s votes</string>
<string name="polls_add_option">Add Option</string>
<string name="title_attachments">Attachments</string>