Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/talk-android.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/app/src
diff options
context:
space:
mode:
Diffstat (limited to 'app/src')
-rw-r--r--app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLocationMessageViewHolder.kt3
-rw-r--r--app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingPollMessageViewHolder.kt248
-rw-r--r--app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingVoiceMessageViewHolder.kt3
-rw-r--r--app/src/main/java/com/nextcloud/talk/adapters/messages/MagicIncomingTextMessageViewHolder.kt4
-rw-r--r--app/src/main/java/com/nextcloud/talk/adapters/messages/MagicPreviewMessageViewHolder.java6
-rw-r--r--app/src/main/java/com/nextcloud/talk/adapters/messages/MessagePayload.kt29
-rw-r--r--app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingPollMessageViewHolder.kt214
-rw-r--r--app/src/main/java/com/nextcloud/talk/api/NcApi.java26
-rw-r--r--app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt46
-rw-r--r--app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt8
-rw-r--r--app/src/main/java/com/nextcloud/talk/dagger/modules/ViewModelModule.kt24
-rw-r--r--app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.kt36
-rw-r--r--app/src/main/java/com/nextcloud/talk/models/json/converters/EnumSystemMessageTypeConverter.kt6
-rw-r--r--app/src/main/java/com/nextcloud/talk/polls/adapters/PollCreateOptionItem.kt25
-rw-r--r--app/src/main/java/com/nextcloud/talk/polls/adapters/PollCreateOptionViewHolder.kt82
-rw-r--r--app/src/main/java/com/nextcloud/talk/polls/adapters/PollCreateOptionsAdapter.kt59
-rw-r--r--app/src/main/java/com/nextcloud/talk/polls/adapters/PollCreateOptionsItemListener.kt32
-rw-r--r--app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultHeaderItem.kt39
-rw-r--r--app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultHeaderViewHolder.kt47
-rw-r--r--app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultItem.kt25
-rw-r--r--app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultItemClickListener.kt25
-rw-r--r--app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultViewHolder.kt30
-rw-r--r--app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultVoterItem.kt38
-rw-r--r--app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultVoterViewHolder.kt87
-rw-r--r--app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultVotersOverviewItem.kt38
-rw-r--r--app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultVotersOverviewViewHolder.kt141
-rw-r--r--app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultsAdapter.kt90
-rw-r--r--app/src/main/java/com/nextcloud/talk/polls/model/Poll.kt44
-rw-r--r--app/src/main/java/com/nextcloud/talk/polls/model/PollDetails.kt28
-rw-r--r--app/src/main/java/com/nextcloud/talk/polls/repositories/PollRepository.kt43
-rw-r--r--app/src/main/java/com/nextcloud/talk/polls/repositories/PollRepositoryImpl.kt138
-rw-r--r--app/src/main/java/com/nextcloud/talk/polls/repositories/model/PollDetailsResponse.kt44
-rw-r--r--app/src/main/java/com/nextcloud/talk/polls/repositories/model/PollOCS.kt35
-rw-r--r--app/src/main/java/com/nextcloud/talk/polls/repositories/model/PollOverall.kt35
-rw-r--r--app/src/main/java/com/nextcloud/talk/polls/repositories/model/PollResponse.kt71
-rw-r--r--app/src/main/java/com/nextcloud/talk/polls/ui/PollCreateDialogFragment.kt187
-rw-r--r--app/src/main/java/com/nextcloud/talk/polls/ui/PollLoadingFragment.kt74
-rw-r--r--app/src/main/java/com/nextcloud/talk/polls/ui/PollMainDialogFragment.kt188
-rw-r--r--app/src/main/java/com/nextcloud/talk/polls/ui/PollResultsFragment.kt139
-rw-r--r--app/src/main/java/com/nextcloud/talk/polls/ui/PollVoteFragment.kt219
-rw-r--r--app/src/main/java/com/nextcloud/talk/polls/viewmodels/PollCreateViewModel.kt204
-rw-r--r--app/src/main/java/com/nextcloud/talk/polls/viewmodels/PollMainViewModel.kt188
-rw-r--r--app/src/main/java/com/nextcloud/talk/polls/viewmodels/PollResultsViewModel.kt128
-rw-r--r--app/src/main/java/com/nextcloud/talk/polls/viewmodels/PollVoteViewModel.kt133
-rw-r--r--app/src/main/java/com/nextcloud/talk/shareditems/repositories/SharedItemsRepository.kt5
-rw-r--r--app/src/main/java/com/nextcloud/talk/ui/dialog/AttachmentDialog.kt19
-rw-r--r--app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java52
-rw-r--r--app/src/main/res/drawable/ic_baseline_bar_chart_24.xml10
-rw-r--r--app/src/main/res/drawable/ic_baseline_close_24.xml10
-rw-r--r--app/src/main/res/drawable/ic_comment_white.xml29
-rw-r--r--app/src/main/res/layout/dialog_attachment.xml33
-rw-r--r--app/src/main/res/layout/dialog_poll_create.xml124
-rw-r--r--app/src/main/res/layout/dialog_poll_loading.xml33
-rw-r--r--app/src/main/res/layout/dialog_poll_main.xml95
-rw-r--r--app/src/main/res/layout/dialog_poll_results.xml65
-rw-r--r--app/src/main/res/layout/dialog_poll_vote.xml89
-rw-r--r--app/src/main/res/layout/item_custom_incoming_poll_message.xml110
-rw-r--r--app/src/main/res/layout/item_custom_outcoming_poll_message.xml105
-rw-r--r--app/src/main/res/layout/poll_create_options_item.xml49
-rw-r--r--app/src/main/res/layout/poll_result_header_item.xml60
-rw-r--r--app/src/main/res/layout/poll_result_voter_item.xml44
-rw-r--r--app/src/main/res/layout/poll_result_voters_overview_item.xml29
-rw-r--r--app/src/main/res/values/colors.xml3
-rw-r--r--app/src/main/res/values/strings.xml22
-rw-r--r--app/src/main/res/values/styles.xml2
65 files changed, 4233 insertions, 64 deletions
diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLocationMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLocationMessageViewHolder.kt
index 43d163878..2e9e723d4 100644
--- a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLocationMessageViewHolder.kt
+++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLocationMessageViewHolder.kt
@@ -50,7 +50,6 @@ import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.databinding.ItemCustomIncomingLocationMessageBinding
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.DisplayUtils
import com.nextcloud.talk.utils.UriUtils
@@ -117,7 +116,7 @@ class IncomingLocationMessageViewHolder(incomingView: View, payload: Any) : Mess
if (!TextUtils.isEmpty(author)) {
binding.messageAuthor.text = author
binding.messageUserAvatar.setOnClickListener {
- (payload as? ProfileBottomSheet)?.showFor(message.actorId!!, itemView.context)
+ (payload as? MessagePayload)?.profileBottomSheet?.showFor(message.actorId!!, itemView.context)
}
} else {
binding.messageAuthor.setText(R.string.nc_nick_guest)
diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingPollMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingPollMessageViewHolder.kt
new file mode 100644
index 000000000..177882d44
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingPollMessageViewHolder.kt
@@ -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
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingVoiceMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingVoiceMessageViewHolder.kt
index a7e904da4..e06604763 100644
--- a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingVoiceMessageViewHolder.kt
+++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingVoiceMessageViewHolder.kt
@@ -48,7 +48,6 @@ import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.databinding.ItemCustomIncomingVoiceMessageBinding
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.DisplayUtils
import com.nextcloud.talk.utils.preferences.AppPreferences
@@ -210,7 +209,7 @@ class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) : Message
if (!TextUtils.isEmpty(author)) {
binding.messageAuthor.text = author
binding.messageUserAvatar.setOnClickListener {
- (payload as? ProfileBottomSheet)?.showFor(message.actorId!!, itemView.context)
+ (payload as? MessagePayload)?.profileBottomSheet?.showFor(message.actorId!!, itemView.context)
}
} else {
binding.messageAuthor.setText(R.string.nc_nick_guest)
diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicIncomingTextMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicIncomingTextMessageViewHolder.kt
index d098ba697..95152a920 100644
--- a/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicIncomingTextMessageViewHolder.kt
+++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicIncomingTextMessageViewHolder.kt
@@ -47,14 +47,12 @@ import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.databinding.ItemCustomIncomingTextMessageBinding
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.utils.ApiUtils
import com.nextcloud.talk.utils.DisplayUtils
import com.nextcloud.talk.utils.TextMatchers
import com.nextcloud.talk.utils.preferences.AppPreferences
import com.stfalcon.chatkit.messages.MessageHolders
-import java.util.HashMap
import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
@@ -136,7 +134,7 @@ class MagicIncomingTextMessageViewHolder(itemView: View, payload: Any) : Message
if (!TextUtils.isEmpty(message.actorDisplayName)) {
binding.messageAuthor.text = message.actorDisplayName
binding.messageUserAvatar.setOnClickListener {
- (payload as? ProfileBottomSheet)?.showFor(message.actorId!!, itemView.context)
+ (payload as? MessagePayload)?.profileBottomSheet?.showFor(message.actorId!!, itemView.context)
}
} else {
binding.messageAuthor.setText(R.string.nc_nick_guest)
diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicPreviewMessageViewHolder.java b/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicPreviewMessageViewHolder.java
index 0732e3341..48e33915e 100644
--- a/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicPreviewMessageViewHolder.java
+++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicPreviewMessageViewHolder.java
@@ -49,7 +49,6 @@ import com.nextcloud.talk.components.filebrowser.webdav.ReadFilesystemOperation;
import com.nextcloud.talk.data.user.model.User;
import com.nextcloud.talk.databinding.ReactionsInsideMessageBinding;
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.DrawableUtils;
import com.nextcloud.talk.utils.FileViewerUtils;
@@ -125,8 +124,9 @@ public abstract class MagicPreviewMessageViewHolder extends MessageHolders.Incom
} else {
userAvatar.setVisibility(View.VISIBLE);
userAvatar.setOnClickListener(v -> {
- if (payload instanceof ProfileBottomSheet) {
- ((ProfileBottomSheet) payload).showFor(message.getActorId(), v.getContext());
+ if (payload instanceof MessagePayload) {
+ ((MessagePayload) payload).getProfileBottomSheet().showFor(message.getActorId(),
+ v.getContext());
}
});
diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/MessagePayload.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/MessagePayload.kt
new file mode 100644
index 000000000..df6d22a8f
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/MessagePayload.kt
@@ -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
+)
diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingPollMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingPollMessageViewHolder.kt
new file mode 100644
index 000000000..8f865cccc
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingPollMessageViewHolder.kt
@@ -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
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/api/NcApi.java b/app/src/main/java/com/nextcloud/talk/api/NcApi.java
index 6f2b65e3d..5a531f5c7 100644
--- a/app/src/main/java/com/nextcloud/talk/api/NcApi.java
+++ b/app/src/main/java/com/nextcloud/talk/api/NcApi.java
@@ -1,5 +1,5 @@
/*
- * Nextcloud Talk application
+ * Nextcloud Talk application
*
* @author Mario Danic
* @author Marcel Hibbe
@@ -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.userprofile.UserProfileFieldsOverall;
import com.nextcloud.talk.models.json.userprofile.UserProfileOverall;
+import com.nextcloud.talk.polls.repositories.model.PollOverall;
import java.util.List;
import java.util.Map;
@@ -526,4 +527,27 @@ public interface NcApi {
@Query("from") String fromUrl,
@Query("limit") Integer limit,
@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);
}
diff --git a/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt b/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt
index 15c506670..48a7fc96e 100644
--- a/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt
+++ b/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt
@@ -104,13 +104,16 @@ import com.nextcloud.talk.activities.CallActivity
import com.nextcloud.talk.activities.MainActivity
import com.nextcloud.talk.activities.TakePhotoActivity
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.IncomingVoiceMessageViewHolder
import com.nextcloud.talk.adapters.messages.MagicIncomingTextMessageViewHolder
import com.nextcloud.talk.adapters.messages.MagicOutcomingTextMessageViewHolder
import com.nextcloud.talk.adapters.messages.MagicSystemMessageViewHolder
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.OutcomingPollMessageViewHolder
import com.nextcloud.talk.adapters.messages.OutcomingPreviewMessageViewHolder
import com.nextcloud.talk.adapters.messages.OutcomingVoiceMessageViewHolder
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.generic.GenericOverall
import com.nextcloud.talk.models.json.mention.Mention
+import com.nextcloud.talk.polls.ui.PollCreateDialogFragment
import com.nextcloud.talk.presenters.MentionAutocompletePresenter
import com.nextcloud.talk.remotefilebrowser.activities.RemoteFileBrowserActivity
import com.nextcloud.talk.shareditems.activities.SharedItemsActivity
@@ -483,10 +487,12 @@ class ChatController(args: Bundle) :
val messageHolders = MessageHolders()
val profileBottomSheet = ProfileBottomSheet(ncApi!!, conversationUser!!, router)
+ val payload = MessagePayload(currentConversation!!, profileBottomSheet)
+
messageHolders.setIncomingTextConfig(
MagicIncomingTextMessageViewHolder::class.java,
R.layout.item_custom_incoming_text_message,
- profileBottomSheet
+ payload
)
messageHolders.setOutcomingTextConfig(
MagicOutcomingTextMessageViewHolder::class.java,
@@ -496,7 +502,7 @@ class ChatController(args: Bundle) :
messageHolders.setIncomingImageConfig(
IncomingPreviewMessageViewHolder::class.java,
R.layout.item_custom_incoming_preview_message,
- profileBottomSheet
+ payload
)
messageHolders.setOutcomingImageConfig(
@@ -525,7 +531,7 @@ class ChatController(args: Bundle) :
messageHolders.registerContentType(
CONTENT_TYPE_LOCATION,
IncomingLocationMessageViewHolder::class.java,
- profileBottomSheet,
+ payload,
R.layout.item_custom_incoming_location_message,
OutcomingLocationMessageViewHolder::class.java,
null,
@@ -536,7 +542,7 @@ class ChatController(args: Bundle) :
messageHolders.registerContentType(
CONTENT_TYPE_VOICE_MESSAGE,
IncomingVoiceMessageViewHolder::class.java,
- profileBottomSheet,
+ payload,
R.layout.item_custom_incoming_voice_message,
OutcomingVoiceMessageViewHolder::class.java,
null,
@@ -544,6 +550,17 @@ class ChatController(args: Bundle) :
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 = ""
if (!conversationUser?.userId.equals("?")) {
senderId = "users/" + conversationUser?.userId
@@ -2576,6 +2593,11 @@ class ChatController(args: Bundle) :
chatMessageIterator.remove()
}
+
+ // delete poll system messages
+ else if (isPollVotedMessage(currentMessage)) {
+ chatMessageIterator.remove()
+ }
}
return chatMessageMap.values.toList()
}
@@ -2591,6 +2613,10 @@ class ChatController(args: Bundle) :
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) {
if (currentConversation?.canStartCall == false && currentConversation?.hasCall == false) {
Toast.makeText(context, R.string.startCallForbidden, Toast.LENGTH_LONG).show()
@@ -3012,6 +3038,7 @@ class ChatController(args: Bundle) :
return when (type) {
CONTENT_TYPE_LOCATION -> message.hasGeoLocation()
CONTENT_TYPE_VOICE_MESSAGE -> message.isVoiceMessage
+ CONTENT_TYPE_POLL -> message.isPoll()
CONTENT_TYPE_SYSTEM_MESSAGE -> !TextUtils.isEmpty(message.systemMessage)
CONTENT_TYPE_UNREAD_NOTICE_MESSAGE -> message.id == "-1"
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 {
private const val TAG = "ChatController"
private const val CONTENT_TYPE_SYSTEM_MESSAGE: Byte = 1
private const val CONTENT_TYPE_UNREAD_NOTICE_MESSAGE: Byte = 2
private const val CONTENT_TYPE_LOCATION: Byte = 3
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 POP_CURRENT_CONTROLLER_DELAY: Long = 100
private const val LOBBY_TIMER_DELAY: Long = 5000
diff --git a/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt b/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt
index bb8a5f255..47e3747b3 100644
--- a/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt
+++ b/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt
@@ -29,6 +29,8 @@ import com.nextcloud.talk.data.storage.ArbitraryStoragesRepository
import com.nextcloud.talk.data.storage.ArbitraryStoragesRepositoryImpl
import com.nextcloud.talk.data.user.UsersRepository
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.RemoteFileBrowserItemsRepositoryImpl
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.SharedItemsRepositoryImpl
import com.nextcloud.talk.utils.database.user.CurrentUserProvider
+import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
import dagger.Module
import dagger.Provides
import okhttp3.OkHttpClient
@@ -53,6 +56,11 @@ class RepositoryModule {
}
@Provides
+ fun provideDialogPollRepository(ncApi: NcApi, userProvider: CurrentUserProviderNew): PollRepository {
+ return PollRepositoryImpl(ncApi, userProvider)
+ }
+
+ @Provides
fun provideRemoteFileBrowserItemsRepository(okHttpClient: OkHttpClient, userProvider: CurrentUserProvider):
RemoteFileBrowserItemsRepository {
return RemoteFileBrowserItemsRepositoryImpl(okHttpClient, userProvider)
diff --git a/app/src/main/java/com/nextcloud/talk/dagger/modules/ViewModelModule.kt b/app/src/main/java/com/nextcloud/talk/dagger/modules/ViewModelModule.kt
index f2356d0ab..3d8ee7535 100644
--- a/app/src/main/java/com/nextcloud/talk/dagger/modules/ViewModelModule.kt
+++ b/app/src/main/java/com/nextcloud/talk/dagger/modules/ViewModelModule.kt
@@ -25,6 +25,10 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.nextcloud.talk.remotefilebrowser.viewmodels.RemoteFileBrowserItemsViewModel
import com.nextcloud.talk.messagesearch.MessageSearchViewModel
+import com.nextcloud.talk.polls.viewmodels.PollCreateViewModel
+import com.nextcloud.talk.polls.viewmodels.PollMainViewModel
+import com.nextcloud.talk.polls.viewmodels.PollResultsViewModel
+import com.nextcloud.talk.polls.viewmodels.PollVoteViewModel
import com.nextcloud.talk.shareditems.viewmodels.SharedItemsViewModel
import dagger.Binds
import dagger.MapKey
@@ -63,6 +67,26 @@ abstract class ViewModelModule {
@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
+ @IntoMap
@ViewModelKey(RemoteFileBrowserItemsViewModel::class)
abstract fun remoteFileBrowserItemsViewModel(viewModel: RemoteFileBrowserItemsViewModel): ViewModel
}
diff --git a/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.kt b/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.kt
index 215c3b3f0..1d68ce625 100644
--- a/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.kt
+++ b/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.kt
@@ -124,6 +124,8 @@ data class ChatMessage(
var voiceMessageDownloadProgress: Int = 0,
) : Parcelable, MessageContentType, MessageContentType.Image {
+
+ // messageTypesToIgnore is weird. must be deleted by refactoring!!!
@JsonIgnore
var messageTypesToIgnore = Arrays.asList(
MessageType.REGULAR_TEXT_MESSAGE,
@@ -132,7 +134,8 @@ data class ChatMessage(
MessageType.SINGLE_LINK_AUDIO_MESSAGE,
MessageType.SINGLE_LINK_MESSAGE,
MessageType.SINGLE_NC_GEOLOCATION_MESSAGE,
- MessageType.VOICE_MESSAGE
+ MessageType.VOICE_MESSAGE,
+ MessageType.POLL_MESSAGE
)
fun hasFileAttachment(): Boolean {
@@ -165,6 +168,21 @@ data class ChatMessage(
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? {
if (messageParameters != null && messageParameters!!.size > 0) {
for ((_, individualHashMap) in messageParameters!!) {
@@ -207,6 +225,8 @@ data class ChatMessage(
MessageType.SINGLE_NC_ATTACHMENT_MESSAGE
} else if (hasGeoLocation()) {
MessageType.SINGLE_NC_GEOLOCATION_MESSAGE
+ } else if (isPoll()) {
+ MessageType.POLL_MESSAGE
} else {
MessageType.REGULAR_TEXT_MESSAGE
}
@@ -334,6 +354,15 @@ data class ChatMessage(
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 ""
@@ -410,6 +439,7 @@ data class ChatMessage(
SINGLE_LINK_AUDIO_MESSAGE,
SINGLE_NC_ATTACHMENT_MESSAGE,
SINGLE_NC_GEOLOCATION_MESSAGE,
+ POLL_MESSAGE,
VOICE_MESSAGE
}
@@ -460,7 +490,9 @@ data class ChatMessage(
CLEARED_CHAT,
REACTION,
REACTION_DELETED,
- REACTION_REVOKED
+ REACTION_REVOKED,
+ POLL_VOTED,
+ POLL_CLOSED
}
companion object {
diff --git a/app/src/main/java/com/nextcloud/talk/models/json/converters/EnumSystemMessageTypeConverter.kt b/app/src/main/java/com/nextcloud/talk/models/json/converters/EnumSystemMessageTypeConverter.kt
index caa75ec2b..20e525110 100644
--- a/app/src/main/java/com/nextcloud/talk/models/json/converters/EnumSystemMessageTypeConverter.kt
+++ b/app/src/main/java/com/nextcloud/talk/models/json/converters/EnumSystemMessageTypeConverter.kt
@@ -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.PASSWORD_REMOVED
import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.PASSWORD_SET
+import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.POLL_CLOSED
+import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.POLL_VOTED
import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.REACTION
import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.REACTION_DELETED
import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.REACTION_REVOKED
@@ -167,6 +169,8 @@ class EnumSystemMessageTypeConverter : StringBasedTypeConverter<ChatMessage.Syst
"reaction" -> REACTION
"reaction_deleted" -> REACTION_DELETED
"reaction_revoked" -> REACTION_REVOKED
+ "poll_voted" -> POLL_VOTED
+ "poll_closed" -> POLL_CLOSED
else -> DUMMY
}
}
@@ -224,6 +228,8 @@ class EnumSystemMessageTypeConverter : StringBasedTypeConverter<ChatMessage.Syst
REACTION -> return "reaction"
REACTION_DELETED -> return "reaction_deleted"
REACTION_REVOKED -> return "reaction_revoked"
+ POLL_VOTED -> return "poll_voted"
+ POLL_CLOSED -> return "poll_closed"
else -> return ""
}
}
diff --git a/app/src/main/java/com/nextcloud/talk/polls/adapters/PollCreateOptionItem.kt b/app/src/main/java/com/nextcloud/talk/polls/adapters/PollCreateOptionItem.kt
new file mode 100644
index 000000000..2f4217c8f
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/polls/adapters/PollCreateOptionItem.kt
@@ -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
+)
diff --git a/app/src/main/java/com/nextcloud/talk/polls/adapters/PollCreateOptionViewHolder.kt b/app/src/main/java/com/nextcloud/talk/polls/adapters/PollCreateOptionViewHolder.kt
new file mode 100644
index 000000000..6d40969dc
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/polls/adapters/PollCreateOptionViewHolder.kt
@@ -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)
+ }
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/polls/adapters/PollCreateOptionsAdapter.kt b/app/src/main/java/com/nextcloud/talk/polls/adapters/PollCreateOptionsAdapter.kt
new file mode 100644
index 000000000..39c8f7d7f
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/polls/adapters/PollCreateOptionsAdapter.kt
@@ -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()
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/polls/adapters/PollCreateOptionsItemListener.kt b/app/src/main/java/com/nextcloud/talk/polls/adapters/PollCreateOptionsItemListener.kt
new file mode 100644
index 000000000..a057592bd
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/polls/adapters/PollCreateOptionsItemListener.kt
@@ -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)
+}
diff --git a/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultHeaderItem.kt b/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultHeaderItem.kt
new file mode 100644
index 000000000..b8f14bc90
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultHeaderItem.kt
@@ -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
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultHeaderViewHolder.kt b/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultHeaderViewHolder.kt
new file mode 100644
index 000000000..b963f515d
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultHeaderViewHolder.kt
@@ -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
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultItem.kt b/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultItem.kt
new file mode 100644
index 000000000..d4d97fd77
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultItem.kt
@@ -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
+}
diff --git a/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultItemClickListener.kt b/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultItemClickListener.kt
new file mode 100644
index 000000000..d0a15156e
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultItemClickListener.kt
@@ -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()
+}
diff --git a/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultViewHolder.kt b/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultViewHolder.kt
new file mode 100644
index 000000000..e8da7ceee
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultViewHolder.kt
@@ -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)
+}
diff --git a/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultVoterItem.kt b/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultVoterItem.kt
new file mode 100644
index 000000000..3512b4c88
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultVoterItem.kt
@@ -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
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultVoterViewHolder.kt b/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultVoterViewHolder.kt
new file mode 100644
index 000000000..543591302
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultVoterViewHolder.kt
@@ -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
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultVotersOverviewItem.kt b/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultVotersOverviewItem.kt
new file mode 100644
index 000000000..ffb3065f3
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultVotersOverviewItem.kt
@@ -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
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultVotersOverviewViewHolder.kt b/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultVotersOverviewViewHolder.kt
new file mode 100644
index 000000000..c3805afe1
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultVotersOverviewViewHolder.kt
@@ -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 = "…"
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultsAdapter.kt b/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultsAdapter.kt
new file mode 100644
index 000000000..98a576cba
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultsAdapter.kt
@@ -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()
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/polls/model/Poll.kt b/app/src/main/java/com/nextcloud/talk/polls/model/Poll.kt
new file mode 100644
index 000000000..f96e0009e
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/polls/model/Poll.kt
@@ -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
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/polls/model/PollDetails.kt b/app/src/main/java/com/nextcloud/talk/polls/model/PollDetails.kt
new file mode 100644
index 000000000..6b025bf8f
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/polls/model/PollDetails.kt
@@ -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
+)
diff --git a/app/src/main/java/com/nextcloud/talk/polls/repositories/PollRepository.kt b/app/src/main/java/com/nextcloud/talk/polls/repositories/PollRepository.kt
new file mode 100644
index 000000000..feef4b563
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/polls/repositories/PollRepository.kt
@@ -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>
+}
diff --git a/app/src/main/java/com/nextcloud/talk/polls/repositories/PollRepositoryImpl.kt b/app/src/main/java/com/nextcloud/talk/polls/repositories/PollRepositoryImpl.kt
new file mode 100644
index 000000000..30138b67b
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/polls/repositories/PollRepositoryImpl.kt
@@ -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,
+ )
+ }
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/polls/repositories/model/PollDetailsResponse.kt b/app/src/main/java/com/nextcloud/talk/polls/repositories/model/PollDetailsResponse.kt
new file mode 100644
index 000000000..0d03e0172
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/polls/repositories/model/PollDetailsResponse.kt
@@ -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)
+}
diff --git a/app/src/main/java/com/nextcloud/talk/polls/repositories/model/PollOCS.kt b/app/src/main/java/com/nextcloud/talk/polls/repositories/model/PollOCS.kt
new file mode 100644
index 000000000..1adf3832b
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/polls/repositories/model/PollOCS.kt
@@ -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)
+}
diff --git a/app/src/main/java/com/nextcloud/talk/polls/repositories/model/PollOverall.kt b/app/src/main/java/com/nextcloud/talk/polls/repositories/model/PollOverall.kt
new file mode 100644
index 000000000..d5b8fb331
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/polls/repositories/model/PollOverall.kt
@@ -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)
+}
diff --git a/app/src/main/java/com/nextcloud/talk/polls/repositories/model/PollResponse.kt b/app/src/main/java/com/nextcloud/talk/polls/repositories/model/PollResponse.kt
new file mode 100644
index 000000000..9010ee2f5
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/polls/repositories/model/PollResponse.kt
@@ -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)
+}
diff --git a/app/src/main/java/com/nextcloud/talk/polls/ui/PollCreateDialogFragment.kt b/app/src/main/java/com/nextcloud/talk/polls/ui/PollCreateDialogFragment.kt
new file mode 100644
index 000000000..cdaa8fa58
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/polls/ui/PollCreateDialogFragment.kt
@@ -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
+ }
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/polls/ui/PollLoadingFragment.kt b/app/src/main/java/com/nextcloud/talk/polls/ui/PollLoadingFragment.kt
new file mode 100644
index 000000000..77b46f8aa
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/polls/ui/PollLoadingFragment.kt
@@ -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
+ }
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/polls/ui/PollMainDialogFragment.kt b/app/src/main/java/com/nextcloud/talk/polls/ui/PollMainDialogFragment.kt
new file mode 100644
index 000000000..85cba1f64
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/polls/ui/PollMainDialogFragment.kt
@@ -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
+ }
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/polls/ui/PollResultsFragment.kt b/app/src/main/java/com/nextcloud/talk/polls/ui/PollResultsFragment.kt
new file mode 100644
index 000000000..b1cbe4392
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/polls/ui/PollResultsFragment.kt
@@ -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()
+ }
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/polls/ui/PollVoteFragment.kt b/app/src/main/java/com/nextcloud/talk/polls/ui/PollVoteFragment.kt
new file mode 100644
index 000000000..67b878442
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/polls/ui/PollVoteFragment.kt
@@ -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()
+ }
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/polls/viewmodels/PollCreateViewModel.kt b/app/src/main/java/com/nextcloud/talk/polls/viewmodels/PollCreateViewModel.kt
new file mode 100644
index 000000000..ebaae84be
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/polls/viewmodels/PollCreateViewModel.kt
@@ -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
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/polls/viewmodels/PollMainViewModel.kt b/app/src/main/java/com/nextcloud/talk/polls/viewmodels/PollMainViewModel.kt
new file mode 100644
index 000000000..734b93fdf
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/polls/viewmodels/PollMainViewModel.kt
@@ -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
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/polls/viewmodels/PollResultsViewModel.kt b/app/src/main/java/com/nextcloud/talk/polls/viewmodels/PollResultsViewModel.kt
new file mode 100644
index 000000000..430b0d37c
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/polls/viewmodels/PollResultsViewModel.kt
@@ -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
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/polls/viewmodels/PollVoteViewModel.kt b/app/src/main/java/com/nextcloud/talk/polls/viewmodels/PollVoteViewModel.kt
new file mode 100644
index 000000000..e3689ff62
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/polls/viewmodels/PollVoteViewModel.kt
@@ -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
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/shareditems/repositories/SharedItemsRepository.kt b/app/src/main/java/com/nextcloud/talk/shareditems/repositories/SharedItemsRepository.kt
index a668d672d..62f69beee 100644
--- a/app/src/main/java/com/nextcloud/talk/shareditems/repositories/SharedItemsRepository.kt
+++ b/app/src/main/java/com/nextcloud/talk/shareditems/repositories/SharedItemsRepository.kt
@@ -28,7 +28,10 @@ import io.reactivex.Observable
interface SharedItemsRepository {
- fun media(parameters: Parameters, type: SharedItemType): Observable<SharedMediaItems>?
+ fun media(
+ parameters: Parameters,
+ type: SharedItemType
+ ): Observable<SharedMediaItems>?
fun media(
parameters: Parameters,
diff --git a/app/src/main/java/com/nextcloud/talk/ui/dialog/AttachmentDialog.kt b/app/src/main/java/com/nextcloud/talk/ui/dialog/AttachmentDialog.kt
index c760990ab..13a0564fb 100644
--- a/app/src/main/java/com/nextcloud/talk/ui/dialog/AttachmentDialog.kt
+++ b/app/src/main/java/com/nextcloud/talk/ui/dialog/AttachmentDialog.kt
@@ -43,6 +43,12 @@ class AttachmentDialog(val activity: Activity, var chatController: ChatControlle
setContentView(dialogAttachmentBinding.root)
window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
+ initItemsStrings()
+ initItemsVisibility()
+ initItemsClickListeners()
+ }
+
+ private fun initItemsStrings() {
var serverName = CapabilitiesUtilNew.getServerName(chatController.conversationUser)
dialogAttachmentBinding.txtAttachFileFromCloud.text = chatController.resources?.let {
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)
}
+ }
+ private fun initItemsVisibility() {
if (!CapabilitiesUtilNew.hasSpreedFeatureCapability(
chatController.conversationUser,
"geo-location-sharing"
@@ -59,6 +67,12 @@ class AttachmentDialog(val activity: Activity, var chatController: ChatControlle
dialogAttachmentBinding.menuShareLocation.visibility = View.GONE
}
+ if (!CapabilitiesUtilNew.hasSpreedFeatureCapability(chatController.conversationUser, "talk-polls")) {
+ dialogAttachmentBinding.menuAttachPoll.visibility = View.GONE
+ }
+ }
+
+ private fun initItemsClickListeners() {
dialogAttachmentBinding.menuShareLocation.setOnClickListener {
chatController.showShareLocationScreen()
dismiss()
@@ -74,6 +88,11 @@ class AttachmentDialog(val activity: Activity, var chatController: ChatControlle
dismiss()
}
+ dialogAttachmentBinding.menuAttachPoll.setOnClickListener {
+ chatController.createPoll()
+ dismiss()
+ }
+
dialogAttachmentBinding.menuAttachFileFromCloud.setOnClickListener {
chatController.showBrowserScreen()
dismiss()
diff --git a/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java b/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java
index 5c999ed0e..d38cbcb12 100644
--- a/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java
+++ b/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java
@@ -2,8 +2,10 @@
* Nextcloud Talk application
*
* @author Mario Danic
+ * @author Marcel Hibbe
* @author Tim Krüger
* 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>
*
* 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
- * {@link ApiUtils#getUrlForAttendees(int, String, String)} instead.
+ * @deprecated This is only supported on API v1-3, in API v4+ please use {@link ApiUtils#getUrlForAttendees(int,
+ * String, String)} instead.
*/
@Deprecated
public static String getUrlForRemovingParticipantFromConversation(String baseUrl, String roomToken, boolean isGuest) {
@@ -95,13 +97,13 @@ public class ApiUtils {
public static String getUrlForFilePreviewWithRemotePath(String baseUrl, String remotePath, int px) {
return baseUrl + "/index.php/core/preview.png?file="
- + Uri.encode(remotePath, "UTF-8")
- + "&x=" + px + "&y=" + px + "&a=1&mode=cover&forceIcon=1";
+ + Uri.encode(remotePath, "UTF-8")
+ + "&x=" + px + "&y=" + px + "&a=1&mode=cover&forceIcon=1";
}
public static String getUrlForFilePreviewWithFileId(String baseUrl, String fileId, int px) {
return baseUrl + "/index.php/core/preview?fileId="
- + fileId + "&x=" + px + "&y=" + px + "&a=1&mode=cover&forceIcon=1";
+ + fileId + "&x=" + px + "&y=" + px + "&a=1&mode=cover&forceIcon=1";
}
public static String getSharingUrl(String baseUrl) {
@@ -151,8 +153,8 @@ public class ApiUtils {
if (user.hasSpreedFeatureCapability("conversation-v2")) {
return version;
}
- if (version == APIv1 &&
- user.hasSpreedFeatureCapability("mention-flag") &&
+ if (version == APIv1 &&
+ user.hasSpreedFeatureCapability( "mention-flag") &&
!user.hasSpreedFeatureCapability("conversation-v4")) {
return version;
}
@@ -238,7 +240,7 @@ public class ApiUtils {
}
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");
}
return getUrlForRoom(version, baseUrl, token) + "/participants";
@@ -287,6 +289,7 @@ public class ApiUtils {
public static String getUrlForCall(int version, String baseUrl, String token) {
return getUrlForApi(version, baseUrl) + "/call/" + token;
}
+
public static String getUrlForChat(int version, String baseUrl, String token) {
return getUrlForApi(version, baseUrl) + "/chat/" + token;
}
@@ -294,10 +297,11 @@ public class ApiUtils {
public static String getUrlForMentionSuggestions(int version, String baseUrl, String token) {
return getUrlForChat(version, baseUrl, token) + "/mentions";
}
+
public static String getUrlForChatMessage(int version, String baseUrl, String token, String messageId) {
return getUrlForChat(version, baseUrl, token) + "/" + messageId;
}
-
+
public static String getUrlForChatSharedItems(int version, String baseUrl, String token) {
return getUrlForChat(version, baseUrl, token) + "/share";
}
@@ -366,11 +370,11 @@ public class ApiUtils {
}
public static RetrofitBucket getRetrofitBucketForAddParticipantWithSource(
- int version,
- String baseUrl,
- String token,
- String source,
- String id
+ int version,
+ String baseUrl,
+ String token,
+ String source,
+ String id
) {
RetrofitBucket retrofitBucket = getRetrofitBucketForAddParticipant(version, baseUrl, token, id);
retrofitBucket.getQueryMap().put("source", source);
@@ -417,7 +421,7 @@ public class ApiUtils {
public static String getUrlPushProxy() {
return NextcloudTalkApplication.Companion.getSharedApplication().
- getApplicationContext().getResources().getString(R.string.nc_push_server_url) + "/devices";
+ getApplicationContext().getResources().getString(R.string.nc_push_server_url) + "/devices";
}
public static String getUrlForNotificationWithId(String baseUrl, String notificationId) {
@@ -448,8 +452,10 @@ public class ApiUtils {
return getUrlForChat(version, baseUrl, roomToken) + "/share";
}
- public static String getUrlForHoverCard(String baseUrl, String userId) { return baseUrl + ocsApiVersion +
- "/hovercard/v1/" + userId; }
+ public static String getUrlForHoverCard(String baseUrl, String userId) {
+ return baseUrl + ocsApiVersion +
+ "/hovercard/v1/" + userId;
+ }
public static String getUrlForSetChatReadMarker(int version, String baseUrl, String roomToken) {
return getUrlForChat(version, baseUrl, roomToken) + "/read";
@@ -497,4 +503,16 @@ public class ApiUtils {
public static String getUrlForUnifiedSearch(@NotNull String baseUrl, @NotNull String providerId) {
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;
+ }
+
}
diff --git a/app/src/main/res/drawable/ic_baseline_bar_chart_24.xml b/app/src/main/res/drawable/ic_baseline_bar_chart_24.xml
new file mode 100644
index 000000000..b84f89296
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_bar_chart_24.xml
@@ -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>
diff --git a/app/src/main/res/drawable/ic_baseline_close_24.xml b/app/src/main/res/drawable/ic_baseline_close_24.xml
new file mode 100644
index 000000000..1d6c00461
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_close_24.xml
@@ -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>
diff --git a/app/src/main/res/drawable/ic_comment_white.xml b/app/src/main/res/drawable/ic_comment_white.xml
deleted file mode 100644
index 74f759c9e..000000000
--- a/app/src/main/res/drawable/ic_comment_white.xml
+++ /dev/null
@@ -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>
diff --git a/app/src/main/res/layout/dialog_attachment.xml b/app/src/main/res/layout/dialog_attachment.xml
index ed381203c..8280c8a90 100644
--- a/app/src/main/res/layout/dialog_attachment.xml
+++ b/app/src/main/res/layout/dialog_attachment.xml
@@ -40,6 +40,39 @@
android:textSize="@dimen/bottom_sheet_text_size" />
<LinearLayout
+ android:id="@+id/menu_attach_poll"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/bottom_sheet_item_height"
+ android:background="?android:attr/selectableItemBackground"
+ android:gravity="center_vertical"
+ android:orientation="horizontal"
+ android:paddingStart="@dimen/standard_padding"
+ android:paddingEnd="@dimen/standard_padding"
+ tools:ignore="UseCompoundDrawables">
+
+ <ImageView
+ android:id="@+id/menu_icon_attach_poll"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:contentDescription="@null"
+ android:src="@drawable/ic_baseline_bar_chart_24"
+ app:tint="@color/high_emphasis_menu_icon" />
+
+ <androidx.appcompat.widget.AppCompatTextView
+ android:id="@+id/txt_attach_poll"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="start|center_vertical"
+ android:paddingStart="@dimen/standard_double_padding"
+ android:paddingEnd="@dimen/zero"
+ android:text="@string/nc_create_poll"
+ android:textAlignment="viewStart"
+ android:textColor="@color/high_emphasis_text"
+ android:textSize="@dimen/bottom_sheet_text_size" />
+
+ </LinearLayout>
+
+ <LinearLayout
android:id="@+id/menu_attach_contact"
android:layout_width="match_parent"
android:layout_height="@dimen/bottom_sheet_item_height"
diff --git a/app/src/main/res/layout/dialog_poll_create.xml b/app/src/main/res/layout/dialog_poll_create.xml
new file mode 100644
index 000000000..9106aa53c
--- /dev/null
+++ b/app/src/main/res/layout/dialog_poll_create.xml
@@ -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>
diff --git a/app/src/main/res/layout/dialog_poll_loading.xml b/app/src/main/res/layout/dialog_poll_loading.xml
new file mode 100644
index 000000000..07e00ecd0
--- /dev/null
+++ b/app/src/main/res/layout/dialog_poll_loading.xml
@@ -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>
diff --git a/app/src/main/res/layout/dialog_poll_main.xml b/app/src/main/res/layout/dialog_poll_main.xml
new file mode 100644
index 000000000..d583b2905
--- /dev/null
+++ b/app/src/main/res/layout/dialog_poll_main.xml
@@ -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>
diff --git a/app/src/main/res/layout/dialog_poll_results.xml b/app/src/main/res/layout/dialog_poll_results.xml
new file mode 100644
index 000000000..0e347c4a5
--- /dev/null
+++ b/app/src/main/res/layout/dialog_poll_results.xml
@@ -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>
diff --git a/app/src/main/res/layout/dialog_poll_vote.xml b/app/src/main/res/layout/dialog_poll_vote.xml
new file mode 100644
index 000000000..c69f0a946
--- /dev/null
+++ b/app/src/main/res/layout/dialog_poll_vote.xml
@@ -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>
diff --git a/app/src/main/res/layout/item_custom_incoming_poll_message.xml b/app/src/main/res/layout/item_custom_incoming_poll_message.xml
new file mode 100644
index 000000000..6c8520a51
--- /dev/null
+++ b/app/src/main/res/layout/item_custom_incoming_poll_message.xml
@@ -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>
diff --git a/app/src/main/res/layout/item_custom_outcoming_poll_message.xml b/app/src/main/res/layout/item_custom_outcoming_poll_message.xml
new file mode 100644
index 000000000..1f3cfbdfb
--- /dev/null
+++ b/app/src/main/res/layout/item_custom_outcoming_poll_message.xml
@@ -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>
diff --git a/app/src/main/res/layout/poll_create_options_item.xml b/app/src/main/res/layout/poll_create_options_item.xml
new file mode 100644
index 000000000..2ae9e0fcf
--- /dev/null
+++ b/app/src/main/res/layout/poll_create_options_item.xml
@@ -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>
diff --git a/app/src/main/res/layout/poll_result_header_item.xml b/app/src/main/res/layout/poll_result_header_item.xml
new file mode 100644
index 000000000..44bfbf364
--- /dev/null
+++ b/app/src/main/res/layout/poll_result_header_item.xml
@@ -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>
diff --git a/app/src/main/res/layout/poll_result_voter_item.xml b/app/src/main/res/layout/poll_result_voter_item.xml
new file mode 100644
index 000000000..1e7774224
--- /dev/null
+++ b/app/src/main/res/layout/poll_result_voter_item.xml
@@ -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>
diff --git a/app/src/main/res/layout/poll_result_voters_overview_item.xml b/app/src/main/res/layout/poll_result_voters_overview_item.xml
new file mode 100644
index 000000000..06b03b840
--- /dev/null
+++ b/app/src/main/res/layout/poll_result_voters_overview_item.xml
@@ -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>
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 0d5bf97ad..2a0d69148 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -106,6 +106,9 @@
<color name="list_divider_background">#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
possible?! don't use this to set the background of dialogs -->
<color name="dialog_background">#FFFFFF</color>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index f6acc81cd..ec961c518 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -26,6 +26,7 @@
<string name="nc_no">No</string>
<string name="nc_common_skip">Skip</string>
<string name="nc_common_set">Set</string>
+ <string name="nc_common_dismiss">Dismiss</string>
<string name="nc_common_error_sorry">Sorry, something went wrong!</string>
<!-- Bottom Navigation -->
@@ -309,6 +310,7 @@
<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_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_voice" formatted="true">%1$s sent a voice message.</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_a_video_you">You sent a video.</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_voice_you">You sent a voice message.</string>
<string name="nc_formatted_message" translatable="false">%1$s: %2$s</string>
@@ -401,6 +404,7 @@
<!-- Upload -->
<string name="nc_add_file">Add to conversation</string>
<string name="nc_upload_picture_from_cam">Take photo</string>
+ <string name="nc_create_poll">Create poll</string>
<string name="nc_upload_from_cloud">Share from %1$s</string>
<string name="nc_upload_failed">Sorry, upload failed</string>
<string name="nc_upload_choose_local_files">Choose files</string>
@@ -527,6 +531,23 @@
<string name="message_search_begin_typing">Start typing to search …</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="reactions_tab_all">All</string>
@@ -534,4 +555,5 @@
<string name="call_without_notification">Call without notification</string>
<string name="set_avatar_from_camera">Set avatar from camera</string>
+
</resources>
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 02455e83e..011181880 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -147,6 +147,7 @@
<item name="android:textColor">@color/white</item>
<item name="android:typeface">sans</item>
<item name="android:textStyle">bold</item>
+ <item name="android:layout_gravity">center_vertical</item>
</style>
<style name="Widget.AppTheme.Button.IconButton" parent="Widget.MaterialComponents.Button.TextButton">
@@ -268,6 +269,7 @@
<item name="android:textAllCaps">false</item>
<item name="android:typeface">sans</item>
<item name="android:textStyle">bold</item>
+ <item name="android:layout_gravity">center_vertical</item>
</style>
<style name="TextAppearanceTab" parent="TextAppearance.Design.Tab">