mirror of
https://github.com/nextcloud/talk-android
synced 2025-06-20 12:09:45 +01:00
Merge pull request #2095 from nextcloud/feature/2024/simplePolls
Support "Simple polls"
This commit is contained in:
commit
2896d0758f
@ -50,7 +50,6 @@ import com.nextcloud.talk.application.NextcloudTalkApplication
|
|||||||
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
|
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
|
||||||
import com.nextcloud.talk.databinding.ItemCustomIncomingLocationMessageBinding
|
import com.nextcloud.talk.databinding.ItemCustomIncomingLocationMessageBinding
|
||||||
import com.nextcloud.talk.models.json.chat.ChatMessage
|
import com.nextcloud.talk.models.json.chat.ChatMessage
|
||||||
import com.nextcloud.talk.ui.bottom.sheet.ProfileBottomSheet
|
|
||||||
import com.nextcloud.talk.utils.ApiUtils
|
import com.nextcloud.talk.utils.ApiUtils
|
||||||
import com.nextcloud.talk.utils.DisplayUtils
|
import com.nextcloud.talk.utils.DisplayUtils
|
||||||
import com.nextcloud.talk.utils.UriUtils
|
import com.nextcloud.talk.utils.UriUtils
|
||||||
@ -117,7 +116,7 @@ class IncomingLocationMessageViewHolder(incomingView: View, payload: Any) : Mess
|
|||||||
if (!TextUtils.isEmpty(author)) {
|
if (!TextUtils.isEmpty(author)) {
|
||||||
binding.messageAuthor.text = author
|
binding.messageAuthor.text = author
|
||||||
binding.messageUserAvatar.setOnClickListener {
|
binding.messageUserAvatar.setOnClickListener {
|
||||||
(payload as? ProfileBottomSheet)?.showFor(message.actorId!!, itemView.context)
|
(payload as? MessagePayload)?.profileBottomSheet?.showFor(message.actorId!!, itemView.context)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
binding.messageAuthor.setText(R.string.nc_nick_guest)
|
binding.messageAuthor.setText(R.string.nc_nick_guest)
|
||||||
|
@ -0,0 +1,248 @@
|
|||||||
|
/*
|
||||||
|
* Nextcloud Talk application
|
||||||
|
*
|
||||||
|
* @author Marcel Hibbe
|
||||||
|
* Copyright (C) 2022 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 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.nextcloud.talk.adapters.messages
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.graphics.drawable.LayerDrawable
|
||||||
|
import android.os.Build
|
||||||
|
import android.text.TextUtils
|
||||||
|
import android.view.View
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.content.res.ResourcesCompat
|
||||||
|
import androidx.core.view.ViewCompat
|
||||||
|
import autodagger.AutoInjector
|
||||||
|
import coil.load
|
||||||
|
import com.amulyakhare.textdrawable.TextDrawable
|
||||||
|
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.ItemCustomIncomingPollMessageBinding
|
||||||
|
import com.nextcloud.talk.models.json.chat.ChatMessage
|
||||||
|
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 javax.inject.Inject
|
||||||
|
|
||||||
|
@AutoInjector(NextcloudTalkApplication::class)
|
||||||
|
class IncomingPollMessageViewHolder(incomingView: View, payload: Any) : MessageHolders
|
||||||
|
.IncomingTextMessageViewHolder<ChatMessage>(incomingView, payload) {
|
||||||
|
|
||||||
|
private val binding: ItemCustomIncomingPollMessageBinding =
|
||||||
|
ItemCustomIncomingPollMessageBinding.bind(itemView)
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var context: Context
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var appPreferences: AppPreferences
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var ncApi: NcApi
|
||||||
|
|
||||||
|
lateinit var message: ChatMessage
|
||||||
|
|
||||||
|
lateinit var reactionsInterface: ReactionsInterface
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
|
override fun onBind(message: ChatMessage) {
|
||||||
|
super.onBind(message)
|
||||||
|
this.message = message
|
||||||
|
sharedApplication!!.componentApplication.inject(this)
|
||||||
|
|
||||||
|
setAvatarAndAuthorOnMessageItem(message)
|
||||||
|
|
||||||
|
colorizeMessageBubble(message)
|
||||||
|
|
||||||
|
itemView.isSelected = false
|
||||||
|
binding.messageTime.setTextColor(ResourcesCompat.getColor(context?.resources!!, R.color.warm_grey_four, null))
|
||||||
|
|
||||||
|
// parent message handling
|
||||||
|
setParentMessageDataOnMessageItem(message)
|
||||||
|
|
||||||
|
setPollPreview(message)
|
||||||
|
|
||||||
|
Reaction().showReactions(message, binding.reactions, binding.messageTime.context, false)
|
||||||
|
binding.reactions.reactionsEmojiWrapper.setOnClickListener {
|
||||||
|
reactionsInterface.onClickReactions(message)
|
||||||
|
}
|
||||||
|
binding.reactions.reactionsEmojiWrapper.setOnLongClickListener { l: View? ->
|
||||||
|
reactionsInterface.onLongClickReactions(message)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)!!.currentConversation.token!!
|
||||||
|
val isOwnerOrModerator = (payload as? MessagePayload)!!.currentConversation.isParticipantOwnerOrModerator
|
||||||
|
|
||||||
|
binding.bubble.setOnClickListener {
|
||||||
|
val pollVoteDialog = PollMainDialogFragment.newInstance(
|
||||||
|
message.activeUser!!,
|
||||||
|
roomToken,
|
||||||
|
isOwnerOrModerator,
|
||||||
|
pollId,
|
||||||
|
pollName
|
||||||
|
)
|
||||||
|
pollVoteDialog.show(
|
||||||
|
(binding.messagePollIcon.context as MainActivity).supportFragmentManager,
|
||||||
|
TAG
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setAvatarAndAuthorOnMessageItem(message: ChatMessage) {
|
||||||
|
val author: String = message.actorDisplayName!!
|
||||||
|
if (!TextUtils.isEmpty(author)) {
|
||||||
|
binding.messageAuthor.text = author
|
||||||
|
binding.messageUserAvatar.setOnClickListener {
|
||||||
|
(payload as? MessagePayload)?.profileBottomSheet?.showFor(message.actorId!!, itemView.context)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
binding.messageAuthor.setText(R.string.nc_nick_guest)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!message.isGrouped && !message.isOneToOneConversation) {
|
||||||
|
setAvatarOnMessage(message)
|
||||||
|
} else {
|
||||||
|
if (message.isOneToOneConversation) {
|
||||||
|
binding.messageUserAvatar.visibility = View.GONE
|
||||||
|
} else {
|
||||||
|
binding.messageUserAvatar.visibility = View.INVISIBLE
|
||||||
|
}
|
||||||
|
binding.messageAuthor.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setAvatarOnMessage(message: ChatMessage) {
|
||||||
|
binding.messageUserAvatar.visibility = View.VISIBLE
|
||||||
|
if (message.actorType == "guests") {
|
||||||
|
// do nothing, avatar is set
|
||||||
|
} else if (message.actorType == "bots" && message.actorId == "changelog") {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
val layers = arrayOfNulls<Drawable>(2)
|
||||||
|
layers[0] = ContextCompat.getDrawable(context, R.drawable.ic_launcher_background)
|
||||||
|
layers[1] = ContextCompat.getDrawable(context, R.drawable.ic_launcher_foreground)
|
||||||
|
val layerDrawable = LayerDrawable(layers)
|
||||||
|
binding.messageUserAvatar.setImageDrawable(DisplayUtils.getRoundedDrawable(layerDrawable))
|
||||||
|
} else {
|
||||||
|
binding.messageUserAvatar.setImageResource(R.mipmap.ic_launcher)
|
||||||
|
}
|
||||||
|
} else if (message.actorType == "bots") {
|
||||||
|
val drawable = TextDrawable.builder()
|
||||||
|
.beginConfig()
|
||||||
|
.bold()
|
||||||
|
.endConfig()
|
||||||
|
.buildRound(
|
||||||
|
">",
|
||||||
|
ResourcesCompat.getColor(context.resources, R.color.black, null)
|
||||||
|
)
|
||||||
|
binding.messageUserAvatar.visibility = View.VISIBLE
|
||||||
|
binding.messageUserAvatar.setImageDrawable(drawable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun colorizeMessageBubble(message: ChatMessage) {
|
||||||
|
val resources = itemView.resources
|
||||||
|
|
||||||
|
var bubbleResource = R.drawable.shape_incoming_message
|
||||||
|
|
||||||
|
if (message.isGrouped) {
|
||||||
|
bubbleResource = R.drawable.shape_grouped_incoming_message
|
||||||
|
}
|
||||||
|
|
||||||
|
val bgBubbleColor = if (message.isDeleted) {
|
||||||
|
ResourcesCompat.getColor(resources, R.color.bg_message_list_incoming_bubble_deleted, null)
|
||||||
|
} else {
|
||||||
|
ResourcesCompat.getColor(resources, R.color.bg_message_list_incoming_bubble, null)
|
||||||
|
}
|
||||||
|
val bubbleDrawable = DisplayUtils.getMessageSelector(
|
||||||
|
bgBubbleColor,
|
||||||
|
ResourcesCompat.getColor(resources, R.color.transparent, null),
|
||||||
|
bgBubbleColor, bubbleResource
|
||||||
|
)
|
||||||
|
ViewCompat.setBackground(bubble, bubbleDrawable)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setParentMessageDataOnMessageItem(message: ChatMessage) {
|
||||||
|
if (!message.isDeleted && message.parentMessage != null) {
|
||||||
|
val parentChatMessage = message.parentMessage
|
||||||
|
parentChatMessage!!.activeUser = message.activeUser
|
||||||
|
parentChatMessage.imageUrl?.let {
|
||||||
|
binding.messageQuote.quotedMessageImage.visibility = View.VISIBLE
|
||||||
|
binding.messageQuote.quotedMessageImage.load(it) {
|
||||||
|
addHeader(
|
||||||
|
"Authorization",
|
||||||
|
ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} ?: run {
|
||||||
|
binding.messageQuote.quotedMessageImage.visibility = View.GONE
|
||||||
|
}
|
||||||
|
binding.messageQuote.quotedMessageAuthor.text = parentChatMessage.actorDisplayName
|
||||||
|
?: context.getText(R.string.nc_nick_guest)
|
||||||
|
binding.messageQuote.quotedMessage.text = parentChatMessage.text
|
||||||
|
|
||||||
|
binding.messageQuote.quotedMessageAuthor
|
||||||
|
.setTextColor(ContextCompat.getColor(context, R.color.textColorMaxContrast))
|
||||||
|
|
||||||
|
if (parentChatMessage.actorId?.equals(message.activeUser!!.userId) == true) {
|
||||||
|
binding.messageQuote.quoteColoredView.setBackgroundResource(R.color.colorPrimary)
|
||||||
|
} else {
|
||||||
|
binding.messageQuote.quoteColoredView.setBackgroundResource(R.color.textColorMaxContrast)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.messageQuote.quotedChatMessageView.visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
binding.messageQuote.quotedChatMessageView.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun assignReactionInterface(reactionsInterface: ReactionsInterface) {
|
||||||
|
this.reactionsInterface = reactionsInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val TAG = NextcloudTalkApplication::class.java.simpleName
|
||||||
|
}
|
||||||
|
}
|
@ -48,7 +48,6 @@ import com.nextcloud.talk.application.NextcloudTalkApplication
|
|||||||
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
|
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
|
||||||
import com.nextcloud.talk.databinding.ItemCustomIncomingVoiceMessageBinding
|
import com.nextcloud.talk.databinding.ItemCustomIncomingVoiceMessageBinding
|
||||||
import com.nextcloud.talk.models.json.chat.ChatMessage
|
import com.nextcloud.talk.models.json.chat.ChatMessage
|
||||||
import com.nextcloud.talk.ui.bottom.sheet.ProfileBottomSheet
|
|
||||||
import com.nextcloud.talk.utils.ApiUtils
|
import com.nextcloud.talk.utils.ApiUtils
|
||||||
import com.nextcloud.talk.utils.DisplayUtils
|
import com.nextcloud.talk.utils.DisplayUtils
|
||||||
import com.nextcloud.talk.utils.preferences.AppPreferences
|
import com.nextcloud.talk.utils.preferences.AppPreferences
|
||||||
@ -210,7 +209,7 @@ class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) : Message
|
|||||||
if (!TextUtils.isEmpty(author)) {
|
if (!TextUtils.isEmpty(author)) {
|
||||||
binding.messageAuthor.text = author
|
binding.messageAuthor.text = author
|
||||||
binding.messageUserAvatar.setOnClickListener {
|
binding.messageUserAvatar.setOnClickListener {
|
||||||
(payload as? ProfileBottomSheet)?.showFor(message.actorId!!, itemView.context)
|
(payload as? MessagePayload)?.profileBottomSheet?.showFor(message.actorId!!, itemView.context)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
binding.messageAuthor.setText(R.string.nc_nick_guest)
|
binding.messageAuthor.setText(R.string.nc_nick_guest)
|
||||||
|
@ -47,14 +47,12 @@ import com.nextcloud.talk.application.NextcloudTalkApplication
|
|||||||
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
|
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
|
||||||
import com.nextcloud.talk.databinding.ItemCustomIncomingTextMessageBinding
|
import com.nextcloud.talk.databinding.ItemCustomIncomingTextMessageBinding
|
||||||
import com.nextcloud.talk.models.json.chat.ChatMessage
|
import com.nextcloud.talk.models.json.chat.ChatMessage
|
||||||
import com.nextcloud.talk.ui.bottom.sheet.ProfileBottomSheet
|
|
||||||
import com.nextcloud.talk.ui.recyclerview.MessageSwipeCallback
|
import com.nextcloud.talk.ui.recyclerview.MessageSwipeCallback
|
||||||
import com.nextcloud.talk.utils.ApiUtils
|
import com.nextcloud.talk.utils.ApiUtils
|
||||||
import com.nextcloud.talk.utils.DisplayUtils
|
import com.nextcloud.talk.utils.DisplayUtils
|
||||||
import com.nextcloud.talk.utils.TextMatchers
|
import com.nextcloud.talk.utils.TextMatchers
|
||||||
import com.nextcloud.talk.utils.preferences.AppPreferences
|
import com.nextcloud.talk.utils.preferences.AppPreferences
|
||||||
import com.stfalcon.chatkit.messages.MessageHolders
|
import com.stfalcon.chatkit.messages.MessageHolders
|
||||||
import java.util.HashMap
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@AutoInjector(NextcloudTalkApplication::class)
|
@AutoInjector(NextcloudTalkApplication::class)
|
||||||
@ -136,7 +134,7 @@ class MagicIncomingTextMessageViewHolder(itemView: View, payload: Any) : Message
|
|||||||
if (!TextUtils.isEmpty(message.actorDisplayName)) {
|
if (!TextUtils.isEmpty(message.actorDisplayName)) {
|
||||||
binding.messageAuthor.text = message.actorDisplayName
|
binding.messageAuthor.text = message.actorDisplayName
|
||||||
binding.messageUserAvatar.setOnClickListener {
|
binding.messageUserAvatar.setOnClickListener {
|
||||||
(payload as? ProfileBottomSheet)?.showFor(message.actorId!!, itemView.context)
|
(payload as? MessagePayload)?.profileBottomSheet?.showFor(message.actorId!!, itemView.context)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
binding.messageAuthor.setText(R.string.nc_nick_guest)
|
binding.messageAuthor.setText(R.string.nc_nick_guest)
|
||||||
|
@ -49,7 +49,6 @@ import com.nextcloud.talk.components.filebrowser.webdav.ReadFilesystemOperation;
|
|||||||
import com.nextcloud.talk.data.user.model.User;
|
import com.nextcloud.talk.data.user.model.User;
|
||||||
import com.nextcloud.talk.databinding.ReactionsInsideMessageBinding;
|
import com.nextcloud.talk.databinding.ReactionsInsideMessageBinding;
|
||||||
import com.nextcloud.talk.models.json.chat.ChatMessage;
|
import com.nextcloud.talk.models.json.chat.ChatMessage;
|
||||||
import com.nextcloud.talk.ui.bottom.sheet.ProfileBottomSheet;
|
|
||||||
import com.nextcloud.talk.utils.DisplayUtils;
|
import com.nextcloud.talk.utils.DisplayUtils;
|
||||||
import com.nextcloud.talk.utils.DrawableUtils;
|
import com.nextcloud.talk.utils.DrawableUtils;
|
||||||
import com.nextcloud.talk.utils.FileViewerUtils;
|
import com.nextcloud.talk.utils.FileViewerUtils;
|
||||||
@ -125,8 +124,9 @@ public abstract class MagicPreviewMessageViewHolder extends MessageHolders.Incom
|
|||||||
} else {
|
} else {
|
||||||
userAvatar.setVisibility(View.VISIBLE);
|
userAvatar.setVisibility(View.VISIBLE);
|
||||||
userAvatar.setOnClickListener(v -> {
|
userAvatar.setOnClickListener(v -> {
|
||||||
if (payload instanceof ProfileBottomSheet) {
|
if (payload instanceof MessagePayload) {
|
||||||
((ProfileBottomSheet) payload).showFor(message.getActorId(), v.getContext());
|
((MessagePayload) payload).getProfileBottomSheet().showFor(message.getActorId(),
|
||||||
|
v.getContext());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* Nextcloud Talk application
|
||||||
|
*
|
||||||
|
* @author Marcel Hibbe
|
||||||
|
* Copyright (C) 2022 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 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.nextcloud.talk.adapters.messages
|
||||||
|
|
||||||
|
import com.nextcloud.talk.models.json.conversations.Conversation
|
||||||
|
import com.nextcloud.talk.ui.bottom.sheet.ProfileBottomSheet
|
||||||
|
|
||||||
|
data class MessagePayload(
|
||||||
|
var currentConversation: Conversation,
|
||||||
|
val profileBottomSheet: ProfileBottomSheet
|
||||||
|
)
|
@ -0,0 +1,214 @@
|
|||||||
|
/*
|
||||||
|
* Nextcloud Talk application
|
||||||
|
*
|
||||||
|
* @author Marcel Hibbe
|
||||||
|
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
|
||||||
|
* Copyright (C) 2022 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 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.nextcloud.talk.adapters.messages
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.PorterDuff
|
||||||
|
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.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 javax.inject.Inject
|
||||||
|
|
||||||
|
@AutoInjector(NextcloudTalkApplication::class)
|
||||||
|
class OutcomingPollMessageViewHolder(outcomingView: View, payload: Any) : MessageHolders
|
||||||
|
.OutcomingTextMessageViewHolder<ChatMessage>(outcomingView, payload) {
|
||||||
|
|
||||||
|
private val binding: ItemCustomOutcomingPollMessageBinding =
|
||||||
|
ItemCustomOutcomingPollMessageBinding.bind(itemView)
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var context: Context
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var appPreferences: AppPreferences
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var ncApi: NcApi
|
||||||
|
|
||||||
|
lateinit var message: ChatMessage
|
||||||
|
|
||||||
|
lateinit var reactionsInterface: ReactionsInterface
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
|
override fun onBind(message: ChatMessage) {
|
||||||
|
super.onBind(message)
|
||||||
|
this.message = message
|
||||||
|
sharedApplication!!.componentApplication.inject(this)
|
||||||
|
|
||||||
|
colorizeMessageBubble(message)
|
||||||
|
|
||||||
|
itemView.isSelected = false
|
||||||
|
binding.messageTime.setTextColor(context.resources.getColor(R.color.white60))
|
||||||
|
|
||||||
|
// parent message handling
|
||||||
|
setParentMessageDataOnMessageItem(message)
|
||||||
|
|
||||||
|
val readStatusDrawableInt = when (message.readStatus) {
|
||||||
|
ReadStatus.READ -> R.drawable.ic_check_all
|
||||||
|
ReadStatus.SENT -> R.drawable.ic_check
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
val readStatusContentDescriptionString = when (message.readStatus) {
|
||||||
|
ReadStatus.READ -> context?.resources?.getString(R.string.nc_message_read)
|
||||||
|
ReadStatus.SENT -> context?.resources?.getString(R.string.nc_message_sent)
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
readStatusDrawableInt?.let { drawableInt ->
|
||||||
|
AppCompatResources.getDrawable(context, drawableInt)?.let {
|
||||||
|
it.setColorFilter(context.resources!!.getColor(R.color.white60), PorterDuff.Mode.SRC_ATOP)
|
||||||
|
binding.checkMark.setImageDrawable(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.checkMark.contentDescription = readStatusContentDescriptionString
|
||||||
|
|
||||||
|
setPollPreview(message)
|
||||||
|
|
||||||
|
Reaction().showReactions(message, binding.reactions, binding.messageTime.context, true)
|
||||||
|
binding.reactions.reactionsEmojiWrapper.setOnClickListener {
|
||||||
|
reactionsInterface.onClickReactions(message)
|
||||||
|
}
|
||||||
|
binding.reactions.reactionsEmojiWrapper.setOnLongClickListener { l: View? ->
|
||||||
|
reactionsInterface.onLongClickReactions(message)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)!!.currentConversation.token!!
|
||||||
|
val isOwnerOrModerator = (payload as? MessagePayload)!!.currentConversation.isParticipantOwnerOrModerator
|
||||||
|
|
||||||
|
binding.bubble.setOnClickListener {
|
||||||
|
val pollVoteDialog = PollMainDialogFragment.newInstance(
|
||||||
|
message.activeUser!!,
|
||||||
|
roomToken,
|
||||||
|
isOwnerOrModerator,
|
||||||
|
pollId,
|
||||||
|
pollName
|
||||||
|
)
|
||||||
|
pollVoteDialog.show(
|
||||||
|
(binding.messagePollIcon.context as MainActivity).supportFragmentManager,
|
||||||
|
TAG
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setParentMessageDataOnMessageItem(message: ChatMessage) {
|
||||||
|
if (!message.isDeleted && message.parentMessage != null) {
|
||||||
|
val parentChatMessage = message.parentMessage
|
||||||
|
parentChatMessage!!.activeUser = message.activeUser
|
||||||
|
parentChatMessage.imageUrl?.let {
|
||||||
|
binding.messageQuote.quotedMessageImage.visibility = View.VISIBLE
|
||||||
|
binding.messageQuote.quotedMessageImage.load(it) {
|
||||||
|
addHeader(
|
||||||
|
"Authorization",
|
||||||
|
ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} ?: run {
|
||||||
|
binding.messageQuote.quotedMessageImage.visibility = View.GONE
|
||||||
|
}
|
||||||
|
binding.messageQuote.quotedMessageAuthor.text = parentChatMessage.actorDisplayName
|
||||||
|
?: context.getText(R.string.nc_nick_guest)
|
||||||
|
binding.messageQuote.quotedMessage.text = parentChatMessage.text
|
||||||
|
binding.messageQuote.quotedMessage.setTextColor(
|
||||||
|
context.resources.getColor(R.color.nc_outcoming_text_default)
|
||||||
|
)
|
||||||
|
binding.messageQuote.quotedMessageAuthor.setTextColor(context.resources.getColor(R.color.nc_grey))
|
||||||
|
|
||||||
|
binding.messageQuote.quoteColoredView.setBackgroundResource(R.color.white)
|
||||||
|
|
||||||
|
binding.messageQuote.quotedChatMessageView.visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
binding.messageQuote.quotedChatMessageView.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun colorizeMessageBubble(message: ChatMessage) {
|
||||||
|
val resources = sharedApplication!!.resources
|
||||||
|
val bgBubbleColor = if (message.isDeleted) {
|
||||||
|
resources.getColor(R.color.bg_message_list_outcoming_bubble_deleted)
|
||||||
|
} else {
|
||||||
|
resources.getColor(R.color.bg_message_list_outcoming_bubble)
|
||||||
|
}
|
||||||
|
if (message.isGrouped) {
|
||||||
|
val bubbleDrawable = DisplayUtils.getMessageSelector(
|
||||||
|
bgBubbleColor,
|
||||||
|
resources.getColor(R.color.transparent),
|
||||||
|
bgBubbleColor,
|
||||||
|
R.drawable.shape_grouped_outcoming_message
|
||||||
|
)
|
||||||
|
ViewCompat.setBackground(bubble, bubbleDrawable)
|
||||||
|
} else {
|
||||||
|
val bubbleDrawable = DisplayUtils.getMessageSelector(
|
||||||
|
bgBubbleColor,
|
||||||
|
resources.getColor(R.color.transparent),
|
||||||
|
bgBubbleColor,
|
||||||
|
R.drawable.shape_outcoming_message
|
||||||
|
)
|
||||||
|
ViewCompat.setBackground(bubble, bubbleDrawable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun assignReactionInterface(reactionsInterface: ReactionsInterface) {
|
||||||
|
this.reactionsInterface = reactionsInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val TAG = NextcloudTalkApplication::class.java.simpleName
|
||||||
|
}
|
||||||
|
}
|
@ -47,6 +47,7 @@ import com.nextcloud.talk.models.json.statuses.StatusesOverall;
|
|||||||
import com.nextcloud.talk.models.json.unifiedsearch.UnifiedSearchOverall;
|
import com.nextcloud.talk.models.json.unifiedsearch.UnifiedSearchOverall;
|
||||||
import com.nextcloud.talk.models.json.userprofile.UserProfileFieldsOverall;
|
import com.nextcloud.talk.models.json.userprofile.UserProfileFieldsOverall;
|
||||||
import com.nextcloud.talk.models.json.userprofile.UserProfileOverall;
|
import com.nextcloud.talk.models.json.userprofile.UserProfileOverall;
|
||||||
|
import com.nextcloud.talk.polls.repositories.model.PollOverall;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -526,4 +527,27 @@ public interface NcApi {
|
|||||||
@Query("from") String fromUrl,
|
@Query("from") String fromUrl,
|
||||||
@Query("limit") Integer limit,
|
@Query("limit") Integer limit,
|
||||||
@Query("cursor") Integer cursor);
|
@Query("cursor") Integer cursor);
|
||||||
|
|
||||||
|
@GET
|
||||||
|
Observable<PollOverall> getPoll(@Header("Authorization") String authorization,
|
||||||
|
@Url String url);
|
||||||
|
|
||||||
|
@FormUrlEncoded
|
||||||
|
@POST
|
||||||
|
Observable<PollOverall> createPoll(@Header("Authorization") String authorization,
|
||||||
|
@Url String url,
|
||||||
|
@Query("question") String question,
|
||||||
|
@Field("options[]") List<String> options,
|
||||||
|
@Query("resultMode") Integer resultMode,
|
||||||
|
@Query("maxVotes") Integer maxVotes);
|
||||||
|
|
||||||
|
@FormUrlEncoded
|
||||||
|
@POST
|
||||||
|
Observable<PollOverall> votePoll(@Header("Authorization") String authorization,
|
||||||
|
@Url String url,
|
||||||
|
@Field("optionIds[]") List<Integer> optionIds);
|
||||||
|
|
||||||
|
@DELETE
|
||||||
|
Observable<PollOverall> closePoll(@Header("Authorization") String authorization,
|
||||||
|
@Url String url);
|
||||||
}
|
}
|
||||||
|
@ -104,13 +104,16 @@ import com.nextcloud.talk.activities.CallActivity
|
|||||||
import com.nextcloud.talk.activities.MainActivity
|
import com.nextcloud.talk.activities.MainActivity
|
||||||
import com.nextcloud.talk.activities.TakePhotoActivity
|
import com.nextcloud.talk.activities.TakePhotoActivity
|
||||||
import com.nextcloud.talk.adapters.messages.IncomingLocationMessageViewHolder
|
import com.nextcloud.talk.adapters.messages.IncomingLocationMessageViewHolder
|
||||||
|
import com.nextcloud.talk.adapters.messages.IncomingPollMessageViewHolder
|
||||||
import com.nextcloud.talk.adapters.messages.IncomingPreviewMessageViewHolder
|
import com.nextcloud.talk.adapters.messages.IncomingPreviewMessageViewHolder
|
||||||
import com.nextcloud.talk.adapters.messages.IncomingVoiceMessageViewHolder
|
import com.nextcloud.talk.adapters.messages.IncomingVoiceMessageViewHolder
|
||||||
import com.nextcloud.talk.adapters.messages.MagicIncomingTextMessageViewHolder
|
import com.nextcloud.talk.adapters.messages.MagicIncomingTextMessageViewHolder
|
||||||
import com.nextcloud.talk.adapters.messages.MagicOutcomingTextMessageViewHolder
|
import com.nextcloud.talk.adapters.messages.MagicOutcomingTextMessageViewHolder
|
||||||
import com.nextcloud.talk.adapters.messages.MagicSystemMessageViewHolder
|
import com.nextcloud.talk.adapters.messages.MagicSystemMessageViewHolder
|
||||||
import com.nextcloud.talk.adapters.messages.MagicUnreadNoticeMessageViewHolder
|
import com.nextcloud.talk.adapters.messages.MagicUnreadNoticeMessageViewHolder
|
||||||
|
import com.nextcloud.talk.adapters.messages.MessagePayload
|
||||||
import com.nextcloud.talk.adapters.messages.OutcomingLocationMessageViewHolder
|
import com.nextcloud.talk.adapters.messages.OutcomingLocationMessageViewHolder
|
||||||
|
import com.nextcloud.talk.adapters.messages.OutcomingPollMessageViewHolder
|
||||||
import com.nextcloud.talk.adapters.messages.OutcomingPreviewMessageViewHolder
|
import com.nextcloud.talk.adapters.messages.OutcomingPreviewMessageViewHolder
|
||||||
import com.nextcloud.talk.adapters.messages.OutcomingVoiceMessageViewHolder
|
import com.nextcloud.talk.adapters.messages.OutcomingVoiceMessageViewHolder
|
||||||
import com.nextcloud.talk.adapters.messages.PreviewMessageInterface
|
import com.nextcloud.talk.adapters.messages.PreviewMessageInterface
|
||||||
@ -139,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.conversations.RoomsOverall
|
||||||
import com.nextcloud.talk.models.json.generic.GenericOverall
|
import com.nextcloud.talk.models.json.generic.GenericOverall
|
||||||
import com.nextcloud.talk.models.json.mention.Mention
|
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.presenters.MentionAutocompletePresenter
|
||||||
import com.nextcloud.talk.remotefilebrowser.activities.RemoteFileBrowserActivity
|
import com.nextcloud.talk.remotefilebrowser.activities.RemoteFileBrowserActivity
|
||||||
import com.nextcloud.talk.shareditems.activities.SharedItemsActivity
|
import com.nextcloud.talk.shareditems.activities.SharedItemsActivity
|
||||||
@ -483,10 +487,12 @@ class ChatController(args: Bundle) :
|
|||||||
val messageHolders = MessageHolders()
|
val messageHolders = MessageHolders()
|
||||||
val profileBottomSheet = ProfileBottomSheet(ncApi!!, conversationUser!!, router)
|
val profileBottomSheet = ProfileBottomSheet(ncApi!!, conversationUser!!, router)
|
||||||
|
|
||||||
|
val payload = MessagePayload(currentConversation!!, profileBottomSheet)
|
||||||
|
|
||||||
messageHolders.setIncomingTextConfig(
|
messageHolders.setIncomingTextConfig(
|
||||||
MagicIncomingTextMessageViewHolder::class.java,
|
MagicIncomingTextMessageViewHolder::class.java,
|
||||||
R.layout.item_custom_incoming_text_message,
|
R.layout.item_custom_incoming_text_message,
|
||||||
profileBottomSheet
|
payload
|
||||||
)
|
)
|
||||||
messageHolders.setOutcomingTextConfig(
|
messageHolders.setOutcomingTextConfig(
|
||||||
MagicOutcomingTextMessageViewHolder::class.java,
|
MagicOutcomingTextMessageViewHolder::class.java,
|
||||||
@ -496,7 +502,7 @@ class ChatController(args: Bundle) :
|
|||||||
messageHolders.setIncomingImageConfig(
|
messageHolders.setIncomingImageConfig(
|
||||||
IncomingPreviewMessageViewHolder::class.java,
|
IncomingPreviewMessageViewHolder::class.java,
|
||||||
R.layout.item_custom_incoming_preview_message,
|
R.layout.item_custom_incoming_preview_message,
|
||||||
profileBottomSheet
|
payload
|
||||||
)
|
)
|
||||||
|
|
||||||
messageHolders.setOutcomingImageConfig(
|
messageHolders.setOutcomingImageConfig(
|
||||||
@ -525,7 +531,7 @@ class ChatController(args: Bundle) :
|
|||||||
messageHolders.registerContentType(
|
messageHolders.registerContentType(
|
||||||
CONTENT_TYPE_LOCATION,
|
CONTENT_TYPE_LOCATION,
|
||||||
IncomingLocationMessageViewHolder::class.java,
|
IncomingLocationMessageViewHolder::class.java,
|
||||||
profileBottomSheet,
|
payload,
|
||||||
R.layout.item_custom_incoming_location_message,
|
R.layout.item_custom_incoming_location_message,
|
||||||
OutcomingLocationMessageViewHolder::class.java,
|
OutcomingLocationMessageViewHolder::class.java,
|
||||||
null,
|
null,
|
||||||
@ -536,7 +542,7 @@ class ChatController(args: Bundle) :
|
|||||||
messageHolders.registerContentType(
|
messageHolders.registerContentType(
|
||||||
CONTENT_TYPE_VOICE_MESSAGE,
|
CONTENT_TYPE_VOICE_MESSAGE,
|
||||||
IncomingVoiceMessageViewHolder::class.java,
|
IncomingVoiceMessageViewHolder::class.java,
|
||||||
profileBottomSheet,
|
payload,
|
||||||
R.layout.item_custom_incoming_voice_message,
|
R.layout.item_custom_incoming_voice_message,
|
||||||
OutcomingVoiceMessageViewHolder::class.java,
|
OutcomingVoiceMessageViewHolder::class.java,
|
||||||
null,
|
null,
|
||||||
@ -544,6 +550,17 @@ class ChatController(args: Bundle) :
|
|||||||
this
|
this
|
||||||
)
|
)
|
||||||
|
|
||||||
|
messageHolders.registerContentType(
|
||||||
|
CONTENT_TYPE_POLL,
|
||||||
|
IncomingPollMessageViewHolder::class.java,
|
||||||
|
payload,
|
||||||
|
R.layout.item_custom_incoming_poll_message,
|
||||||
|
OutcomingPollMessageViewHolder::class.java,
|
||||||
|
payload,
|
||||||
|
R.layout.item_custom_outcoming_poll_message,
|
||||||
|
this
|
||||||
|
)
|
||||||
|
|
||||||
var senderId = ""
|
var senderId = ""
|
||||||
if (!conversationUser?.userId.equals("?")) {
|
if (!conversationUser?.userId.equals("?")) {
|
||||||
senderId = "users/" + conversationUser?.userId
|
senderId = "users/" + conversationUser?.userId
|
||||||
@ -2576,6 +2593,11 @@ class ChatController(args: Bundle) :
|
|||||||
|
|
||||||
chatMessageIterator.remove()
|
chatMessageIterator.remove()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// delete poll system messages
|
||||||
|
else if (isPollVotedMessage(currentMessage)) {
|
||||||
|
chatMessageIterator.remove()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return chatMessageMap.values.toList()
|
return chatMessageMap.values.toList()
|
||||||
}
|
}
|
||||||
@ -2591,6 +2613,10 @@ class ChatController(args: Bundle) :
|
|||||||
currentMessage.value.systemMessageType == ChatMessage.SystemMessageType.REACTION_REVOKED
|
currentMessage.value.systemMessageType == ChatMessage.SystemMessageType.REACTION_REVOKED
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun isPollVotedMessage(currentMessage: MutableMap.MutableEntry<String, ChatMessage>): Boolean {
|
||||||
|
return currentMessage.value.systemMessageType == ChatMessage.SystemMessageType.POLL_VOTED
|
||||||
|
}
|
||||||
|
|
||||||
private fun startACall(isVoiceOnlyCall: Boolean, callWithoutNotification: Boolean) {
|
private fun startACall(isVoiceOnlyCall: Boolean, callWithoutNotification: Boolean) {
|
||||||
if (currentConversation?.canStartCall == false && currentConversation?.hasCall == false) {
|
if (currentConversation?.canStartCall == false && currentConversation?.hasCall == false) {
|
||||||
Toast.makeText(context, R.string.startCallForbidden, Toast.LENGTH_LONG).show()
|
Toast.makeText(context, R.string.startCallForbidden, Toast.LENGTH_LONG).show()
|
||||||
@ -3012,6 +3038,7 @@ class ChatController(args: Bundle) :
|
|||||||
return when (type) {
|
return when (type) {
|
||||||
CONTENT_TYPE_LOCATION -> message.hasGeoLocation()
|
CONTENT_TYPE_LOCATION -> message.hasGeoLocation()
|
||||||
CONTENT_TYPE_VOICE_MESSAGE -> message.isVoiceMessage
|
CONTENT_TYPE_VOICE_MESSAGE -> message.isVoiceMessage
|
||||||
|
CONTENT_TYPE_POLL -> message.isPoll()
|
||||||
CONTENT_TYPE_SYSTEM_MESSAGE -> !TextUtils.isEmpty(message.systemMessage)
|
CONTENT_TYPE_SYSTEM_MESSAGE -> !TextUtils.isEmpty(message.systemMessage)
|
||||||
CONTENT_TYPE_UNREAD_NOTICE_MESSAGE -> message.id == "-1"
|
CONTENT_TYPE_UNREAD_NOTICE_MESSAGE -> message.id == "-1"
|
||||||
else -> false
|
else -> false
|
||||||
@ -3121,12 +3148,23 @@ class ChatController(args: Bundle) :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun createPoll() {
|
||||||
|
val pollVoteDialog = PollCreateDialogFragment.newInstance(
|
||||||
|
roomToken!!
|
||||||
|
)
|
||||||
|
pollVoteDialog.show(
|
||||||
|
(activity as MainActivity?)!!.supportFragmentManager,
|
||||||
|
TAG
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "ChatController"
|
private const val TAG = "ChatController"
|
||||||
private const val CONTENT_TYPE_SYSTEM_MESSAGE: Byte = 1
|
private const val CONTENT_TYPE_SYSTEM_MESSAGE: Byte = 1
|
||||||
private const val CONTENT_TYPE_UNREAD_NOTICE_MESSAGE: Byte = 2
|
private const val CONTENT_TYPE_UNREAD_NOTICE_MESSAGE: Byte = 2
|
||||||
private const val CONTENT_TYPE_LOCATION: Byte = 3
|
private const val CONTENT_TYPE_LOCATION: Byte = 3
|
||||||
private const val CONTENT_TYPE_VOICE_MESSAGE: Byte = 4
|
private const val CONTENT_TYPE_VOICE_MESSAGE: Byte = 4
|
||||||
|
private const val CONTENT_TYPE_POLL: Byte = 5
|
||||||
private const val NEW_MESSAGES_POPUP_BUBBLE_DELAY: Long = 200
|
private const val NEW_MESSAGES_POPUP_BUBBLE_DELAY: Long = 200
|
||||||
private const val POP_CURRENT_CONTROLLER_DELAY: Long = 100
|
private const val POP_CURRENT_CONTROLLER_DELAY: Long = 100
|
||||||
private const val LOBBY_TIMER_DELAY: Long = 5000
|
private const val LOBBY_TIMER_DELAY: Long = 5000
|
||||||
|
@ -29,6 +29,8 @@ import com.nextcloud.talk.data.storage.ArbitraryStoragesRepository
|
|||||||
import com.nextcloud.talk.data.storage.ArbitraryStoragesRepositoryImpl
|
import com.nextcloud.talk.data.storage.ArbitraryStoragesRepositoryImpl
|
||||||
import com.nextcloud.talk.data.user.UsersRepository
|
import com.nextcloud.talk.data.user.UsersRepository
|
||||||
import com.nextcloud.talk.data.user.UsersRepositoryImpl
|
import com.nextcloud.talk.data.user.UsersRepositoryImpl
|
||||||
|
import com.nextcloud.talk.polls.repositories.PollRepository
|
||||||
|
import com.nextcloud.talk.polls.repositories.PollRepositoryImpl
|
||||||
import com.nextcloud.talk.remotefilebrowser.repositories.RemoteFileBrowserItemsRepository
|
import com.nextcloud.talk.remotefilebrowser.repositories.RemoteFileBrowserItemsRepository
|
||||||
import com.nextcloud.talk.remotefilebrowser.repositories.RemoteFileBrowserItemsRepositoryImpl
|
import com.nextcloud.talk.remotefilebrowser.repositories.RemoteFileBrowserItemsRepositoryImpl
|
||||||
import com.nextcloud.talk.repositories.unifiedsearch.UnifiedSearchRepository
|
import com.nextcloud.talk.repositories.unifiedsearch.UnifiedSearchRepository
|
||||||
@ -36,6 +38,7 @@ import com.nextcloud.talk.repositories.unifiedsearch.UnifiedSearchRepositoryImpl
|
|||||||
import com.nextcloud.talk.shareditems.repositories.SharedItemsRepository
|
import com.nextcloud.talk.shareditems.repositories.SharedItemsRepository
|
||||||
import com.nextcloud.talk.shareditems.repositories.SharedItemsRepositoryImpl
|
import com.nextcloud.talk.shareditems.repositories.SharedItemsRepositoryImpl
|
||||||
import com.nextcloud.talk.utils.database.user.CurrentUserProvider
|
import com.nextcloud.talk.utils.database.user.CurrentUserProvider
|
||||||
|
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
@ -52,6 +55,11 @@ class RepositoryModule {
|
|||||||
return UnifiedSearchRepositoryImpl(ncApi, userProvider)
|
return UnifiedSearchRepositoryImpl(ncApi, userProvider)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
fun provideDialogPollRepository(ncApi: NcApi, userProvider: CurrentUserProviderNew): PollRepository {
|
||||||
|
return PollRepositoryImpl(ncApi, userProvider)
|
||||||
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
fun provideRemoteFileBrowserItemsRepository(okHttpClient: OkHttpClient, userProvider: CurrentUserProvider):
|
fun provideRemoteFileBrowserItemsRepository(okHttpClient: OkHttpClient, userProvider: CurrentUserProvider):
|
||||||
RemoteFileBrowserItemsRepository {
|
RemoteFileBrowserItemsRepository {
|
||||||
|
@ -25,6 +25,10 @@ import androidx.lifecycle.ViewModel
|
|||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import com.nextcloud.talk.remotefilebrowser.viewmodels.RemoteFileBrowserItemsViewModel
|
import com.nextcloud.talk.remotefilebrowser.viewmodels.RemoteFileBrowserItemsViewModel
|
||||||
import com.nextcloud.talk.messagesearch.MessageSearchViewModel
|
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.PollVoteViewModel
|
||||||
import com.nextcloud.talk.shareditems.viewmodels.SharedItemsViewModel
|
import com.nextcloud.talk.shareditems.viewmodels.SharedItemsViewModel
|
||||||
import dagger.Binds
|
import dagger.Binds
|
||||||
import dagger.MapKey
|
import dagger.MapKey
|
||||||
@ -61,6 +65,26 @@ abstract class ViewModelModule {
|
|||||||
@ViewModelKey(MessageSearchViewModel::class)
|
@ViewModelKey(MessageSearchViewModel::class)
|
||||||
abstract fun messageSearchViewModel(viewModel: MessageSearchViewModel): ViewModel
|
abstract fun messageSearchViewModel(viewModel: MessageSearchViewModel): ViewModel
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@ViewModelKey(PollMainViewModel::class)
|
||||||
|
abstract fun pollViewModel(viewModel: PollMainViewModel): ViewModel
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@ViewModelKey(PollVoteViewModel::class)
|
||||||
|
abstract fun pollVoteViewModel(viewModel: PollVoteViewModel): ViewModel
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@ViewModelKey(PollResultsViewModel::class)
|
||||||
|
abstract fun pollResultsViewModel(viewModel: PollResultsViewModel): ViewModel
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@ViewModelKey(PollCreateViewModel::class)
|
||||||
|
abstract fun pollCreateViewModel(viewModel: PollCreateViewModel): ViewModel
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@ViewModelKey(RemoteFileBrowserItemsViewModel::class)
|
@ViewModelKey(RemoteFileBrowserItemsViewModel::class)
|
||||||
|
@ -124,6 +124,8 @@ data class ChatMessage(
|
|||||||
|
|
||||||
var voiceMessageDownloadProgress: Int = 0,
|
var voiceMessageDownloadProgress: Int = 0,
|
||||||
) : Parcelable, MessageContentType, MessageContentType.Image {
|
) : Parcelable, MessageContentType, MessageContentType.Image {
|
||||||
|
|
||||||
|
// messageTypesToIgnore is weird. must be deleted by refactoring!!!
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
var messageTypesToIgnore = Arrays.asList(
|
var messageTypesToIgnore = Arrays.asList(
|
||||||
MessageType.REGULAR_TEXT_MESSAGE,
|
MessageType.REGULAR_TEXT_MESSAGE,
|
||||||
@ -132,7 +134,8 @@ data class ChatMessage(
|
|||||||
MessageType.SINGLE_LINK_AUDIO_MESSAGE,
|
MessageType.SINGLE_LINK_AUDIO_MESSAGE,
|
||||||
MessageType.SINGLE_LINK_MESSAGE,
|
MessageType.SINGLE_LINK_MESSAGE,
|
||||||
MessageType.SINGLE_NC_GEOLOCATION_MESSAGE,
|
MessageType.SINGLE_NC_GEOLOCATION_MESSAGE,
|
||||||
MessageType.VOICE_MESSAGE
|
MessageType.VOICE_MESSAGE,
|
||||||
|
MessageType.POLL_MESSAGE
|
||||||
)
|
)
|
||||||
|
|
||||||
fun hasFileAttachment(): Boolean {
|
fun hasFileAttachment(): Boolean {
|
||||||
@ -165,6 +168,21 @@ data class ChatMessage(
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isPoll(): Boolean {
|
||||||
|
if (messageParameters != null && messageParameters!!.size > 0) {
|
||||||
|
for ((_, individualHashMap) in messageParameters!!) {
|
||||||
|
if (MessageDigest.isEqual(
|
||||||
|
individualHashMap["type"]!!.toByteArray(),
|
||||||
|
"talk-poll".toByteArray()
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
override fun getImageUrl(): String? {
|
override fun getImageUrl(): String? {
|
||||||
if (messageParameters != null && messageParameters!!.size > 0) {
|
if (messageParameters != null && messageParameters!!.size > 0) {
|
||||||
for ((_, individualHashMap) in messageParameters!!) {
|
for ((_, individualHashMap) in messageParameters!!) {
|
||||||
@ -207,6 +225,8 @@ data class ChatMessage(
|
|||||||
MessageType.SINGLE_NC_ATTACHMENT_MESSAGE
|
MessageType.SINGLE_NC_ATTACHMENT_MESSAGE
|
||||||
} else if (hasGeoLocation()) {
|
} else if (hasGeoLocation()) {
|
||||||
MessageType.SINGLE_NC_GEOLOCATION_MESSAGE
|
MessageType.SINGLE_NC_GEOLOCATION_MESSAGE
|
||||||
|
} else if (isPoll()) {
|
||||||
|
MessageType.POLL_MESSAGE
|
||||||
} else {
|
} else {
|
||||||
MessageType.REGULAR_TEXT_MESSAGE
|
MessageType.REGULAR_TEXT_MESSAGE
|
||||||
}
|
}
|
||||||
@ -334,6 +354,15 @@ data class ChatMessage(
|
|||||||
getNullsafeActorDisplayName()
|
getNullsafeActorDisplayName()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
} else if (MessageType.POLL_MESSAGE == getCalculateMessageType()) {
|
||||||
|
return if (actorId == activeUser!!.userId) {
|
||||||
|
sharedApplication!!.getString(R.string.nc_sent_poll_you)
|
||||||
|
} else {
|
||||||
|
String.format(
|
||||||
|
sharedApplication!!.resources.getString(R.string.nc_sent_an_image),
|
||||||
|
getNullsafeActorDisplayName()
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
@ -410,6 +439,7 @@ data class ChatMessage(
|
|||||||
SINGLE_LINK_AUDIO_MESSAGE,
|
SINGLE_LINK_AUDIO_MESSAGE,
|
||||||
SINGLE_NC_ATTACHMENT_MESSAGE,
|
SINGLE_NC_ATTACHMENT_MESSAGE,
|
||||||
SINGLE_NC_GEOLOCATION_MESSAGE,
|
SINGLE_NC_GEOLOCATION_MESSAGE,
|
||||||
|
POLL_MESSAGE,
|
||||||
VOICE_MESSAGE
|
VOICE_MESSAGE
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -460,7 +490,9 @@ data class ChatMessage(
|
|||||||
CLEARED_CHAT,
|
CLEARED_CHAT,
|
||||||
REACTION,
|
REACTION,
|
||||||
REACTION_DELETED,
|
REACTION_DELETED,
|
||||||
REACTION_REVOKED
|
REACTION_REVOKED,
|
||||||
|
POLL_VOTED,
|
||||||
|
POLL_CLOSED
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -65,6 +65,8 @@ 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.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
|
||||||
import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.REACTION_REVOKED
|
import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.REACTION_REVOKED
|
||||||
@ -167,6 +169,8 @@ class EnumSystemMessageTypeConverter : StringBasedTypeConverter<ChatMessage.Syst
|
|||||||
"reaction" -> REACTION
|
"reaction" -> REACTION
|
||||||
"reaction_deleted" -> REACTION_DELETED
|
"reaction_deleted" -> REACTION_DELETED
|
||||||
"reaction_revoked" -> REACTION_REVOKED
|
"reaction_revoked" -> REACTION_REVOKED
|
||||||
|
"poll_voted" -> POLL_VOTED
|
||||||
|
"poll_closed" -> POLL_CLOSED
|
||||||
else -> DUMMY
|
else -> DUMMY
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -224,6 +228,8 @@ class EnumSystemMessageTypeConverter : StringBasedTypeConverter<ChatMessage.Syst
|
|||||||
REACTION -> return "reaction"
|
REACTION -> return "reaction"
|
||||||
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_CLOSED -> return "poll_closed"
|
||||||
else -> return ""
|
else -> return ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Nextcloud Talk application
|
||||||
|
*
|
||||||
|
* @author Marcel Hibbe
|
||||||
|
* Copyright (C) 2022 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 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.nextcloud.talk.polls.adapters
|
||||||
|
|
||||||
|
class PollCreateOptionItem(
|
||||||
|
var pollOption: String
|
||||||
|
)
|
@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
* Nextcloud Talk application
|
||||||
|
*
|
||||||
|
* @author Marcel Hibbe
|
||||||
|
* Copyright (C) 2022 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 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.nextcloud.talk.polls.adapters
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.text.Editable
|
||||||
|
import android.text.TextWatcher
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.nextcloud.talk.databinding.PollCreateOptionsItemBinding
|
||||||
|
import com.nextcloud.talk.utils.EmojiTextInputEditText
|
||||||
|
|
||||||
|
class PollCreateOptionViewHolder(
|
||||||
|
private val binding: PollCreateOptionsItemBinding
|
||||||
|
) : RecyclerView.ViewHolder(binding.root) {
|
||||||
|
|
||||||
|
lateinit var optionText: EmojiTextInputEditText
|
||||||
|
private var textListener: TextWatcher? = null
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
|
fun bind(
|
||||||
|
pollCreateOptionItem: PollCreateOptionItem,
|
||||||
|
itemsListener: PollCreateOptionsItemListener,
|
||||||
|
position: Int,
|
||||||
|
focus: Boolean
|
||||||
|
) {
|
||||||
|
|
||||||
|
textListener?.let {
|
||||||
|
binding.pollOptionText.removeTextChangedListener(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.pollOptionText.setText(pollCreateOptionItem.pollOption)
|
||||||
|
|
||||||
|
if (focus) {
|
||||||
|
itemsListener.requestFocus(binding.pollOptionText)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.pollOptionDelete.setOnClickListener {
|
||||||
|
itemsListener.onRemoveOptionsItemClick(pollCreateOptionItem, position)
|
||||||
|
}
|
||||||
|
|
||||||
|
textListener = getTextWatcher(pollCreateOptionItem, itemsListener)
|
||||||
|
binding.pollOptionText.addTextChangedListener(textListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getTextWatcher(
|
||||||
|
pollCreateOptionItem: PollCreateOptionItem,
|
||||||
|
itemsListener: PollCreateOptionsItemListener
|
||||||
|
) =
|
||||||
|
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()
|
||||||
|
|
||||||
|
itemsListener.onOptionsItemTextChanged(pollCreateOptionItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* Nextcloud Talk application
|
||||||
|
*
|
||||||
|
* @author Marcel Hibbe
|
||||||
|
* Copyright (C) 2022 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 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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: PollCreateOptionsItemListener
|
||||||
|
) : RecyclerView.Adapter<PollCreateOptionViewHolder>() {
|
||||||
|
|
||||||
|
internal var list: ArrayList<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) {
|
||||||
|
val currentItem = list[position]
|
||||||
|
var focus = false
|
||||||
|
|
||||||
|
if (list.size - 1 == position && currentItem.pollOption.isBlank()) {
|
||||||
|
focus = true
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.bind(currentItem, clickListener, position, focus)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int {
|
||||||
|
return list.size
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateOptionsList(optionsList: ArrayList<PollCreateOptionItem>) {
|
||||||
|
list = optionsList
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Nextcloud Talk application
|
||||||
|
*
|
||||||
|
* @author Marcel Hibbe
|
||||||
|
* Copyright (C) 2022 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 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.nextcloud.talk.polls.adapters
|
||||||
|
|
||||||
|
import android.widget.EditText
|
||||||
|
|
||||||
|
interface PollCreateOptionsItemListener {
|
||||||
|
|
||||||
|
fun onRemoveOptionsItemClick(pollCreateOptionItem: PollCreateOptionItem, position: Int)
|
||||||
|
|
||||||
|
fun onOptionsItemTextChanged(pollCreateOptionItem: PollCreateOptionItem)
|
||||||
|
|
||||||
|
fun requestFocus(textField: EditText)
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* Nextcloud Talk application
|
||||||
|
*
|
||||||
|
* @author Marcel Hibbe
|
||||||
|
* Copyright (C) 2022 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 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.nextcloud.talk.polls.adapters
|
||||||
|
|
||||||
|
import com.nextcloud.talk.R
|
||||||
|
|
||||||
|
data class PollResultHeaderItem(
|
||||||
|
val name: String,
|
||||||
|
val percent: Int,
|
||||||
|
val selfVoted: Boolean
|
||||||
|
) : PollResultItem {
|
||||||
|
|
||||||
|
override fun getViewType(): Int {
|
||||||
|
return VIEW_TYPE
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
// layout is used as view type for uniqueness
|
||||||
|
const val VIEW_TYPE: Int = R.layout.poll_result_header_item
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* Nextcloud Talk application
|
||||||
|
*
|
||||||
|
* @author Marcel Hibbe
|
||||||
|
* Copyright (C) 2022 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 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.nextcloud.talk.polls.adapters
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.graphics.Typeface
|
||||||
|
import com.nextcloud.talk.databinding.PollResultHeaderItemBinding
|
||||||
|
|
||||||
|
class PollResultHeaderViewHolder(
|
||||||
|
override val binding: PollResultHeaderItemBinding
|
||||||
|
) : PollResultViewHolder(binding) {
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
|
override fun bind(pollResultItem: PollResultItem, clickListener: PollResultItemClickListener) {
|
||||||
|
val item = pollResultItem as PollResultHeaderItem
|
||||||
|
|
||||||
|
binding.root.setOnClickListener { clickListener.onClick() }
|
||||||
|
|
||||||
|
binding.pollOptionText.text = item.name
|
||||||
|
binding.pollOptionPercentText.text = "${item.percent}%"
|
||||||
|
|
||||||
|
if (item.selfVoted) {
|
||||||
|
binding.pollOptionText.setTypeface(null, Typeface.BOLD)
|
||||||
|
binding.pollOptionPercentText.setTypeface(null, Typeface.BOLD)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.pollOptionBar.progress = item.percent
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Nextcloud Talk application
|
||||||
|
*
|
||||||
|
* @author Marcel Hibbe
|
||||||
|
* Copyright (C) 2022 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 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.nextcloud.talk.polls.adapters
|
||||||
|
|
||||||
|
interface PollResultItem {
|
||||||
|
fun getViewType(): Int
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Nextcloud Talk application
|
||||||
|
*
|
||||||
|
* @author Marcel Hibbe
|
||||||
|
* Copyright (C) 2022 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 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.nextcloud.talk.polls.adapters
|
||||||
|
|
||||||
|
interface PollResultItemClickListener {
|
||||||
|
fun onClick()
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* Nextcloud Talk application
|
||||||
|
*
|
||||||
|
* @author Marcel Hibbe
|
||||||
|
* Copyright (C) 2022 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 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.nextcloud.talk.polls.adapters
|
||||||
|
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import androidx.viewbinding.ViewBinding
|
||||||
|
|
||||||
|
abstract class PollResultViewHolder(
|
||||||
|
open val binding: ViewBinding
|
||||||
|
) : RecyclerView.ViewHolder(binding.root) {
|
||||||
|
abstract fun bind(pollResultItem: PollResultItem, clickListener: PollResultItemClickListener)
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* Nextcloud Talk application
|
||||||
|
*
|
||||||
|
* @author Marcel Hibbe
|
||||||
|
* Copyright (C) 2022 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 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.nextcloud.talk.polls.adapters
|
||||||
|
|
||||||
|
import com.nextcloud.talk.R
|
||||||
|
import com.nextcloud.talk.polls.model.PollDetails
|
||||||
|
|
||||||
|
data class PollResultVoterItem(
|
||||||
|
val details: PollDetails
|
||||||
|
) : PollResultItem {
|
||||||
|
|
||||||
|
override fun getViewType(): Int {
|
||||||
|
return VIEW_TYPE
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
// layout is used as view type for uniqueness
|
||||||
|
const val VIEW_TYPE: Int = R.layout.poll_result_voter_item
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* Nextcloud Talk application
|
||||||
|
*
|
||||||
|
* @author Marcel Hibbe
|
||||||
|
* Copyright (C) 2022 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 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.nextcloud.talk.polls.adapters
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.text.TextUtils
|
||||||
|
import com.facebook.drawee.backends.pipeline.Fresco
|
||||||
|
import com.facebook.drawee.interfaces.DraweeController
|
||||||
|
import com.nextcloud.talk.R
|
||||||
|
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||||
|
import com.nextcloud.talk.data.user.model.User
|
||||||
|
import com.nextcloud.talk.databinding.PollResultVoterItemBinding
|
||||||
|
import com.nextcloud.talk.polls.model.PollDetails
|
||||||
|
import com.nextcloud.talk.utils.ApiUtils
|
||||||
|
import com.nextcloud.talk.utils.DisplayUtils
|
||||||
|
|
||||||
|
class PollResultVoterViewHolder(
|
||||||
|
private val user: User,
|
||||||
|
override val binding: PollResultVoterItemBinding
|
||||||
|
) : PollResultViewHolder(binding) {
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
|
override fun bind(pollResultItem: PollResultItem, clickListener: PollResultItemClickListener) {
|
||||||
|
val item = pollResultItem as PollResultVoterItem
|
||||||
|
|
||||||
|
binding.root.setOnClickListener { clickListener.onClick() }
|
||||||
|
|
||||||
|
binding.pollVoterName.text = item.details.actorDisplayName
|
||||||
|
binding.pollVoterAvatar.controller = getAvatarDraweeController(item.details)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getAvatarDraweeController(pollDetail: PollDetails): DraweeController? {
|
||||||
|
var draweeController: DraweeController? = null
|
||||||
|
if (pollDetail.actorType == "guests") {
|
||||||
|
var displayName = NextcloudTalkApplication.sharedApplication?.resources?.getString(R.string.nc_guest)
|
||||||
|
if (!TextUtils.isEmpty(pollDetail.actorDisplayName)) {
|
||||||
|
displayName = pollDetail.actorDisplayName!!
|
||||||
|
}
|
||||||
|
draweeController = Fresco.newDraweeControllerBuilder()
|
||||||
|
.setAutoPlayAnimations(true)
|
||||||
|
.setImageRequest(
|
||||||
|
DisplayUtils.getImageRequestForUrl(
|
||||||
|
ApiUtils.getUrlForGuestAvatar(
|
||||||
|
user.baseUrl,
|
||||||
|
displayName,
|
||||||
|
false
|
||||||
|
),
|
||||||
|
user
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
} else if (pollDetail.actorType == "users") {
|
||||||
|
draweeController = Fresco.newDraweeControllerBuilder()
|
||||||
|
.setAutoPlayAnimations(true)
|
||||||
|
.setImageRequest(
|
||||||
|
DisplayUtils.getImageRequestForUrl(
|
||||||
|
ApiUtils.getUrlForAvatar(
|
||||||
|
user.baseUrl,
|
||||||
|
pollDetail.actorId,
|
||||||
|
false
|
||||||
|
),
|
||||||
|
user
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
return draweeController
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* Nextcloud Talk application
|
||||||
|
*
|
||||||
|
* @author Marcel Hibbe
|
||||||
|
* Copyright (C) 2022 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 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.nextcloud.talk.polls.adapters
|
||||||
|
|
||||||
|
import com.nextcloud.talk.R
|
||||||
|
import com.nextcloud.talk.polls.model.PollDetails
|
||||||
|
|
||||||
|
data class PollResultVotersOverviewItem(
|
||||||
|
val detailsList: List<PollDetails>
|
||||||
|
) : PollResultItem {
|
||||||
|
|
||||||
|
override fun getViewType(): Int {
|
||||||
|
return VIEW_TYPE
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
// layout is used as view type for uniqueness
|
||||||
|
const val VIEW_TYPE: Int = R.layout.poll_result_voters_overview_item
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,141 @@
|
|||||||
|
/*
|
||||||
|
* Nextcloud Talk application
|
||||||
|
*
|
||||||
|
* @author Marcel Hibbe
|
||||||
|
* Copyright (C) 2022 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 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.nextcloud.talk.polls.adapters
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.text.TextUtils
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.core.content.res.ResourcesCompat
|
||||||
|
import com.facebook.drawee.backends.pipeline.Fresco
|
||||||
|
import com.facebook.drawee.generic.RoundingParams
|
||||||
|
import com.facebook.drawee.interfaces.DraweeController
|
||||||
|
import com.facebook.drawee.view.SimpleDraweeView
|
||||||
|
import com.nextcloud.talk.R
|
||||||
|
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||||
|
import com.nextcloud.talk.data.user.model.User
|
||||||
|
import com.nextcloud.talk.databinding.PollResultVotersOverviewItemBinding
|
||||||
|
import com.nextcloud.talk.polls.model.PollDetails
|
||||||
|
import com.nextcloud.talk.utils.ApiUtils
|
||||||
|
import com.nextcloud.talk.utils.DisplayUtils
|
||||||
|
|
||||||
|
class PollResultVotersOverviewViewHolder(
|
||||||
|
private val user: User,
|
||||||
|
override val binding: PollResultVotersOverviewItemBinding
|
||||||
|
) : PollResultViewHolder(binding) {
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
|
override fun bind(pollResultItem: PollResultItem, clickListener: PollResultItemClickListener) {
|
||||||
|
val item = pollResultItem as PollResultVotersOverviewItem
|
||||||
|
|
||||||
|
binding.root.setOnClickListener { clickListener.onClick() }
|
||||||
|
|
||||||
|
val layoutParams = LinearLayout.LayoutParams(
|
||||||
|
AVATAR_SIZE,
|
||||||
|
AVATAR_SIZE
|
||||||
|
)
|
||||||
|
|
||||||
|
var avatarsToDisplay = MAX_AVATARS
|
||||||
|
if (item.detailsList.size < avatarsToDisplay) {
|
||||||
|
avatarsToDisplay = item.detailsList.size
|
||||||
|
}
|
||||||
|
val shotsDots = item.detailsList.size > avatarsToDisplay
|
||||||
|
|
||||||
|
for (i in 0 until avatarsToDisplay) {
|
||||||
|
val pollDetails = item.detailsList[i]
|
||||||
|
val avatar = SimpleDraweeView(binding.root.context)
|
||||||
|
|
||||||
|
layoutParams.marginStart = i * AVATAR_OFFSET
|
||||||
|
avatar.layoutParams = layoutParams
|
||||||
|
|
||||||
|
avatar.translationZ = i.toFloat() * -1
|
||||||
|
|
||||||
|
val roundingParams = RoundingParams.fromCornersRadius(AVATAR_RADIUS)
|
||||||
|
roundingParams.roundAsCircle = true
|
||||||
|
roundingParams.borderColor = ResourcesCompat.getColor(
|
||||||
|
itemView.context.resources!!,
|
||||||
|
R.color.colorPrimary,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
roundingParams.borderWidth = 2.0f
|
||||||
|
|
||||||
|
avatar.hierarchy.roundingParams = roundingParams
|
||||||
|
avatar.controller = getAvatarDraweeController(pollDetails)
|
||||||
|
|
||||||
|
binding.votersAvatarsOverviewWrapper.addView(avatar)
|
||||||
|
|
||||||
|
if (i == avatarsToDisplay - 1 && shotsDots) {
|
||||||
|
val dotsView = TextView(itemView.context)
|
||||||
|
layoutParams.marginStart = i * AVATAR_OFFSET + DOTS_OFFSET
|
||||||
|
dotsView.layoutParams = layoutParams
|
||||||
|
dotsView.text = DOTS_TEXT
|
||||||
|
binding.votersAvatarsOverviewWrapper.addView(dotsView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getAvatarDraweeController(pollDetail: PollDetails): DraweeController? {
|
||||||
|
var draweeController: DraweeController? = null
|
||||||
|
if (pollDetail.actorType == "guests") {
|
||||||
|
var displayName = NextcloudTalkApplication.sharedApplication?.resources?.getString(R.string.nc_guest)
|
||||||
|
if (!TextUtils.isEmpty(pollDetail.actorDisplayName)) {
|
||||||
|
displayName = pollDetail.actorDisplayName!!
|
||||||
|
}
|
||||||
|
draweeController = Fresco.newDraweeControllerBuilder()
|
||||||
|
.setAutoPlayAnimations(true)
|
||||||
|
.setImageRequest(
|
||||||
|
DisplayUtils.getImageRequestForUrl(
|
||||||
|
ApiUtils.getUrlForGuestAvatar(
|
||||||
|
user.baseUrl,
|
||||||
|
displayName,
|
||||||
|
false
|
||||||
|
),
|
||||||
|
user
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
} else if (pollDetail.actorType == "users") {
|
||||||
|
draweeController = Fresco.newDraweeControllerBuilder()
|
||||||
|
.setAutoPlayAnimations(true)
|
||||||
|
.setImageRequest(
|
||||||
|
DisplayUtils.getImageRequestForUrl(
|
||||||
|
ApiUtils.getUrlForAvatar(
|
||||||
|
user.baseUrl,
|
||||||
|
pollDetail.actorId,
|
||||||
|
false
|
||||||
|
),
|
||||||
|
user
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
return draweeController
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val AVATAR_SIZE = 60
|
||||||
|
const val AVATAR_RADIUS = 5f
|
||||||
|
const val MAX_AVATARS = 10
|
||||||
|
const val AVATAR_OFFSET = AVATAR_SIZE - 10
|
||||||
|
const val DOTS_OFFSET = 70
|
||||||
|
const val DOTS_TEXT = "…"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
* Nextcloud Talk application
|
||||||
|
*
|
||||||
|
* @author Marcel Hibbe
|
||||||
|
* Copyright (C) 2022 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 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.nextcloud.talk.polls.adapters
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.nextcloud.talk.data.user.model.User
|
||||||
|
import com.nextcloud.talk.databinding.PollResultHeaderItemBinding
|
||||||
|
import com.nextcloud.talk.databinding.PollResultVoterItemBinding
|
||||||
|
import com.nextcloud.talk.databinding.PollResultVotersOverviewItemBinding
|
||||||
|
|
||||||
|
class PollResultsAdapter(
|
||||||
|
private val user: User,
|
||||||
|
private val clickListener: PollResultItemClickListener,
|
||||||
|
) : RecyclerView.Adapter<PollResultViewHolder>() {
|
||||||
|
internal var list: MutableList<PollResultItem> = ArrayList()
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PollResultViewHolder {
|
||||||
|
var viewHolder: PollResultViewHolder? = null
|
||||||
|
|
||||||
|
when (viewType) {
|
||||||
|
PollResultHeaderItem.VIEW_TYPE -> {
|
||||||
|
val itemBinding = PollResultHeaderItemBinding.inflate(
|
||||||
|
LayoutInflater.from(parent.context), parent,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
viewHolder = PollResultHeaderViewHolder(itemBinding)
|
||||||
|
}
|
||||||
|
PollResultVoterItem.VIEW_TYPE -> {
|
||||||
|
val itemBinding = PollResultVoterItemBinding.inflate(
|
||||||
|
LayoutInflater.from(parent.context), parent,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
viewHolder = PollResultVoterViewHolder(user, itemBinding)
|
||||||
|
}
|
||||||
|
PollResultVotersOverviewItem.VIEW_TYPE -> {
|
||||||
|
val itemBinding = PollResultVotersOverviewItemBinding.inflate(
|
||||||
|
LayoutInflater.from(parent.context), parent,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
viewHolder = PollResultVotersOverviewViewHolder(user, itemBinding)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return viewHolder!!
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: PollResultViewHolder, position: Int) {
|
||||||
|
when (holder.itemViewType) {
|
||||||
|
PollResultHeaderItem.VIEW_TYPE -> {
|
||||||
|
val pollResultItem = list[position]
|
||||||
|
holder.bind(pollResultItem as PollResultHeaderItem, clickListener)
|
||||||
|
}
|
||||||
|
PollResultVoterItem.VIEW_TYPE -> {
|
||||||
|
val pollResultItem = list[position]
|
||||||
|
holder.bind(pollResultItem as PollResultVoterItem, clickListener)
|
||||||
|
}
|
||||||
|
PollResultVotersOverviewItem.VIEW_TYPE -> {
|
||||||
|
val pollResultItem = list[position]
|
||||||
|
holder.bind(pollResultItem as PollResultVotersOverviewItem, clickListener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int {
|
||||||
|
return list.size
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemViewType(position: Int): Int {
|
||||||
|
return list[position].getViewType()
|
||||||
|
}
|
||||||
|
}
|
44
app/src/main/java/com/nextcloud/talk/polls/model/Poll.kt
Normal file
44
app/src/main/java/com/nextcloud/talk/polls/model/Poll.kt
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Nextcloud Talk application
|
||||||
|
*
|
||||||
|
* @author Marcel Hibbe
|
||||||
|
* Copyright (C) 2022 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 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.nextcloud.talk.polls.model
|
||||||
|
|
||||||
|
data class Poll(
|
||||||
|
val id: String,
|
||||||
|
val question: String?,
|
||||||
|
val options: List<String>?,
|
||||||
|
val votes: Map<String, 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,
|
||||||
|
val details: List<PollDetails>?
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
const val STATUS_OPEN: Int = 0
|
||||||
|
const val STATUS_CLOSED: Int = 1
|
||||||
|
const val RESULT_MODE_PUBLIC: Int = 0
|
||||||
|
const val RESULT_MODE_HIDDEN: Int = 1
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* Nextcloud Talk application
|
||||||
|
*
|
||||||
|
* @author Marcel Hibbe
|
||||||
|
* Copyright (C) 2022 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 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.nextcloud.talk.polls.model
|
||||||
|
|
||||||
|
data class PollDetails(
|
||||||
|
val actorType: String?,
|
||||||
|
val actorId: String?,
|
||||||
|
val actorDisplayName: String?,
|
||||||
|
val optionId: Int
|
||||||
|
)
|
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Nextcloud Talk application
|
||||||
|
*
|
||||||
|
* @author Marcel Hibbe
|
||||||
|
* @author Álvaro Brey
|
||||||
|
* Copyright (C) 2022 Álvaro Brey
|
||||||
|
* Copyright (C) 2022 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 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.polls.model.Poll
|
||||||
|
import io.reactivex.Observable
|
||||||
|
|
||||||
|
interface PollRepository {
|
||||||
|
|
||||||
|
fun createPoll(
|
||||||
|
roomToken: String,
|
||||||
|
question: String,
|
||||||
|
options: List<String>,
|
||||||
|
resultMode: Int,
|
||||||
|
maxVotes: Int
|
||||||
|
): Observable<Poll>
|
||||||
|
|
||||||
|
fun getPoll(roomToken: String, pollId: String): Observable<Poll>
|
||||||
|
|
||||||
|
fun vote(roomToken: String, pollId: String, options: List<Int>): Observable<Poll>
|
||||||
|
|
||||||
|
fun closePoll(roomToken: String, pollId: String): Observable<Poll>
|
||||||
|
}
|
@ -0,0 +1,138 @@
|
|||||||
|
/*
|
||||||
|
* Nextcloud Talk application
|
||||||
|
*
|
||||||
|
* @author Marcel Hibbe
|
||||||
|
* @author Álvaro Brey
|
||||||
|
* Copyright (C) 2022 Álvaro Brey
|
||||||
|
* Copyright (C) 2022 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 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.data.user.model.User
|
||||||
|
import com.nextcloud.talk.polls.model.Poll
|
||||||
|
import com.nextcloud.talk.polls.model.PollDetails
|
||||||
|
import com.nextcloud.talk.polls.repositories.model.PollDetailsResponse
|
||||||
|
import com.nextcloud.talk.polls.repositories.model.PollResponse
|
||||||
|
import com.nextcloud.talk.utils.ApiUtils
|
||||||
|
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
|
||||||
|
import io.reactivex.Observable
|
||||||
|
|
||||||
|
class PollRepositoryImpl(private val ncApi: NcApi, private val currentUserProvider: CurrentUserProviderNew) :
|
||||||
|
PollRepository {
|
||||||
|
|
||||||
|
val currentUser: User = currentUserProvider.currentUser.blockingGet()
|
||||||
|
val credentials: String = ApiUtils.getCredentials(currentUser.username, currentUser.token)
|
||||||
|
|
||||||
|
override fun createPoll(
|
||||||
|
roomToken: String,
|
||||||
|
question: String,
|
||||||
|
options: List<String>,
|
||||||
|
resultMode: Int,
|
||||||
|
maxVotes:
|
||||||
|
Int
|
||||||
|
): Observable<Poll> {
|
||||||
|
return ncApi.createPoll(
|
||||||
|
credentials,
|
||||||
|
ApiUtils.getUrlForPoll(
|
||||||
|
currentUser.baseUrl,
|
||||||
|
roomToken
|
||||||
|
),
|
||||||
|
question,
|
||||||
|
options,
|
||||||
|
resultMode,
|
||||||
|
maxVotes
|
||||||
|
).map { mapToPoll(it.ocs?.data!!) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getPoll(roomToken: String, pollId: String): Observable<Poll> {
|
||||||
|
|
||||||
|
return ncApi.getPoll(
|
||||||
|
credentials,
|
||||||
|
ApiUtils.getUrlForPoll(
|
||||||
|
currentUser.baseUrl,
|
||||||
|
roomToken,
|
||||||
|
pollId
|
||||||
|
),
|
||||||
|
).map { mapToPoll(it.ocs?.data!!) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun vote(roomToken: String, pollId: String, options: List<Int>): Observable<Poll> {
|
||||||
|
|
||||||
|
return ncApi.votePoll(
|
||||||
|
credentials,
|
||||||
|
ApiUtils.getUrlForPoll(
|
||||||
|
currentUser.baseUrl,
|
||||||
|
roomToken,
|
||||||
|
pollId
|
||||||
|
),
|
||||||
|
options
|
||||||
|
).map { mapToPoll(it.ocs?.data!!) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun closePoll(roomToken: String, pollId: String): Observable<Poll> {
|
||||||
|
|
||||||
|
return ncApi.closePoll(
|
||||||
|
credentials,
|
||||||
|
ApiUtils.getUrlForPoll(
|
||||||
|
currentUser.baseUrl,
|
||||||
|
roomToken,
|
||||||
|
pollId
|
||||||
|
),
|
||||||
|
).map { mapToPoll(it.ocs?.data!!) }
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private fun mapToPoll(pollResponse: PollResponse): Poll {
|
||||||
|
val pollDetails = pollResponse.details?.map { it -> mapToPollDetails(it) }
|
||||||
|
|
||||||
|
return Poll(
|
||||||
|
pollResponse.id,
|
||||||
|
pollResponse.question,
|
||||||
|
pollResponse.options,
|
||||||
|
convertVotes(pollResponse.votes),
|
||||||
|
pollResponse.actorType,
|
||||||
|
pollResponse.actorId,
|
||||||
|
pollResponse.actorDisplayName,
|
||||||
|
pollResponse.status,
|
||||||
|
pollResponse.resultMode,
|
||||||
|
pollResponse.maxVotes,
|
||||||
|
pollResponse.votedSelf,
|
||||||
|
pollResponse.numVoters,
|
||||||
|
pollDetails
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun convertVotes(votes: Map<String, Int>?): Map<String, Int> {
|
||||||
|
val resultMap: MutableMap<String, Int> = HashMap()
|
||||||
|
votes?.forEach {
|
||||||
|
resultMap[it.key.replace("option-", "")] = it.value
|
||||||
|
}
|
||||||
|
return resultMap
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun mapToPollDetails(pollDetailsResponse: PollDetailsResponse): PollDetails {
|
||||||
|
return PollDetails(
|
||||||
|
pollDetailsResponse.actorType,
|
||||||
|
pollDetailsResponse.actorId,
|
||||||
|
pollDetailsResponse.actorDisplayName,
|
||||||
|
pollDetailsResponse.optionId,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Nextcloud Talk application
|
||||||
|
*
|
||||||
|
* @author Marcel Hibbe
|
||||||
|
* Copyright (C) 2022 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 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package com.nextcloud.talk.polls.repositories.model
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import com.bluelinelabs.logansquare.annotation.JsonField
|
||||||
|
import com.bluelinelabs.logansquare.annotation.JsonObject
|
||||||
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
@JsonObject
|
||||||
|
data class PollDetailsResponse(
|
||||||
|
@JsonField(name = ["actorType"])
|
||||||
|
var actorType: String? = null,
|
||||||
|
|
||||||
|
@JsonField(name = ["actorId"])
|
||||||
|
var actorId: String,
|
||||||
|
|
||||||
|
@JsonField(name = ["actorDisplayName"])
|
||||||
|
var actorDisplayName: String,
|
||||||
|
|
||||||
|
@JsonField(name = ["optionId"])
|
||||||
|
var optionId: Int,
|
||||||
|
) : Parcelable {
|
||||||
|
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
|
||||||
|
constructor() : this(null, "", "", 0)
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* Nextcloud Talk application
|
||||||
|
*
|
||||||
|
* @author Marcel Hibbe
|
||||||
|
* Copyright (C) 2022 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 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package com.nextcloud.talk.polls.repositories.model
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import com.bluelinelabs.logansquare.annotation.JsonField
|
||||||
|
import com.bluelinelabs.logansquare.annotation.JsonObject
|
||||||
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
@JsonObject
|
||||||
|
data class PollOCS(
|
||||||
|
@JsonField(name = ["data"])
|
||||||
|
var data: PollResponse?
|
||||||
|
) : Parcelable {
|
||||||
|
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
|
||||||
|
constructor() : this(null)
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* Nextcloud Talk application
|
||||||
|
*
|
||||||
|
* @author Marcel Hibbe
|
||||||
|
* Copyright (C) 2022 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 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package com.nextcloud.talk.polls.repositories.model
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import com.bluelinelabs.logansquare.annotation.JsonField
|
||||||
|
import com.bluelinelabs.logansquare.annotation.JsonObject
|
||||||
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
@JsonObject
|
||||||
|
data class PollOverall(
|
||||||
|
@JsonField(name = ["ocs"])
|
||||||
|
var ocs: PollOCS? = null
|
||||||
|
) : Parcelable {
|
||||||
|
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
|
||||||
|
constructor() : this(null)
|
||||||
|
}
|
@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
* Nextcloud Talk application
|
||||||
|
*
|
||||||
|
* @author Marcel Hibbe
|
||||||
|
* Copyright (C) 2022 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 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package com.nextcloud.talk.polls.repositories.model
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import com.bluelinelabs.logansquare.annotation.JsonField
|
||||||
|
import com.bluelinelabs.logansquare.annotation.JsonObject
|
||||||
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
@JsonObject
|
||||||
|
data class PollResponse(
|
||||||
|
@JsonField(name = ["id"])
|
||||||
|
var id: String,
|
||||||
|
|
||||||
|
@JsonField(name = ["question"])
|
||||||
|
var question: String? = null,
|
||||||
|
|
||||||
|
@JsonField(name = ["options"])
|
||||||
|
var options: ArrayList<String>? = null,
|
||||||
|
|
||||||
|
@JsonField(name = ["votes"])
|
||||||
|
var votes: Map<String, Int>? = null,
|
||||||
|
|
||||||
|
@JsonField(name = ["actorType"])
|
||||||
|
var actorType: String? = null,
|
||||||
|
|
||||||
|
@JsonField(name = ["actorId"])
|
||||||
|
var actorId: String? = null,
|
||||||
|
|
||||||
|
@JsonField(name = ["actorDisplayName"])
|
||||||
|
var actorDisplayName: String? = null,
|
||||||
|
|
||||||
|
@JsonField(name = ["status"])
|
||||||
|
var status: Int = 0,
|
||||||
|
|
||||||
|
@JsonField(name = ["resultMode"])
|
||||||
|
var resultMode: Int = 0,
|
||||||
|
|
||||||
|
@JsonField(name = ["maxVotes"])
|
||||||
|
var maxVotes: Int = 0,
|
||||||
|
|
||||||
|
@JsonField(name = ["votedSelf"])
|
||||||
|
var votedSelf: ArrayList<Int>? = null,
|
||||||
|
|
||||||
|
@JsonField(name = ["numVoters"])
|
||||||
|
var numVoters: Int = 0,
|
||||||
|
|
||||||
|
@JsonField(name = ["details"])
|
||||||
|
var details: ArrayList<PollDetailsResponse>? = null,
|
||||||
|
) : Parcelable {
|
||||||
|
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
|
||||||
|
constructor() : this("id", null, null, null, null, null, null, 0, 0, 0, null, 0, null)
|
||||||
|
}
|
@ -0,0 +1,187 @@
|
|||||||
|
/*
|
||||||
|
* Nextcloud Talk application
|
||||||
|
*
|
||||||
|
* @author Marcel Hibbe
|
||||||
|
* Copyright (C) 2022 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 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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.util.Log
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.EditText
|
||||||
|
import android.widget.Toast
|
||||||
|
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.R
|
||||||
|
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.PollCreateOptionsItemListener
|
||||||
|
import com.nextcloud.talk.polls.viewmodels.PollCreateViewModel
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@AutoInjector(NextcloudTalkApplication::class)
|
||||||
|
class PollCreateDialogFragment : DialogFragment(), PollCreateOptionsItemListener {
|
||||||
|
|
||||||
|
@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]
|
||||||
|
val roomToken = arguments?.getString(KEY_ROOM_TOKEN)!!
|
||||||
|
viewModel.setData(roomToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("InflateParams")
|
||||||
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
|
binding = DialogPollCreateBinding.inflate(LayoutInflater.from(context))
|
||||||
|
|
||||||
|
return AlertDialog.Builder(requireContext())
|
||||||
|
.setView(binding.root)
|
||||||
|
.create()
|
||||||
|
}
|
||||||
|
|
||||||
|
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.options.observe(viewLifecycleOwner) { options -> adapter?.updateOptionsList(options) }
|
||||||
|
|
||||||
|
binding.pollCreateOptionsList.layoutManager = LinearLayoutManager(context)
|
||||||
|
|
||||||
|
adapter = PollCreateOptionsAdapter(this)
|
||||||
|
binding.pollCreateOptionsList.adapter = adapter
|
||||||
|
|
||||||
|
setupListeners()
|
||||||
|
setupStateObserver()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupListeners() {
|
||||||
|
binding.pollAddOptionsItem.setOnClickListener {
|
||||||
|
viewModel.addOption()
|
||||||
|
adapter?.itemCount?.minus(1)?.let { binding.pollCreateOptionsList.scrollToPosition(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
if (question.toString() != viewModel.question) {
|
||||||
|
viewModel.setQuestion(question.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
binding.pollPrivatePollCheckbox.setOnClickListener {
|
||||||
|
viewModel.setPrivatePoll(binding.pollPrivatePollCheckbox.isChecked)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.pollMultipleAnswersCheckbox.setOnClickListener {
|
||||||
|
viewModel.setMultipleAnswer(binding.pollMultipleAnswersCheckbox.isChecked)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.pollCreateButton.setOnClickListener {
|
||||||
|
viewModel.createPoll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupStateObserver() {
|
||||||
|
viewModel.viewState.observe(viewLifecycleOwner) { state ->
|
||||||
|
when (state) {
|
||||||
|
is PollCreateViewModel.PollCreatedState -> dismiss()
|
||||||
|
is PollCreateViewModel.PollCreationFailedState -> showError()
|
||||||
|
is PollCreateViewModel.PollCreationState -> updateButtons(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateButtons(state: PollCreateViewModel.PollCreationState) {
|
||||||
|
binding.pollAddOptionsItem.isEnabled = state.enableAddOptionButton
|
||||||
|
binding.pollCreateButton.isEnabled = state.enableCreatePollButton
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showError() {
|
||||||
|
dismiss()
|
||||||
|
Log.e(TAG, "Failed to create poll")
|
||||||
|
Toast.makeText(context, R.string.nc_common_error_sorry, Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRemoveOptionsItemClick(pollCreateOptionItem: PollCreateOptionItem, position: Int) {
|
||||||
|
viewModel.removeOption(pollCreateOptionItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOptionsItemTextChanged(pollCreateOptionItem: PollCreateOptionItem) {
|
||||||
|
viewModel.optionsItemTextChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun requestFocus(textField: EditText) {
|
||||||
|
if (binding.pollCreateQuestion.text.isBlank()) {
|
||||||
|
binding.pollCreateQuestion.requestFocus()
|
||||||
|
} else {
|
||||||
|
textField.requestFocus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fragment creator
|
||||||
|
*/
|
||||||
|
companion object {
|
||||||
|
private val TAG = PollCreateDialogFragment::class.java.simpleName
|
||||||
|
private const val KEY_ROOM_TOKEN = "keyRoomToken"
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun newInstance(roomTokenParam: String): PollCreateDialogFragment {
|
||||||
|
val args = Bundle()
|
||||||
|
args.putString(KEY_ROOM_TOKEN, roomTokenParam)
|
||||||
|
val fragment = PollCreateDialogFragment()
|
||||||
|
fragment.arguments = args
|
||||||
|
return fragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* Nextcloud Talk application
|
||||||
|
*
|
||||||
|
* @author Marcel Hibbe
|
||||||
|
* Copyright (C) 2022 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 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 androidx.core.os.bundleOf
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import autodagger.AutoInjector
|
||||||
|
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||||
|
import com.nextcloud.talk.databinding.DialogPollLoadingBinding
|
||||||
|
|
||||||
|
@AutoInjector(NextcloudTalkApplication::class)
|
||||||
|
class PollLoadingFragment : Fragment() {
|
||||||
|
|
||||||
|
private lateinit var binding: DialogPollLoadingBinding
|
||||||
|
|
||||||
|
var fragmentHeight = 0
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
|
||||||
|
fragmentHeight = arguments?.getInt(KEY_FRAGMENT_HEIGHT)!!
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
binding = DialogPollLoadingBinding.inflate(inflater, container, false)
|
||||||
|
binding.root.layoutParams.height = fragmentHeight
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val TAG = PollLoadingFragment::class.java.simpleName
|
||||||
|
private const val KEY_FRAGMENT_HEIGHT = "keyFragmentHeight"
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun newInstance(
|
||||||
|
fragmentHeight: Int
|
||||||
|
): PollLoadingFragment {
|
||||||
|
|
||||||
|
val args = bundleOf(
|
||||||
|
KEY_FRAGMENT_HEIGHT to fragmentHeight,
|
||||||
|
)
|
||||||
|
|
||||||
|
val fragment = PollLoadingFragment()
|
||||||
|
fragment.arguments = args
|
||||||
|
return fragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,188 @@
|
|||||||
|
/*
|
||||||
|
* Nextcloud Talk application
|
||||||
|
*
|
||||||
|
* @author Marcel Hibbe
|
||||||
|
* Copyright (C) 2022 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 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.nextcloud.talk.polls.ui
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.core.os.bundleOf
|
||||||
|
import androidx.fragment.app.DialogFragment
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import autodagger.AutoInjector
|
||||||
|
import com.nextcloud.talk.R
|
||||||
|
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||||
|
import com.nextcloud.talk.data.user.model.User
|
||||||
|
import com.nextcloud.talk.databinding.DialogPollMainBinding
|
||||||
|
import com.nextcloud.talk.polls.viewmodels.PollMainViewModel
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@AutoInjector(NextcloudTalkApplication::class)
|
||||||
|
class PollMainDialogFragment : DialogFragment() {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var viewModelFactory: ViewModelProvider.Factory
|
||||||
|
|
||||||
|
private lateinit var binding: DialogPollMainBinding
|
||||||
|
private lateinit var viewModel: PollMainViewModel
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
|
||||||
|
|
||||||
|
viewModel = ViewModelProvider(this, viewModelFactory)[PollMainViewModel::class.java]
|
||||||
|
|
||||||
|
val user: User = arguments?.getParcelable(KEY_USER_ENTITY)!!
|
||||||
|
val roomToken = arguments?.getString(KEY_ROOM_TOKEN)!!
|
||||||
|
val isOwnerOrModerator = arguments?.getBoolean(KEY_OWNER_OR_MODERATOR)!!
|
||||||
|
val pollId = arguments?.getString(KEY_POLL_ID)!!
|
||||||
|
val pollTitle = arguments?.getString(KEY_POLL_TITLE)!!
|
||||||
|
|
||||||
|
viewModel.setData(user, roomToken, isOwnerOrModerator, pollId, pollTitle)
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("InflateParams")
|
||||||
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
|
binding = DialogPollMainBinding.inflate(LayoutInflater.from(context))
|
||||||
|
|
||||||
|
val dialog = AlertDialog.Builder(requireContext())
|
||||||
|
.setView(binding.root)
|
||||||
|
.create()
|
||||||
|
|
||||||
|
binding.messagePollTitle.text = viewModel.pollTitle
|
||||||
|
|
||||||
|
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) {
|
||||||
|
PollMainViewModel.InitialState -> {}
|
||||||
|
is PollMainViewModel.PollVoteState -> {
|
||||||
|
initVotersAmount(state.showVotersAmount, state.poll.numVoters, false)
|
||||||
|
showVoteScreen()
|
||||||
|
}
|
||||||
|
is PollMainViewModel.PollResultState -> {
|
||||||
|
initVotersAmount(state.showVotersAmount, state.poll.numVoters, true)
|
||||||
|
showResultsScreen()
|
||||||
|
}
|
||||||
|
is PollMainViewModel.LoadingState -> {
|
||||||
|
showLoadingScreen()
|
||||||
|
}
|
||||||
|
is PollMainViewModel.DismissDialogState -> {
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showLoadingScreen() {
|
||||||
|
binding.root.post {
|
||||||
|
run() {
|
||||||
|
val fragmentHeight = binding.messagePollContentFragment.measuredHeight
|
||||||
|
|
||||||
|
val contentFragment = PollLoadingFragment.newInstance(fragmentHeight)
|
||||||
|
val transaction = childFragmentManager.beginTransaction()
|
||||||
|
transaction.replace(binding.messagePollContentFragment.id, contentFragment)
|
||||||
|
transaction.commit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showVoteScreen() {
|
||||||
|
val contentFragment = PollVoteFragment.newInstance()
|
||||||
|
|
||||||
|
val transaction = childFragmentManager.beginTransaction()
|
||||||
|
transaction.replace(binding.messagePollContentFragment.id, contentFragment)
|
||||||
|
transaction.commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showResultsScreen() {
|
||||||
|
val contentFragment = PollResultsFragment.newInstance()
|
||||||
|
|
||||||
|
val transaction = childFragmentManager.beginTransaction()
|
||||||
|
transaction.replace(binding.messagePollContentFragment.id, contentFragment)
|
||||||
|
transaction.commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initVotersAmount(showVotersAmount: Boolean, numVoters: Int, showResultSubtitle: Boolean) {
|
||||||
|
if (showVotersAmount) {
|
||||||
|
binding.pollVotesAmount.visibility = View.VISIBLE
|
||||||
|
binding.pollVotesAmount.text = String.format(
|
||||||
|
resources.getString(R.string.polls_amount_voters),
|
||||||
|
numVoters
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
binding.pollVotesAmount.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showResultSubtitle) {
|
||||||
|
binding.pollResultsSubtitle.visibility = View.VISIBLE
|
||||||
|
binding.pollResultsSubtitleSeperator.visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
binding.pollResultsSubtitle.visibility = View.GONE
|
||||||
|
binding.pollResultsSubtitleSeperator.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fragment creator
|
||||||
|
*/
|
||||||
|
companion object {
|
||||||
|
private const val KEY_USER_ENTITY = "keyUserEntity"
|
||||||
|
private const val KEY_ROOM_TOKEN = "keyRoomToken"
|
||||||
|
private const val KEY_OWNER_OR_MODERATOR = "keyIsOwnerOrModerator"
|
||||||
|
private const val KEY_POLL_ID = "keyPollId"
|
||||||
|
private const val KEY_POLL_TITLE = "keyPollTitle"
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun newInstance(
|
||||||
|
user: User,
|
||||||
|
roomTokenParam: String,
|
||||||
|
isOwnerOrModerator: Boolean,
|
||||||
|
pollId: String,
|
||||||
|
name: String
|
||||||
|
): PollMainDialogFragment {
|
||||||
|
|
||||||
|
val args = bundleOf(
|
||||||
|
KEY_USER_ENTITY to user,
|
||||||
|
KEY_ROOM_TOKEN to roomTokenParam,
|
||||||
|
KEY_OWNER_OR_MODERATOR to isOwnerOrModerator,
|
||||||
|
KEY_POLL_ID to pollId,
|
||||||
|
KEY_POLL_TITLE to name
|
||||||
|
)
|
||||||
|
|
||||||
|
val fragment = PollMainDialogFragment()
|
||||||
|
fragment.arguments = args
|
||||||
|
return fragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,139 @@
|
|||||||
|
/*
|
||||||
|
* Nextcloud Talk application
|
||||||
|
*
|
||||||
|
* @author Marcel Hibbe
|
||||||
|
* @author Álvaro Brey
|
||||||
|
* Copyright (C) 2022 Álvaro Brey
|
||||||
|
* Copyright (C) 2022 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 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 androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import autodagger.AutoInjector
|
||||||
|
import com.nextcloud.talk.R
|
||||||
|
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||||
|
import com.nextcloud.talk.databinding.DialogPollResultsBinding
|
||||||
|
import com.nextcloud.talk.polls.adapters.PollResultItemClickListener
|
||||||
|
import com.nextcloud.talk.polls.adapters.PollResultsAdapter
|
||||||
|
import com.nextcloud.talk.polls.viewmodels.PollMainViewModel
|
||||||
|
import com.nextcloud.talk.polls.viewmodels.PollResultsViewModel
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@AutoInjector(NextcloudTalkApplication::class)
|
||||||
|
class PollResultsFragment : Fragment(), PollResultItemClickListener {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var viewModelFactory: ViewModelProvider.Factory
|
||||||
|
|
||||||
|
private lateinit var parentViewModel: PollMainViewModel
|
||||||
|
lateinit var viewModel: PollResultsViewModel
|
||||||
|
|
||||||
|
lateinit var binding: DialogPollResultsBinding
|
||||||
|
|
||||||
|
private var adapter: PollResultsAdapter? = null
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
|
||||||
|
viewModel = ViewModelProvider(this, viewModelFactory)[PollResultsViewModel::class.java]
|
||||||
|
parentViewModel = ViewModelProvider(requireParentFragment(), viewModelFactory)[PollMainViewModel::class.java]
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
binding = DialogPollResultsBinding.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 PollMainViewModel.PollResultState) {
|
||||||
|
initAdapter()
|
||||||
|
viewModel.setPoll(state.poll)
|
||||||
|
initEditButton(state.showEditButton)
|
||||||
|
initEndPollButton(state.showEndPollButton)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.items.observe(viewLifecycleOwner) {
|
||||||
|
val adapter = PollResultsAdapter(parentViewModel.user, this).apply {
|
||||||
|
if (it != null) {
|
||||||
|
list = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
binding.pollResultsList.adapter = adapter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initAdapter() {
|
||||||
|
adapter = PollResultsAdapter(parentViewModel.user, this)
|
||||||
|
binding.pollResultsList.adapter = adapter
|
||||||
|
binding.pollResultsList.layoutManager = LinearLayoutManager(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initEditButton(showEditButton: Boolean) {
|
||||||
|
if (showEditButton) {
|
||||||
|
binding.editVoteButton.visibility = View.VISIBLE
|
||||||
|
binding.editVoteButton.setOnClickListener {
|
||||||
|
parentViewModel.editVotes()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
binding.editVoteButton.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initEndPollButton(showEndPollButton: Boolean) {
|
||||||
|
if (showEndPollButton) {
|
||||||
|
binding.pollResultsEndPollButton.visibility = View.VISIBLE
|
||||||
|
binding.pollResultsEndPollButton.setOnClickListener {
|
||||||
|
AlertDialog.Builder(requireContext())
|
||||||
|
.setTitle(R.string.polls_end_poll)
|
||||||
|
.setMessage(R.string.polls_end_poll_confirm)
|
||||||
|
.setPositiveButton(R.string.polls_end_poll) { _, _ ->
|
||||||
|
parentViewModel.endPoll()
|
||||||
|
}
|
||||||
|
.setNegativeButton(R.string.nc_cancel, null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
binding.pollResultsEndPollButton.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClick() {
|
||||||
|
viewModel.toggleDetails()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
fun newInstance(): PollResultsFragment {
|
||||||
|
return PollResultsFragment()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,219 @@
|
|||||||
|
/*
|
||||||
|
* Nextcloud Talk application
|
||||||
|
*
|
||||||
|
* @author Marcel Hibbe
|
||||||
|
* @author Álvaro Brey
|
||||||
|
* Copyright (C) 2022 Álvaro Brey
|
||||||
|
* Copyright (C) 2022 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 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.graphics.Typeface
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.CheckBox
|
||||||
|
import android.widget.CompoundButton
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.RadioButton
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import autodagger.AutoInjector
|
||||||
|
import com.nextcloud.talk.R
|
||||||
|
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.PollMainViewModel
|
||||||
|
import com.nextcloud.talk.polls.viewmodels.PollVoteViewModel
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@AutoInjector(NextcloudTalkApplication::class)
|
||||||
|
class PollVoteFragment : Fragment() {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var viewModelFactory: ViewModelProvider.Factory
|
||||||
|
|
||||||
|
private lateinit var parentViewModel: PollMainViewModel
|
||||||
|
lateinit var viewModel: PollVoteViewModel
|
||||||
|
|
||||||
|
private lateinit var binding: DialogPollVoteBinding
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
|
||||||
|
viewModel = ViewModelProvider(this, viewModelFactory)[PollVoteViewModel::class.java]
|
||||||
|
|
||||||
|
parentViewModel = ViewModelProvider(requireParentFragment(), viewModelFactory)[PollMainViewModel::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 PollMainViewModel.PollVoteState) {
|
||||||
|
initPollOptions(state.poll)
|
||||||
|
initEndPollButton(state.showEndPollButton)
|
||||||
|
updateSubmitButton()
|
||||||
|
updateDismissEditButton(state.showDismissEditButton)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.viewState.observe(viewLifecycleOwner) { state ->
|
||||||
|
when (state) {
|
||||||
|
PollVoteViewModel.InitialState -> {}
|
||||||
|
is PollVoteViewModel.PollVoteFailedState -> {
|
||||||
|
Log.e(TAG, "Failed to vote on poll.")
|
||||||
|
Toast.makeText(context, R.string.nc_common_error_sorry, Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
is PollVoteViewModel.PollVoteHiddenSuccessState -> {
|
||||||
|
Toast.makeText(context, R.string.polls_voted_hidden_success, Toast.LENGTH_LONG).show()
|
||||||
|
parentViewModel.dismissDialog()
|
||||||
|
}
|
||||||
|
is PollVoteViewModel.PollVoteSuccessState -> {
|
||||||
|
parentViewModel.voted()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.submitButtonEnabled.observe(viewLifecycleOwner) { enabled ->
|
||||||
|
binding.pollVoteSubmitButton.isEnabled = enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.pollVoteRadioGroup.setOnCheckedChangeListener { _, checkedId ->
|
||||||
|
viewModel.selectOption(checkedId, true)
|
||||||
|
updateSubmitButton()
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.pollVoteSubmitButton.setOnClickListener {
|
||||||
|
viewModel.vote(parentViewModel.roomToken, parentViewModel.pollId)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.pollVoteEditDismiss.setOnClickListener {
|
||||||
|
parentViewModel.dismissEditVotes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateDismissEditButton(showDismissEditButton: Boolean) {
|
||||||
|
if (showDismissEditButton) {
|
||||||
|
binding.pollVoteEditDismiss.visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
binding.pollVoteEditDismiss.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initPollOptions(poll: Poll) {
|
||||||
|
poll.votedSelf?.let { viewModel.initVotedOptions(it as ArrayList<Int>) }
|
||||||
|
|
||||||
|
if (poll.maxVotes == 1) {
|
||||||
|
binding.pollVoteRadioGroup.removeAllViews()
|
||||||
|
poll.options?.map { option ->
|
||||||
|
RadioButton(context).apply { text = option }
|
||||||
|
}?.forEachIndexed { index, radioButton ->
|
||||||
|
radioButton.id = index
|
||||||
|
makeOptionBoldIfSelfVoted(radioButton, poll, index)
|
||||||
|
binding.pollVoteRadioGroup.addView(radioButton)
|
||||||
|
|
||||||
|
radioButton.isChecked = viewModel.selectedOptions.contains(index) == true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
binding.voteOptionsCheckboxesWrapper.removeAllViews()
|
||||||
|
|
||||||
|
val layoutParams = LinearLayout.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
|
)
|
||||||
|
layoutParams.marginStart = CHECKBOX_MARGIN_LEFT
|
||||||
|
|
||||||
|
poll.options?.map { option ->
|
||||||
|
CheckBox(context).apply {
|
||||||
|
text = option
|
||||||
|
setLayoutParams(layoutParams)
|
||||||
|
}
|
||||||
|
}?.forEachIndexed { index, checkBox ->
|
||||||
|
checkBox.id = index
|
||||||
|
makeOptionBoldIfSelfVoted(checkBox, poll, index)
|
||||||
|
binding.voteOptionsCheckboxesWrapper.addView(checkBox)
|
||||||
|
|
||||||
|
checkBox.isChecked = viewModel.selectedOptions.contains(index) == true
|
||||||
|
checkBox.setOnCheckedChangeListener { _, isChecked ->
|
||||||
|
if (isChecked) {
|
||||||
|
if (poll.maxVotes == UNLIMITED_VOTES || viewModel.selectedOptions.size < poll.maxVotes) {
|
||||||
|
viewModel.selectOption(index, false)
|
||||||
|
} else {
|
||||||
|
checkBox.isChecked = false
|
||||||
|
Toast.makeText(context, R.string.polls_max_votes_reached, Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
viewModel.deSelectOption(index)
|
||||||
|
}
|
||||||
|
updateSubmitButton()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateSubmitButton() {
|
||||||
|
viewModel.updateSubmitButton()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun makeOptionBoldIfSelfVoted(button: CompoundButton, poll: Poll, index: Int) {
|
||||||
|
if (poll.votedSelf?.contains(index) == true) {
|
||||||
|
button.setTypeface(null, Typeface.BOLD)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initEndPollButton(showEndPollButton: Boolean) {
|
||||||
|
if (showEndPollButton) {
|
||||||
|
binding.pollVoteEndPollButton.visibility = View.VISIBLE
|
||||||
|
binding.pollVoteEndPollButton.setOnClickListener {
|
||||||
|
AlertDialog.Builder(requireContext())
|
||||||
|
.setTitle(R.string.polls_end_poll)
|
||||||
|
.setMessage(R.string.polls_end_poll_confirm)
|
||||||
|
.setPositiveButton(R.string.polls_end_poll) { _, _ ->
|
||||||
|
parentViewModel.endPoll()
|
||||||
|
}
|
||||||
|
.setNegativeButton(R.string.nc_cancel, null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
binding.pollVoteEndPollButton.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val TAG = PollVoteFragment::class.java.simpleName
|
||||||
|
private const val UNLIMITED_VOTES = 0
|
||||||
|
private const val CHECKBOX_MARGIN_LEFT = -18
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun newInstance(): PollVoteFragment {
|
||||||
|
return PollVoteFragment()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,204 @@
|
|||||||
|
/*
|
||||||
|
* Nextcloud Talk application
|
||||||
|
*
|
||||||
|
* @author Marcel Hibbe
|
||||||
|
* Copyright (C) 2022 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 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.nextcloud.talk.polls.viewmodels
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import com.nextcloud.talk.polls.adapters.PollCreateOptionItem
|
||||||
|
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
|
||||||
|
|
||||||
|
sealed interface ViewState
|
||||||
|
open class PollCreationState(val enableAddOptionButton: Boolean, val enableCreatePollButton: Boolean) : ViewState
|
||||||
|
object PollCreatedState : ViewState
|
||||||
|
object PollCreationFailedState : ViewState
|
||||||
|
|
||||||
|
private val _viewState: MutableLiveData<ViewState> = MutableLiveData(
|
||||||
|
PollCreationState(
|
||||||
|
enableAddOptionButton = true,
|
||||||
|
enableCreatePollButton = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val viewState: LiveData<ViewState>
|
||||||
|
get() = _viewState
|
||||||
|
|
||||||
|
private var _options: MutableLiveData<ArrayList<PollCreateOptionItem>> =
|
||||||
|
MutableLiveData<ArrayList<PollCreateOptionItem>>()
|
||||||
|
val options: LiveData<ArrayList<PollCreateOptionItem>>
|
||||||
|
get() = _options
|
||||||
|
|
||||||
|
private var _question: String = ""
|
||||||
|
val question: String
|
||||||
|
get() = _question
|
||||||
|
|
||||||
|
private var _privatePoll: Boolean = false
|
||||||
|
val privatePoll: Boolean
|
||||||
|
get() = _privatePoll
|
||||||
|
|
||||||
|
private var _multipleAnswer: Boolean = false
|
||||||
|
val multipleAnswer: Boolean
|
||||||
|
get() = _multipleAnswer
|
||||||
|
|
||||||
|
private var disposable: Disposable? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
addOption()
|
||||||
|
addOption()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setData(roomToken: String) {
|
||||||
|
this.roomToken = roomToken
|
||||||
|
updateCreationState()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCleared() {
|
||||||
|
super.onCleared()
|
||||||
|
disposable?.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addOption() {
|
||||||
|
val item = PollCreateOptionItem("")
|
||||||
|
val currentOptions: ArrayList<PollCreateOptionItem> = _options.value ?: ArrayList()
|
||||||
|
currentOptions.add(item)
|
||||||
|
_options.value = currentOptions
|
||||||
|
updateCreationState()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeOption(item: PollCreateOptionItem) {
|
||||||
|
val currentOptions: ArrayList<PollCreateOptionItem> = _options.value ?: ArrayList()
|
||||||
|
currentOptions.remove(item)
|
||||||
|
_options.value = currentOptions
|
||||||
|
updateCreationState()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createPoll() {
|
||||||
|
var maxVotes = 1
|
||||||
|
if (multipleAnswer) {
|
||||||
|
maxVotes = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
var resultMode = 0
|
||||||
|
if (privatePoll) {
|
||||||
|
resultMode = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
_options.value = _options.value?.filter { it.pollOption.isNotEmpty() } as ArrayList<PollCreateOptionItem>
|
||||||
|
|
||||||
|
if (_question.isNotEmpty() && _options.value?.isNotEmpty() == true) {
|
||||||
|
_viewState.value = PollCreationState(enableAddOptionButton = false, enableCreatePollButton = false)
|
||||||
|
|
||||||
|
repository.createPoll(
|
||||||
|
roomToken, _question, _options.value!!.map { it.pollOption }, resultMode,
|
||||||
|
maxVotes
|
||||||
|
)
|
||||||
|
.doOnSubscribe { disposable = it }
|
||||||
|
?.subscribeOn(Schedulers.io())
|
||||||
|
?.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
?.subscribe(PollObserver())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setQuestion(question: String) {
|
||||||
|
_question = question
|
||||||
|
updateCreationState()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setPrivatePoll(checked: Boolean) {
|
||||||
|
_privatePoll = checked
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setMultipleAnswer(checked: Boolean) {
|
||||||
|
_multipleAnswer = checked
|
||||||
|
}
|
||||||
|
|
||||||
|
fun optionsItemTextChanged() {
|
||||||
|
updateCreationState()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateCreationState() {
|
||||||
|
_viewState.value = PollCreationState(enableAddOptionButton(), enableCreatePollButton())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun enableCreatePollButton(): Boolean {
|
||||||
|
return _question.isNotEmpty() && atLeastTwoOptionsAreFilled()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun atLeastTwoOptionsAreFilled(): Boolean {
|
||||||
|
if (_options.value != null) {
|
||||||
|
var filledOptions = 0
|
||||||
|
_options.value?.forEach {
|
||||||
|
if (it.pollOption.isNotEmpty()) {
|
||||||
|
filledOptions++
|
||||||
|
}
|
||||||
|
if (filledOptions >= 2) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun enableAddOptionButton(): Boolean {
|
||||||
|
if (_options.value != null && _options.value?.size != 0) {
|
||||||
|
_options.value?.forEach {
|
||||||
|
if (it.pollOption.isBlank()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
Log.e(TAG, "Failed to create poll", e)
|
||||||
|
_viewState.value = PollCreationFailedState
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onComplete() {
|
||||||
|
_viewState.value = PollCreatedState
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val TAG = PollCreateViewModel::class.java.simpleName
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,188 @@
|
|||||||
|
/*
|
||||||
|
* Nextcloud Talk application
|
||||||
|
*
|
||||||
|
* @author Marcel Hibbe
|
||||||
|
* Copyright (C) 2022 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 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.nextcloud.talk.polls.viewmodels
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import com.nextcloud.talk.data.user.model.User
|
||||||
|
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
|
||||||
|
import io.reactivex.schedulers.Schedulers
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class PollMainViewModel @Inject constructor(private val repository: PollRepository) : ViewModel() {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var userUtils: UserUtils
|
||||||
|
|
||||||
|
lateinit var user: User
|
||||||
|
lateinit var roomToken: String
|
||||||
|
private var isOwnerOrModerator: Boolean = false
|
||||||
|
lateinit var pollId: String
|
||||||
|
lateinit var pollTitle: String
|
||||||
|
|
||||||
|
private var editVotes: Boolean = false
|
||||||
|
|
||||||
|
sealed interface ViewState
|
||||||
|
object InitialState : ViewState
|
||||||
|
object DismissDialogState : ViewState
|
||||||
|
object LoadingState : ViewState
|
||||||
|
|
||||||
|
open class PollVoteState(
|
||||||
|
val poll: Poll,
|
||||||
|
val showVotersAmount: Boolean,
|
||||||
|
val showEndPollButton: Boolean,
|
||||||
|
val showDismissEditButton: Boolean
|
||||||
|
) : ViewState
|
||||||
|
|
||||||
|
open class PollResultState(
|
||||||
|
val poll: Poll,
|
||||||
|
val showVotersAmount: Boolean,
|
||||||
|
val showEndPollButton: Boolean,
|
||||||
|
val showEditButton: Boolean
|
||||||
|
) : ViewState
|
||||||
|
|
||||||
|
private val _viewState: MutableLiveData<ViewState> = MutableLiveData(InitialState)
|
||||||
|
val viewState: LiveData<ViewState>
|
||||||
|
get() = _viewState
|
||||||
|
|
||||||
|
private var disposable: Disposable? = null
|
||||||
|
|
||||||
|
fun setData(user: User, roomToken: String, isOwnerOrModerator: Boolean, pollId: String, pollTitle: String) {
|
||||||
|
this.user = user
|
||||||
|
this.roomToken = roomToken
|
||||||
|
this.isOwnerOrModerator = isOwnerOrModerator
|
||||||
|
this.pollId = pollId
|
||||||
|
this.pollTitle = pollTitle
|
||||||
|
|
||||||
|
loadPoll()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun voted() {
|
||||||
|
loadPoll()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun editVotes() {
|
||||||
|
editVotes = true
|
||||||
|
loadPoll()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun dismissEditVotes() {
|
||||||
|
loadPoll()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadPoll() {
|
||||||
|
_viewState.value = LoadingState
|
||||||
|
repository.getPoll(roomToken, pollId)
|
||||||
|
.doOnSubscribe { disposable = it }
|
||||||
|
?.subscribeOn(Schedulers.io())
|
||||||
|
?.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
?.subscribe(PollObserver())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun endPoll() {
|
||||||
|
_viewState.value = LoadingState
|
||||||
|
repository.closePoll(roomToken, pollId)
|
||||||
|
.doOnSubscribe { disposable = it }
|
||||||
|
?.subscribeOn(Schedulers.io())
|
||||||
|
?.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
?.subscribe(PollObserver())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCleared() {
|
||||||
|
super.onCleared()
|
||||||
|
disposable?.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
Log.e(TAG, "An error occurred: $e")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onComplete() {
|
||||||
|
val showEndPollButton = showEndPollButton(poll)
|
||||||
|
val showVotersAmount = showVotersAmount(poll)
|
||||||
|
|
||||||
|
if (votedForOpenHiddenPoll(poll)) {
|
||||||
|
_viewState.value = PollVoteState(poll, showVotersAmount, showEndPollButton, false)
|
||||||
|
} else if (editVotes && poll.status == Poll.STATUS_OPEN) {
|
||||||
|
_viewState.value = PollVoteState(poll, false, showEndPollButton, true)
|
||||||
|
editVotes = false
|
||||||
|
} else if (poll.status == Poll.STATUS_CLOSED || poll.votedSelf?.isNotEmpty() == true) {
|
||||||
|
val showEditButton = poll.status == Poll.STATUS_OPEN && poll.resultMode == Poll.RESULT_MODE_PUBLIC
|
||||||
|
_viewState.value = PollResultState(poll, showVotersAmount, showEndPollButton, showEditButton)
|
||||||
|
} else if (poll.votedSelf.isNullOrEmpty()) {
|
||||||
|
_viewState.value = PollVoteState(poll, showVotersAmount, showEndPollButton, false)
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "unknown poll state")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showEndPollButton(poll: Poll): Boolean {
|
||||||
|
return poll.status == Poll.STATUS_OPEN && (isPollCreatedByCurrentUser(poll) || isOwnerOrModerator)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showVotersAmount(poll: Poll): Boolean {
|
||||||
|
return votedForPublicPoll(poll) ||
|
||||||
|
poll.status == Poll.STATUS_CLOSED ||
|
||||||
|
isOwnerOrModerator ||
|
||||||
|
isPollCreatedByCurrentUser(poll)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun votedForOpenHiddenPoll(poll: Poll): Boolean {
|
||||||
|
return poll.status == Poll.STATUS_OPEN &&
|
||||||
|
poll.resultMode == Poll.RESULT_MODE_HIDDEN &&
|
||||||
|
poll.votedSelf?.isNotEmpty() == true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun votedForPublicPoll(poll: Poll): Boolean {
|
||||||
|
return poll.resultMode == Poll.RESULT_MODE_PUBLIC &&
|
||||||
|
poll.votedSelf?.isNotEmpty() == true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isPollCreatedByCurrentUser(poll: Poll): Boolean {
|
||||||
|
return userUtils.currentUser?.userId == poll.actorId
|
||||||
|
}
|
||||||
|
|
||||||
|
fun dismissDialog() {
|
||||||
|
_viewState.value = DismissDialogState
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val TAG = PollMainViewModel::class.java.simpleName
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,128 @@
|
|||||||
|
/*
|
||||||
|
* Nextcloud Talk application
|
||||||
|
*
|
||||||
|
* @author Álvaro Brey
|
||||||
|
* @author Marcel Hibbe
|
||||||
|
* Copyright (C) 2022 Álvaro Brey
|
||||||
|
* Copyright (C) 2022 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 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.MutableLiveData
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import com.nextcloud.talk.polls.adapters.PollResultHeaderItem
|
||||||
|
import com.nextcloud.talk.polls.adapters.PollResultItem
|
||||||
|
import com.nextcloud.talk.polls.adapters.PollResultVoterItem
|
||||||
|
import com.nextcloud.talk.polls.adapters.PollResultVotersOverviewItem
|
||||||
|
import com.nextcloud.talk.polls.model.Poll
|
||||||
|
import io.reactivex.disposables.Disposable
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class PollResultsViewModel @Inject constructor() : ViewModel() {
|
||||||
|
|
||||||
|
sealed interface ViewState
|
||||||
|
object InitialState : ViewState
|
||||||
|
|
||||||
|
private var _poll: Poll? = null
|
||||||
|
val poll: Poll?
|
||||||
|
get() = _poll
|
||||||
|
|
||||||
|
private var _itemsOverviewList: ArrayList<PollResultItem> = ArrayList()
|
||||||
|
private var _itemsDetailsList: ArrayList<PollResultItem> = ArrayList()
|
||||||
|
|
||||||
|
private var _items: MutableLiveData<ArrayList<PollResultItem>?> = MutableLiveData<ArrayList<PollResultItem>?>()
|
||||||
|
val items: MutableLiveData<ArrayList<PollResultItem>?>
|
||||||
|
get() = _items
|
||||||
|
|
||||||
|
private var disposable: Disposable? = null
|
||||||
|
|
||||||
|
override fun onCleared() {
|
||||||
|
super.onCleared()
|
||||||
|
disposable?.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setPoll(poll: Poll) {
|
||||||
|
_poll = poll
|
||||||
|
initPollResults(_poll!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initPollResults(poll: Poll) {
|
||||||
|
_items.value = ArrayList()
|
||||||
|
|
||||||
|
var oneVoteInPercent = 0
|
||||||
|
if (poll.numVoters != 0) {
|
||||||
|
oneVoteInPercent = HUNDRED / poll.numVoters
|
||||||
|
}
|
||||||
|
|
||||||
|
poll.options?.forEachIndexed { index, option ->
|
||||||
|
val votersAmountForThisOption = getVotersAmountForOption(poll, index)
|
||||||
|
val optionsPercent = oneVoteInPercent * votersAmountForThisOption
|
||||||
|
|
||||||
|
val pollResultHeaderItem = PollResultHeaderItem(
|
||||||
|
option,
|
||||||
|
optionsPercent,
|
||||||
|
isOptionSelfVoted(poll, index)
|
||||||
|
)
|
||||||
|
_itemsOverviewList.add(pollResultHeaderItem)
|
||||||
|
_itemsDetailsList.add(pollResultHeaderItem)
|
||||||
|
|
||||||
|
val voters = poll.details?.filter { it.optionId == index }
|
||||||
|
|
||||||
|
if (!voters.isNullOrEmpty()) {
|
||||||
|
_itemsOverviewList.add(PollResultVotersOverviewItem(voters))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!voters.isNullOrEmpty()) {
|
||||||
|
voters.forEach {
|
||||||
|
_itemsDetailsList.add(PollResultVoterItem(it))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_items.value = _itemsOverviewList
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getVotersAmountForOption(poll: Poll, index: Int): Int {
|
||||||
|
var votersAmountForThisOption: Int? = 0
|
||||||
|
if (poll.details != null) {
|
||||||
|
votersAmountForThisOption = poll.details.filter { it.optionId == index }.size
|
||||||
|
} else if (poll.votes != null) {
|
||||||
|
votersAmountForThisOption = poll.votes.filter { it.key.toInt() == index }[index.toString()]
|
||||||
|
if (votersAmountForThisOption == null) {
|
||||||
|
votersAmountForThisOption = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return votersAmountForThisOption!!
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isOptionSelfVoted(poll: Poll, index: Int): Boolean {
|
||||||
|
return poll.votedSelf?.contains(index) == true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toggleDetails() {
|
||||||
|
if (_items.value?.containsAll(_itemsDetailsList) == true) {
|
||||||
|
_items.value = _itemsOverviewList
|
||||||
|
} else {
|
||||||
|
_items.value = _itemsDetailsList
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val TAG = PollResultsViewModel::class.java.simpleName
|
||||||
|
private const val HUNDRED = 100
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,133 @@
|
|||||||
|
/*
|
||||||
|
* Nextcloud Talk application
|
||||||
|
*
|
||||||
|
* @author Marcel Hibbe
|
||||||
|
* @author Álvaro Brey
|
||||||
|
* Copyright (C) 2022 Álvaro Brey
|
||||||
|
* Copyright (C) 2022 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 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 android.util.Log
|
||||||
|
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 PollVoteViewModel @Inject constructor(private val repository: PollRepository) : ViewModel() {
|
||||||
|
|
||||||
|
sealed interface ViewState
|
||||||
|
object InitialState : ViewState
|
||||||
|
open class PollVoteSuccessState : ViewState
|
||||||
|
open class PollVoteHiddenSuccessState : ViewState
|
||||||
|
open class PollVoteFailedState : ViewState
|
||||||
|
|
||||||
|
private val _viewState: MutableLiveData<ViewState> = MutableLiveData(InitialState)
|
||||||
|
val viewState: LiveData<ViewState>
|
||||||
|
get() = _viewState
|
||||||
|
|
||||||
|
private val _submitButtonEnabled: MutableLiveData<Boolean> = MutableLiveData()
|
||||||
|
val submitButtonEnabled: LiveData<Boolean>
|
||||||
|
get() = _submitButtonEnabled
|
||||||
|
|
||||||
|
private var disposable: Disposable? = null
|
||||||
|
|
||||||
|
private var _votedOptions: List<Int> = emptyList()
|
||||||
|
val votedOptions: List<Int>
|
||||||
|
get() = _votedOptions
|
||||||
|
|
||||||
|
private var _selectedOptions: List<Int> = emptyList()
|
||||||
|
val selectedOptions: List<Int>
|
||||||
|
get() = _selectedOptions
|
||||||
|
|
||||||
|
fun initVotedOptions(selectedOptions: List<Int>) {
|
||||||
|
_votedOptions = selectedOptions
|
||||||
|
_selectedOptions = selectedOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
fun selectOption(option: Int, isRadioBox: Boolean) {
|
||||||
|
_selectedOptions = if (isRadioBox) {
|
||||||
|
listOf(option)
|
||||||
|
} else {
|
||||||
|
_selectedOptions.plus(option)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deSelectOption(option: Int) {
|
||||||
|
_selectedOptions = _selectedOptions.minus(option)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun vote(roomToken: String, pollId: String) {
|
||||||
|
if (_selectedOptions.isNotEmpty()) {
|
||||||
|
_submitButtonEnabled.value = false
|
||||||
|
|
||||||
|
repository.vote(roomToken, pollId, _selectedOptions)
|
||||||
|
.doOnSubscribe { disposable = it }
|
||||||
|
?.subscribeOn(Schedulers.io())
|
||||||
|
?.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
?.subscribe(PollObserver())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCleared() {
|
||||||
|
super.onCleared()
|
||||||
|
disposable?.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateSubmitButton() {
|
||||||
|
val areSelectedOptionsDifferentToVotedOptions = !(
|
||||||
|
votedOptions.containsAll(selectedOptions) &&
|
||||||
|
selectedOptions.containsAll(votedOptions)
|
||||||
|
)
|
||||||
|
|
||||||
|
_submitButtonEnabled.value = areSelectedOptionsDifferentToVotedOptions && selectedOptions.isNotEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
Log.e(TAG, "An error occurred: $e")
|
||||||
|
_viewState.value = PollVoteFailedState()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onComplete() {
|
||||||
|
if (poll.resultMode == 1) {
|
||||||
|
_viewState.value = PollVoteHiddenSuccessState()
|
||||||
|
} else {
|
||||||
|
_viewState.value = PollVoteSuccessState()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val TAG = PollVoteViewModel::class.java.simpleName
|
||||||
|
}
|
||||||
|
}
|
@ -28,7 +28,10 @@ import io.reactivex.Observable
|
|||||||
|
|
||||||
interface SharedItemsRepository {
|
interface SharedItemsRepository {
|
||||||
|
|
||||||
fun media(parameters: Parameters, type: SharedItemType): Observable<SharedMediaItems>?
|
fun media(
|
||||||
|
parameters: Parameters,
|
||||||
|
type: SharedItemType
|
||||||
|
): Observable<SharedMediaItems>?
|
||||||
|
|
||||||
fun media(
|
fun media(
|
||||||
parameters: Parameters,
|
parameters: Parameters,
|
||||||
|
@ -43,6 +43,12 @@ class AttachmentDialog(val activity: Activity, var chatController: ChatControlle
|
|||||||
setContentView(dialogAttachmentBinding.root)
|
setContentView(dialogAttachmentBinding.root)
|
||||||
window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||||
|
|
||||||
|
initItemsStrings()
|
||||||
|
initItemsVisibility()
|
||||||
|
initItemsClickListeners()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initItemsStrings() {
|
||||||
var serverName = CapabilitiesUtilNew.getServerName(chatController.conversationUser)
|
var serverName = CapabilitiesUtilNew.getServerName(chatController.conversationUser)
|
||||||
dialogAttachmentBinding.txtAttachFileFromCloud.text = chatController.resources?.let {
|
dialogAttachmentBinding.txtAttachFileFromCloud.text = chatController.resources?.let {
|
||||||
if (serverName.isNullOrEmpty()) {
|
if (serverName.isNullOrEmpty()) {
|
||||||
@ -50,7 +56,9 @@ class AttachmentDialog(val activity: Activity, var chatController: ChatControlle
|
|||||||
}
|
}
|
||||||
String.format(it.getString(R.string.nc_upload_from_cloud), serverName)
|
String.format(it.getString(R.string.nc_upload_from_cloud), serverName)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initItemsVisibility() {
|
||||||
if (!CapabilitiesUtilNew.hasSpreedFeatureCapability(
|
if (!CapabilitiesUtilNew.hasSpreedFeatureCapability(
|
||||||
chatController.conversationUser,
|
chatController.conversationUser,
|
||||||
"geo-location-sharing"
|
"geo-location-sharing"
|
||||||
@ -59,6 +67,12 @@ class AttachmentDialog(val activity: Activity, var chatController: ChatControlle
|
|||||||
dialogAttachmentBinding.menuShareLocation.visibility = View.GONE
|
dialogAttachmentBinding.menuShareLocation.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!CapabilitiesUtilNew.hasSpreedFeatureCapability(chatController.conversationUser, "talk-polls")) {
|
||||||
|
dialogAttachmentBinding.menuAttachPoll.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initItemsClickListeners() {
|
||||||
dialogAttachmentBinding.menuShareLocation.setOnClickListener {
|
dialogAttachmentBinding.menuShareLocation.setOnClickListener {
|
||||||
chatController.showShareLocationScreen()
|
chatController.showShareLocationScreen()
|
||||||
dismiss()
|
dismiss()
|
||||||
@ -74,6 +88,11 @@ class AttachmentDialog(val activity: Activity, var chatController: ChatControlle
|
|||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dialogAttachmentBinding.menuAttachPoll.setOnClickListener {
|
||||||
|
chatController.createPoll()
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
dialogAttachmentBinding.menuAttachFileFromCloud.setOnClickListener {
|
dialogAttachmentBinding.menuAttachFileFromCloud.setOnClickListener {
|
||||||
chatController.showBrowserScreen()
|
chatController.showBrowserScreen()
|
||||||
dismiss()
|
dismiss()
|
||||||
|
@ -2,8 +2,10 @@
|
|||||||
* Nextcloud Talk application
|
* Nextcloud Talk application
|
||||||
*
|
*
|
||||||
* @author Mario Danic
|
* @author Mario Danic
|
||||||
|
* @author Marcel Hibbe
|
||||||
* @author Tim Krüger
|
* @author Tim Krüger
|
||||||
* Copyright (C) 2021 Tim Krüger <t@timkrueger.me>
|
* Copyright (C) 2021 Tim Krüger <t@timkrueger.me>
|
||||||
|
* Copyright (C) 2021-2022 Marcel Hibbe <dev@mhibbe.de>
|
||||||
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
|
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
@ -61,8 +63,8 @@ public class ApiUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated This is only supported on API v1-3, in API v4+ please use
|
* @deprecated This is only supported on API v1-3, in API v4+ please use {@link ApiUtils#getUrlForAttendees(int,
|
||||||
* {@link ApiUtils#getUrlForAttendees(int, String, String)} instead.
|
* String, String)} instead.
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public static String getUrlForRemovingParticipantFromConversation(String baseUrl, String roomToken, boolean isGuest) {
|
public static String getUrlForRemovingParticipantFromConversation(String baseUrl, String roomToken, boolean isGuest) {
|
||||||
@ -152,7 +154,7 @@ public class ApiUtils {
|
|||||||
return version;
|
return version;
|
||||||
}
|
}
|
||||||
if (version == APIv1 &&
|
if (version == APIv1 &&
|
||||||
user.hasSpreedFeatureCapability("mention-flag") &&
|
user.hasSpreedFeatureCapability( "mention-flag") &&
|
||||||
!user.hasSpreedFeatureCapability("conversation-v4")) {
|
!user.hasSpreedFeatureCapability("conversation-v4")) {
|
||||||
return version;
|
return version;
|
||||||
}
|
}
|
||||||
@ -238,7 +240,7 @@ public class ApiUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static String getUrlForParticipants(int version, String baseUrl, String token) {
|
public static String getUrlForParticipants(int version, String baseUrl, String token) {
|
||||||
if (token == null || token.isEmpty()){
|
if (token == null || token.isEmpty()) {
|
||||||
Log.e(TAG, "token was null or empty");
|
Log.e(TAG, "token was null or empty");
|
||||||
}
|
}
|
||||||
return getUrlForRoom(version, baseUrl, token) + "/participants";
|
return getUrlForRoom(version, baseUrl, token) + "/participants";
|
||||||
@ -287,6 +289,7 @@ public class ApiUtils {
|
|||||||
public static String getUrlForCall(int version, String baseUrl, String token) {
|
public static String getUrlForCall(int version, String baseUrl, String token) {
|
||||||
return getUrlForApi(version, baseUrl) + "/call/" + token;
|
return getUrlForApi(version, baseUrl) + "/call/" + token;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getUrlForChat(int version, String baseUrl, String token) {
|
public static String getUrlForChat(int version, String baseUrl, String token) {
|
||||||
return getUrlForApi(version, baseUrl) + "/chat/" + token;
|
return getUrlForApi(version, baseUrl) + "/chat/" + token;
|
||||||
}
|
}
|
||||||
@ -294,6 +297,7 @@ public class ApiUtils {
|
|||||||
public static String getUrlForMentionSuggestions(int version, String baseUrl, String token) {
|
public static String getUrlForMentionSuggestions(int version, String baseUrl, String token) {
|
||||||
return getUrlForChat(version, baseUrl, token) + "/mentions";
|
return getUrlForChat(version, baseUrl, token) + "/mentions";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getUrlForChatMessage(int version, String baseUrl, String token, String messageId) {
|
public static String getUrlForChatMessage(int version, String baseUrl, String token, String messageId) {
|
||||||
return getUrlForChat(version, baseUrl, token) + "/" + messageId;
|
return getUrlForChat(version, baseUrl, token) + "/" + messageId;
|
||||||
}
|
}
|
||||||
@ -448,8 +452,10 @@ public class ApiUtils {
|
|||||||
return getUrlForChat(version, baseUrl, roomToken) + "/share";
|
return getUrlForChat(version, baseUrl, roomToken) + "/share";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getUrlForHoverCard(String baseUrl, String userId) { return baseUrl + ocsApiVersion +
|
public static String getUrlForHoverCard(String baseUrl, String userId) {
|
||||||
"/hovercard/v1/" + userId; }
|
return baseUrl + ocsApiVersion +
|
||||||
|
"/hovercard/v1/" + userId;
|
||||||
|
}
|
||||||
|
|
||||||
public static String getUrlForSetChatReadMarker(int version, String baseUrl, String roomToken) {
|
public static String getUrlForSetChatReadMarker(int version, String baseUrl, String roomToken) {
|
||||||
return getUrlForChat(version, baseUrl, roomToken) + "/read";
|
return getUrlForChat(version, baseUrl, roomToken) + "/read";
|
||||||
@ -497,4 +503,16 @@ public class ApiUtils {
|
|||||||
public static String getUrlForUnifiedSearch(@NotNull String baseUrl, @NotNull String providerId) {
|
public static String getUrlForUnifiedSearch(@NotNull String baseUrl, @NotNull String providerId) {
|
||||||
return baseUrl + ocsApiVersion + "/search/providers/" + providerId + "/search";
|
return baseUrl + ocsApiVersion + "/search/providers/" + providerId + "/search";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getUrlForPoll(String baseUrl,
|
||||||
|
String roomToken,
|
||||||
|
String pollId) {
|
||||||
|
return getUrlForPoll(baseUrl, roomToken) + "/" + pollId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getUrlForPoll(String baseUrl,
|
||||||
|
String roomToken) {
|
||||||
|
return baseUrl + ocsApiVersion + spreedApiVersion + "/poll/" + roomToken;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
10
app/src/main/res/drawable/ic_baseline_bar_chart_24.xml
Normal file
10
app/src/main/res/drawable/ic_baseline_bar_chart_24.xml
Normal 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="M5,9.2h3L8,19L5,19zM10.6,5h2.8v14h-2.8zM16.2,13L19,13v6h-2.8z" />
|
||||||
|
</vector>
|
10
app/src/main/res/drawable/ic_baseline_close_24.xml
Normal file
10
app/src/main/res/drawable/ic_baseline_close_24.xml
Normal 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>
|
@ -1,29 +0,0 @@
|
|||||||
<!--
|
|
||||||
~ Nextcloud Talk application
|
|
||||||
~
|
|
||||||
~ @author Mario Danic
|
|
||||||
~ Copyright (C) 2017-2019 Mario Danic <mario@lovelyhq.com>
|
|
||||||
~
|
|
||||||
~ 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 <http://www.gnu.org/licenses/>.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="32"
|
|
||||||
android:viewportHeight="32">
|
|
||||||
<path
|
|
||||||
android:fillColor="#FFFFFF"
|
|
||||||
android:pathData="M6.667,4C4.089,4 2,6.105 2,8.7v11.282c0,2.597 2.09,4.701 4.667,4.701 1.716,0.01 12.083,0.003 17.057,0 1.115,0.842 1.807,1.748 3.057,3.206a0.93,0.93 0,0 0,0.561 0.103,0.969 0.969,0 0,0 0.445,-0.187c0.302,-0.223 0.466,-0.603 0.427,-0.988l-0.314,-2.912a4.699,4.699 0,0 0,2.1 -3.923L30,8.701C30,6.105 27.91,4 25.333,4zM10.4,12.461c1.03,0 1.867,0.842 1.867,1.88 0,1.676 -2.01,2.514 -3.187,1.33 -1.176,-1.184 -0.343,-3.21 1.32,-3.21zM16,12.461c1.03,0 1.867,0.842 1.867,1.88 0,1.676 -2.01,2.514 -3.187,1.33 -1.176,-1.184 -0.343,-3.21 1.32,-3.21zM21.6,12.461c1.03,0 1.867,0.842 1.867,1.88 0,1.676 -2.01,2.514 -3.187,1.33 -1.176,-1.184 -0.343,-3.21 1.32,-3.21z"/>
|
|
||||||
</vector>
|
|
@ -39,6 +39,39 @@
|
|||||||
android:textColor="@color/medium_emphasis_text"
|
android:textColor="@color/medium_emphasis_text"
|
||||||
android:textSize="@dimen/bottom_sheet_text_size" />
|
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
|
<LinearLayout
|
||||||
android:id="@+id/menu_attach_contact"
|
android:id="@+id/menu_attach_contact"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
124
app/src/main/res/layout/dialog_poll_create.xml
Normal file
124
app/src/main/res/layout/dialog_poll_create.xml
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
~ Nextcloud Talk application
|
||||||
|
~
|
||||||
|
~ @author Marcel Hibbe
|
||||||
|
~ Copyright (C) 2022 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 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 <http://www.gnu.org/licenses/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<ScrollView 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">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@color/colorPrimary"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:text="@string/polls_question" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/poll_create_question"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:inputType="textMultiLine"
|
||||||
|
tools:ignore="Autofill,LabelFor"/>
|
||||||
|
|
||||||
|
<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="@string/polls_options" />
|
||||||
|
|
||||||
|
<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" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/poll_add_options_item"
|
||||||
|
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="@string/polls_settings" />
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/poll_private_poll_checkbox"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/polls_private_poll" />
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/poll_multiple_answers_checkbox"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/polls_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>
|
||||||
|
</ScrollView>
|
33
app/src/main/res/layout/dialog_poll_loading.xml
Normal file
33
app/src/main/res/layout/dialog_poll_loading.xml
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
~ Nextcloud Talk application
|
||||||
|
~
|
||||||
|
~ @author Marcel Hibbe
|
||||||
|
~ Copyright (C) 2022 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 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 <http://www.gnu.org/licenses/>.
|
||||||
|
-->
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:gravity="center"
|
||||||
|
tools:background="@color/white">
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:layout_width="25dp"
|
||||||
|
android:layout_height="25dp">
|
||||||
|
</ProgressBar>
|
||||||
|
|
||||||
|
</LinearLayout>
|
95
app/src/main/res/layout/dialog_poll_main.xml
Normal file
95
app/src/main/res/layout/dialog_poll_main.xml
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
~ Nextcloud Talk application
|
||||||
|
~
|
||||||
|
~ @author Marcel Hibbe
|
||||||
|
~ Copyright (C) 2022 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 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 <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:padding="@dimen/standard_padding"
|
||||||
|
android:orientation="vertical"
|
||||||
|
tools:background="@color/white">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
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/high_emphasis_menu_icon" />
|
||||||
|
|
||||||
|
<androidx.emoji.widget.EmojiTextView
|
||||||
|
android:id="@+id/message_poll_title"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
tools:text="This is the poll title?" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/poll_results_subtitle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:textColor="@color/low_emphasis_text"
|
||||||
|
android:text="@string/polls_results_subtitle"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/poll_results_subtitle_seperator"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:textColor="@color/low_emphasis_text"
|
||||||
|
android:text=" - "
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible"
|
||||||
|
tools:ignore="HardcodedText" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/poll_votes_amount"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:textColor="@color/low_emphasis_text"
|
||||||
|
tools:text="93 votes" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/message_poll_content_fragment"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
65
app/src/main/res/layout/dialog_poll_results.xml
Normal file
65
app/src/main/res/layout/dialog_poll_results.xml
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
~ Nextcloud Talk application
|
||||||
|
~
|
||||||
|
~ @author Marcel Hibbe
|
||||||
|
~ Copyright (C) 2022 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 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 <http://www.gnu.org/licenses/>.
|
||||||
|
-->
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
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"
|
||||||
|
tools:background="@color/white"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/poll_results_list_wrapper"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1">
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/poll_results_list"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
tools:listitem="@layout/poll_result_header_item" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/standard_margin"
|
||||||
|
android:gravity="end">
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/poll_results_end_poll_button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/polls_end_poll"
|
||||||
|
style="@style/OutlinedButton"
|
||||||
|
android:layout_marginEnd="@dimen/standard_margin"
|
||||||
|
app:cornerRadius="@dimen/button_corner_radius" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/edit_vote_button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/polls_edit_vote"
|
||||||
|
android:theme="@style/Button.Primary"
|
||||||
|
app:cornerRadius="@dimen/button_corner_radius" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
89
app/src/main/res/layout/dialog_poll_vote.xml
Normal file
89
app/src/main/res/layout/dialog_poll_vote.xml
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
~ Nextcloud Talk application
|
||||||
|
~
|
||||||
|
~ @author Marcel Hibbe
|
||||||
|
~ Copyright (C) 2022 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 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 <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"
|
||||||
|
tools:background="@color/white">
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
android:id="@+id/vote_options_wrapper"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/vote_options_checkboxes_wrapper"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical" />
|
||||||
|
|
||||||
|
<RadioGroup
|
||||||
|
android:id="@+id/poll_vote_radio_group"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="-4dp"
|
||||||
|
tools:layout_height="400dp" />
|
||||||
|
</LinearLayout>
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/standard_margin"
|
||||||
|
android:gravity="end">
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/poll_vote_end_poll_button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/polls_end_poll"
|
||||||
|
style="@style/OutlinedButton"
|
||||||
|
android:layout_marginEnd="@dimen/standard_margin"
|
||||||
|
app:cornerRadius="@dimen/button_corner_radius" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/poll_vote_edit_dismiss"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/nc_common_dismiss"
|
||||||
|
style="@style/OutlinedButton"
|
||||||
|
android:layout_marginEnd="@dimen/standard_margin"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:cornerRadius="@dimen/button_corner_radius"
|
||||||
|
tools:visibility="visible"/>
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/poll_vote_submit_button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/polls_submit_vote"
|
||||||
|
android:theme="@style/Button.Primary"
|
||||||
|
app:cornerRadius="@dimen/button_corner_radius" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
110
app/src/main/res/layout/item_custom_incoming_poll_message.xml
Normal file
110
app/src/main/res/layout/item_custom_incoming_poll_message.xml
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
~ Nextcloud Talk 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 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 <http://www.gnu.org/licenses/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<RelativeLayout 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:layout_marginLeft="16dp"
|
||||||
|
android:layout_marginTop="2dp"
|
||||||
|
android:layout_marginRight="16dp"
|
||||||
|
android:layout_marginBottom="2dp">
|
||||||
|
|
||||||
|
<com.facebook.drawee.view.SimpleDraweeView
|
||||||
|
android:id="@id/messageUserAvatar"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
app:roundAsCircle="true" />
|
||||||
|
|
||||||
|
<com.google.android.flexbox.FlexboxLayout
|
||||||
|
android:id="@id/bubble"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginEnd="@dimen/message_incoming_bubble_margin_right"
|
||||||
|
android:layout_toEndOf="@id/messageUserAvatar"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:alignContent="stretch"
|
||||||
|
app:alignItems="stretch"
|
||||||
|
app:flexWrap="wrap"
|
||||||
|
app:justifyContent="flex_end">
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/message_quote"
|
||||||
|
layout="@layout/item_message_quote"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<androidx.emoji.widget.EmojiTextView
|
||||||
|
android:id="@+id/messageAuthor"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="4dp"
|
||||||
|
android:textAlignment="viewStart"
|
||||||
|
android:textColor="@color/textColorMaxContrast"
|
||||||
|
android:textSize="12sp" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
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/high_emphasis_menu_icon" />
|
||||||
|
|
||||||
|
<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"
|
||||||
|
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_open" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@id/messageTime"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/messageText"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
app:layout_alignSelf="center"
|
||||||
|
tools:text="12:38" />
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/reactions"
|
||||||
|
layout="@layout/reactions_inside_message" />
|
||||||
|
|
||||||
|
</com.google.android.flexbox.FlexboxLayout>
|
||||||
|
</RelativeLayout>
|
105
app/src/main/res/layout/item_custom_outcoming_poll_message.xml
Normal file
105
app/src/main/res/layout/item_custom_outcoming_poll_message.xml
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
~ Nextcloud Talk 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 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 <http://www.gnu.org/licenses/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<RelativeLayout 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:layout_marginLeft="16dp"
|
||||||
|
android:layout_marginTop="2dp"
|
||||||
|
android:layout_marginRight="16dp"
|
||||||
|
android:layout_marginBottom="2dp">
|
||||||
|
|
||||||
|
<com.google.android.flexbox.FlexboxLayout
|
||||||
|
android:id="@id/bubble"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_marginStart="@dimen/message_outcoming_bubble_margin_left"
|
||||||
|
app:alignContent="stretch"
|
||||||
|
app:alignItems="stretch"
|
||||||
|
app:flexWrap="wrap"
|
||||||
|
app:justifyContent="flex_end">
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/message_quote"
|
||||||
|
layout="@layout/item_message_quote"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
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_open"
|
||||||
|
android:textColor="@color/nc_outcoming_text_default" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@id/messageTime"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
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
|
||||||
|
android:id="@+id/checkMark"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
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" />
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/reactions"
|
||||||
|
layout="@layout/reactions_inside_message" />
|
||||||
|
|
||||||
|
</com.google.android.flexbox.FlexboxLayout>
|
||||||
|
</RelativeLayout>
|
49
app/src/main/res/layout/poll_create_options_item.xml
Normal file
49
app/src/main/res/layout/poll_create_options_item.xml
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
~ Nextcloud Talk application
|
||||||
|
~
|
||||||
|
~ @author Marcel Hibbe
|
||||||
|
~ Copyright (C) 2022 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 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 <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="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
tools:background="@color/white">
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/poll_option_text"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:inputType="text"
|
||||||
|
tools:ignore="Autofill,LabelFor" />
|
||||||
|
|
||||||
|
<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>
|
60
app/src/main/res/layout/poll_result_header_item.xml
Normal file
60
app/src/main/res/layout/poll_result_header_item.xml
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
~ Nextcloud Talk application
|
||||||
|
~
|
||||||
|
~ @author Marcel Hibbe
|
||||||
|
~ Copyright (C) 2022 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 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 <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">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/poll_option_text"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:text="Option Number One" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/poll_option_percent_text"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/poll_option_text"
|
||||||
|
tools:text="50%" />
|
||||||
|
|
||||||
|
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||||
|
android:id="@+id/poll_option_bar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:indeterminate="false"
|
||||||
|
app:indicatorColor="@color/poll_bar_color"
|
||||||
|
app:layout_constraintStart_toStartOf="@+id/poll_option_text"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/poll_option_text"
|
||||||
|
app:trackColor="@color/dialog_background"
|
||||||
|
app:trackCornerRadius="5dp"
|
||||||
|
app:trackThickness="5dp"
|
||||||
|
android:paddingBottom="@dimen/standard_half_padding"
|
||||||
|
tools:progress="50" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
44
app/src/main/res/layout/poll_result_voter_item.xml
Normal file
44
app/src/main/res/layout/poll_result_voter_item.xml
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
~ Nextcloud Talk application
|
||||||
|
~
|
||||||
|
~ @author Marcel Hibbe
|
||||||
|
~ Copyright (C) 2022 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 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 <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="wrap_content"
|
||||||
|
android:paddingBottom="4dp"
|
||||||
|
tools:background="@color/white">
|
||||||
|
|
||||||
|
<com.facebook.drawee.view.SimpleDraweeView
|
||||||
|
android:id="@+id/poll_voter_avatar"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
app:roundAsCircle="true" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/poll_voter_name"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
tools:text="Bill Murray" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
29
app/src/main/res/layout/poll_result_voters_overview_item.xml
Normal file
29
app/src/main/res/layout/poll_result_voters_overview_item.xml
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
~ Nextcloud Talk application
|
||||||
|
~
|
||||||
|
~ @author Marcel Hibbe
|
||||||
|
~ Copyright (C) 2022 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 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 <http://www.gnu.org/licenses/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/voters_avatars_overview_wrapper"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingBottom="4dp"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
tools:background="@color/white">
|
||||||
|
</RelativeLayout>
|
@ -106,6 +106,9 @@
|
|||||||
<color name="list_divider_background">#eeeeee</color>
|
<color name="list_divider_background">#eeeeee</color>
|
||||||
<color name="grey_200">#EEEEEE</color>
|
<color name="grey_200">#EEEEEE</color>
|
||||||
|
|
||||||
|
<!-- poll -->
|
||||||
|
<color name="poll_bar_color">#8dd4f6</color>
|
||||||
|
|
||||||
<!-- this is just a helper for status icon background because getting the background color of a dialog is not
|
<!-- this is just a helper for status icon background because getting the background color of a dialog is not
|
||||||
possible?! don't use this to set the background of dialogs -->
|
possible?! don't use this to set the background of dialogs -->
|
||||||
<color name="dialog_background">#FFFFFF</color>
|
<color name="dialog_background">#FFFFFF</color>
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
<string name="nc_no">No</string>
|
<string name="nc_no">No</string>
|
||||||
<string name="nc_common_skip">Skip</string>
|
<string name="nc_common_skip">Skip</string>
|
||||||
<string name="nc_common_set">Set</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_error_sorry">Sorry, something went wrong!</string>
|
||||||
|
|
||||||
<!-- Bottom Navigation -->
|
<!-- Bottom Navigation -->
|
||||||
@ -309,6 +310,7 @@
|
|||||||
<string name="nc_sent_an_audio" formatted="true">%1$s sent an audio.</string>
|
<string name="nc_sent_an_audio" formatted="true">%1$s sent an audio.</string>
|
||||||
<string name="nc_sent_a_video" formatted="true">%1$s sent a video.</string>
|
<string name="nc_sent_a_video" formatted="true">%1$s sent a video.</string>
|
||||||
<string name="nc_sent_an_image" formatted="true">%1$s sent an image.</string>
|
<string name="nc_sent_an_image" formatted="true">%1$s sent an image.</string>
|
||||||
|
<string name="nc_sent_poll" formatted="true">%1$s sent a poll.</string>
|
||||||
<string name="nc_sent_location" formatted="true">%1$s sent a location.</string>
|
<string name="nc_sent_location" formatted="true">%1$s sent a location.</string>
|
||||||
<string name="nc_sent_voice" formatted="true">%1$s sent a voice message.</string>
|
<string name="nc_sent_voice" formatted="true">%1$s sent a voice message.</string>
|
||||||
<string name="nc_sent_a_link_you">You sent a link.</string>
|
<string name="nc_sent_a_link_you">You sent a link.</string>
|
||||||
@ -317,6 +319,7 @@
|
|||||||
<string name="nc_sent_an_audio_you">You sent an audio.</string>
|
<string name="nc_sent_an_audio_you">You sent an audio.</string>
|
||||||
<string name="nc_sent_a_video_you">You sent a video.</string>
|
<string name="nc_sent_a_video_you">You sent a video.</string>
|
||||||
<string name="nc_sent_an_image_you">You sent an image.</string>
|
<string name="nc_sent_an_image_you">You sent an image.</string>
|
||||||
|
<string name="nc_sent_poll_you">You sent a poll.</string>
|
||||||
<string name="nc_sent_location_you">You sent a location.</string>
|
<string name="nc_sent_location_you">You sent a location.</string>
|
||||||
<string name="nc_sent_voice_you">You sent a voice message.</string>
|
<string name="nc_sent_voice_you">You sent a voice message.</string>
|
||||||
<string name="nc_formatted_message" translatable="false">%1$s: %2$s</string>
|
<string name="nc_formatted_message" translatable="false">%1$s: %2$s</string>
|
||||||
@ -401,6 +404,7 @@
|
|||||||
<!-- Upload -->
|
<!-- Upload -->
|
||||||
<string name="nc_add_file">Add to conversation</string>
|
<string name="nc_add_file">Add to conversation</string>
|
||||||
<string name="nc_upload_picture_from_cam">Take photo</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_from_cloud">Share from %1$s</string>
|
||||||
<string name="nc_upload_failed">Sorry, upload failed</string>
|
<string name="nc_upload_failed">Sorry, upload failed</string>
|
||||||
<string name="nc_upload_choose_local_files">Choose files</string>
|
<string name="nc_upload_choose_local_files">Choose files</string>
|
||||||
@ -527,6 +531,23 @@
|
|||||||
<string name="message_search_begin_typing">Start typing to search …</string>
|
<string name="message_search_begin_typing">Start typing to search …</string>
|
||||||
<string name="message_search_begin_empty">No search results</string>
|
<string name="message_search_begin_empty">No search results</string>
|
||||||
|
|
||||||
|
<!-- Polls -->
|
||||||
|
<string name="message_poll_tap_to_open">Tap to open poll</string>
|
||||||
|
<string name="polls_amount_voters">%1$s votes</string>
|
||||||
|
<string name="polls_add_option">Add option</string>
|
||||||
|
<string name="polls_edit_vote">Edit vote</string>
|
||||||
|
<string name="polls_submit_vote">Vote</string>
|
||||||
|
<string name="polls_voted_hidden_success">Successfully voted</string>
|
||||||
|
<string name="polls_end_poll">End poll</string>
|
||||||
|
<string name="polls_end_poll_confirm">Do you really want to end this poll? This can\'t be undone.</string>
|
||||||
|
<string name="polls_max_votes_reached">You can\'t vote with more options for this poll.</string>
|
||||||
|
<string name="polls_results_subtitle">Results</string>
|
||||||
|
<string name="polls_question">Question</string>
|
||||||
|
<string name="polls_options">Options</string>
|
||||||
|
<string name="polls_settings">Settings</string>
|
||||||
|
<string name="polls_private_poll">Private poll</string>
|
||||||
|
<string name="polls_multiple_answers">Multiple answers</string>
|
||||||
|
|
||||||
<string name="title_attachments">Attachments</string>
|
<string name="title_attachments">Attachments</string>
|
||||||
|
|
||||||
<string name="reactions_tab_all">All</string>
|
<string name="reactions_tab_all">All</string>
|
||||||
@ -534,4 +555,5 @@
|
|||||||
<string name="call_without_notification">Call without notification</string>
|
<string name="call_without_notification">Call without notification</string>
|
||||||
<string name="set_avatar_from_camera">Set avatar from camera</string>
|
<string name="set_avatar_from_camera">Set avatar from camera</string>
|
||||||
|
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -147,6 +147,7 @@
|
|||||||
<item name="android:textColor">@color/white</item>
|
<item name="android:textColor">@color/white</item>
|
||||||
<item name="android:typeface">sans</item>
|
<item name="android:typeface">sans</item>
|
||||||
<item name="android:textStyle">bold</item>
|
<item name="android:textStyle">bold</item>
|
||||||
|
<item name="android:layout_gravity">center_vertical</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Widget.AppTheme.Button.IconButton" parent="Widget.MaterialComponents.Button.TextButton">
|
<style name="Widget.AppTheme.Button.IconButton" parent="Widget.MaterialComponents.Button.TextButton">
|
||||||
@ -268,6 +269,7 @@
|
|||||||
<item name="android:textAllCaps">false</item>
|
<item name="android:textAllCaps">false</item>
|
||||||
<item name="android:typeface">sans</item>
|
<item name="android:typeface">sans</item>
|
||||||
<item name="android:textStyle">bold</item>
|
<item name="android:textStyle">bold</item>
|
||||||
|
<item name="android:layout_gravity">center_vertical</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="TextAppearanceTab" parent="TextAppearance.Design.Tab">
|
<style name="TextAppearanceTab" parent="TextAppearance.Design.Tab">
|
||||||
|
Loading…
Reference in New Issue
Block a user