diff --git a/changelog.d/4367.feature b/changelog.d/4367.feature new file mode 100644 index 00000000000..04db0bd6be2 --- /dev/null +++ b/changelog.d/4367.feature @@ -0,0 +1 @@ +Poll Feature - Create Poll Screen \ No newline at end of file diff --git a/library/ui-styles/src/main/res/color/form_edit_text_hint_color_selector.xml b/library/ui-styles/src/main/res/color/form_edit_text_hint_color_selector.xml new file mode 100644 index 00000000000..343c42ebf33 --- /dev/null +++ b/library/ui-styles/src/main/res/color/form_edit_text_hint_color_selector.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/library/ui-styles/src/main/res/color/form_edit_text_stroke_color_selector.xml b/library/ui-styles/src/main/res/color/form_edit_text_stroke_color_selector.xml new file mode 100644 index 00000000000..7079cc271b3 --- /dev/null +++ b/library/ui-styles/src/main/res/color/form_edit_text_stroke_color_selector.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/library/ui-styles/src/main/res/values/styles_edit_text.xml b/library/ui-styles/src/main/res/values/styles_edit_text.xml index cc1041a2ce0..a1a9f6a7e85 100644 --- a/library/ui-styles/src/main/res/values/styles_edit_text.xml +++ b/library/ui-styles/src/main/res/values/styles_edit_text.xml @@ -19,4 +19,9 @@ ?vctr_message_text_color + + \ No newline at end of file diff --git a/library/ui-styles/src/main/res/values/styles_polls.xml b/library/ui-styles/src/main/res/values/styles_polls.xml new file mode 100644 index 00000000000..ac99a6514b2 --- /dev/null +++ b/library/ui-styles/src/main/res/values/styles_polls.xml @@ -0,0 +1,10 @@ + + + + + + diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt index d0ce55070e6..a39ca5b4f48 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt @@ -102,6 +102,9 @@ object EventType { // Relation Events const val REACTION = "m.reaction" + // Poll + const val POLL_START = "org.matrix.msc3381.poll.start" + // Unwedging internal const val DUMMY = "m.dummy" diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessagePollContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessagePollContent.kt new file mode 100644 index 00000000000..ef2fd1867a7 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessagePollContent.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class MessagePollContent( + @Json(name = "org.matrix.msc3381.poll.start") val pollCreationInfo: PollCreationInfo? = null +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollAnswer.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollAnswer.kt new file mode 100644 index 00000000000..8f5ff53c85c --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollAnswer.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class PollAnswer( + @Json(name = "id") val id: String? = null, + @Json(name = "org.matrix.msc1767.text") val answer: String? = null +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollCreationInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollCreationInfo.kt new file mode 100644 index 00000000000..e652514b92c --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollCreationInfo.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class PollCreationInfo( + @Json(name = "question") val question: PollQuestion? = null, + @Json(name = "kind") val kind: String? = "org.matrix.msc3381.poll.disclosed", + @Json(name = "max_selections") val maxSelections: Int = 1, + @Json(name = "answers") val answers: List? = null +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollQuestion.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollQuestion.kt new file mode 100644 index 00000000000..76025f745e6 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollQuestion.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class PollQuestion( + @Json(name = "org.matrix.msc1767.text") val question: String? = null +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt index 6ae42de90cd..a2b38b66061 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt @@ -20,7 +20,6 @@ import org.matrix.android.sdk.api.session.content.ContentAttachmentData import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.model.message.MessageType -import org.matrix.android.sdk.api.session.room.model.message.OptionItem import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.util.Cancelable @@ -84,10 +83,10 @@ interface SendService { /** * Send a poll to the room. * @param question the question - * @param options list of (label, value) + * @param options list of options * @return a [Cancelable] */ - fun sendPoll(question: String, options: List): Cancelable + fun sendPoll(question: String, options: List): Cancelable /** * Method to send a poll response. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt index 177c98541c8..77aadef6bd3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt @@ -37,7 +37,6 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageFileContent import org.matrix.android.sdk.api.session.room.model.message.MessageImageContent import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachmentContent -import org.matrix.android.sdk.api.session.room.model.message.OptionItem import org.matrix.android.sdk.api.session.room.model.message.getFileUrl import org.matrix.android.sdk.api.session.room.send.SendService import org.matrix.android.sdk.api.session.room.send.SendState @@ -98,7 +97,7 @@ internal class DefaultSendService @AssistedInject constructor( .let { sendEvent(it) } } - override fun sendPoll(question: String, options: List): Cancelable { + override fun sendPoll(question: String, options: List): Cancelable { return localEchoEventFactory.createPollEvent(roomId, question, options) .also { createLocalEcho(it) } .let { sendEvent(it) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt index 8dd0c593872..5cb96875186 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt @@ -38,13 +38,14 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageContentWithF import org.matrix.android.sdk.api.session.room.model.message.MessageFileContent import org.matrix.android.sdk.api.session.room.model.message.MessageFormat import org.matrix.android.sdk.api.session.room.model.message.MessageImageContent -import org.matrix.android.sdk.api.session.room.model.message.MessageOptionsContent +import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent import org.matrix.android.sdk.api.session.room.model.message.MessagePollResponseContent import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent import org.matrix.android.sdk.api.session.room.model.message.MessageType import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent -import org.matrix.android.sdk.api.session.room.model.message.OPTION_TYPE_POLL -import org.matrix.android.sdk.api.session.room.model.message.OptionItem +import org.matrix.android.sdk.api.session.room.model.message.PollAnswer +import org.matrix.android.sdk.api.session.room.model.message.PollCreationInfo +import org.matrix.android.sdk.api.session.room.model.message.PollQuestion import org.matrix.android.sdk.api.session.room.model.message.ThumbnailInfo import org.matrix.android.sdk.api.session.room.model.message.VideoInfo import org.matrix.android.sdk.api.session.room.model.relation.ReactionContent @@ -138,24 +139,29 @@ internal class LocalEchoEventFactory @Inject constructor( fun createPollEvent(roomId: String, question: String, - options: List): Event { - val compatLabel = buildString { - append("[Poll] ") - append(question) - options.forEach { - append("\n") - append(it.value) - } - } - return createMessageEvent( - roomId, - MessageOptionsContent( - body = compatLabel, - label = question, - optionType = OPTION_TYPE_POLL, - options = options.toList() + options: List): Event { + val content = MessagePollContent( + pollCreationInfo = PollCreationInfo( + question = PollQuestion( + question = question + ), + answers = options.mapIndexed { index, option -> + PollAnswer( + id = index.toString(), + answer = option + ) + } ) ) + val localId = LocalEcho.createLocalEchoId() + return Event( + roomId = roomId, + originServerTs = dummyOriginServerTs(), + senderId = userId, + eventId = localId, + type = EventType.POLL_START, + content = content.toContent(), + unsignedData = UnsignedData(age = null, transactionId = localId)) } fun createReplaceTextOfReply(roomId: String, diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index 376e0e869a3..01d76306786 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -339,6 +339,7 @@ + diff --git a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt index bf72dcb076e..67cb9dd0f7e 100644 --- a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt @@ -94,6 +94,7 @@ import im.vector.app.features.login2.terms.LoginTermsFragment2 import im.vector.app.features.matrixto.MatrixToRoomSpaceFragment import im.vector.app.features.matrixto.MatrixToUserFragment import im.vector.app.features.pin.PinFragment +import im.vector.app.features.poll.create.CreatePollFragment import im.vector.app.features.qrcode.QrCodeScannerFragment import im.vector.app.features.reactions.EmojiChooserFragment import im.vector.app.features.reactions.EmojiSearchResultFragment @@ -837,4 +838,9 @@ interface FragmentModule { @IntoMap @FragmentKey(SpaceLeaveAdvancedFragment::class) fun bindSpaceLeaveAdvancedFragment(fragment: SpaceLeaveAdvancedFragment): Fragment + + @Binds + @IntoMap + @FragmentKey(CreatePollFragment::class) + fun bindCreatePollFragment(fragment: CreatePollFragment): Fragment } diff --git a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt index 5637c97b514..da5e7392e11 100644 --- a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt @@ -54,6 +54,7 @@ import im.vector.app.features.login.LoginViewModel import im.vector.app.features.login2.LoginViewModel2 import im.vector.app.features.login2.created.AccountCreatedViewModel import im.vector.app.features.matrixto.MatrixToBottomSheetViewModel +import im.vector.app.features.poll.create.CreatePollViewModel import im.vector.app.features.rageshake.BugReportViewModel import im.vector.app.features.reactions.EmojiSearchResultViewModel import im.vector.app.features.room.RequireActiveMembershipViewModel @@ -540,4 +541,9 @@ interface MavericksViewModelModule { @IntoMap @MavericksViewModelKey(VerificationBottomSheetViewModel::class) fun verificationBottomSheetViewModelFactory(factory: VerificationBottomSheetViewModel.Factory): MavericksAssistedViewModelFactory<*, *> + + @Binds + @IntoMap + @MavericksViewModelKey(CreatePollViewModel::class) + fun createPollViewModelFactory(factory: CreatePollViewModel.Factory): MavericksAssistedViewModelFactory<*, *> } diff --git a/vector/src/main/java/im/vector/app/core/ui/list/GenericButtonItem.kt b/vector/src/main/java/im/vector/app/core/ui/list/GenericButtonItem.kt index eb683631fde..fe59c82ce97 100644 --- a/vector/src/main/java/im/vector/app/core/ui/list/GenericButtonItem.kt +++ b/vector/src/main/java/im/vector/app/core/ui/list/GenericButtonItem.kt @@ -15,6 +15,8 @@ */ package im.vector.app.core.ui.list +import android.graphics.Typeface +import android.view.Gravity import androidx.annotation.ColorInt import androidx.annotation.DrawableRes import com.airbnb.epoxy.EpoxyAttribute @@ -47,6 +49,12 @@ abstract class GenericButtonItem : VectorEpoxyModel() @DrawableRes var iconRes: Int? = null + @EpoxyAttribute + var gravity: Int = Gravity.CENTER + + @EpoxyAttribute + var bold: Boolean = false + override fun bind(holder: Holder) { super.bind(holder) holder.button.text = text @@ -58,6 +66,10 @@ abstract class GenericButtonItem : VectorEpoxyModel() holder.button.icon = null } + holder.button.gravity = gravity or Gravity.CENTER_VERTICAL + val textStyle = if (bold) Typeface.BOLD else Typeface.NORMAL + holder.button.setTypeface(null, textStyle) + holder.button.onClick(buttonClickAction) } diff --git a/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt b/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt index 35644e18430..6c349d18dce 100644 --- a/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt +++ b/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt @@ -75,6 +75,7 @@ class AttachmentTypeSelectorView(context: Context, views.attachmentStickersButton.configure(Type.STICKER) views.attachmentAudioButton.configure(Type.AUDIO) views.attachmentContactButton.configure(Type.CONTACT) + views.attachmentPollButton.configure(Type.POLL) width = LinearLayout.LayoutParams.MATCH_PARENT height = LinearLayout.LayoutParams.WRAP_CONTENT animationStyle = 0 @@ -108,6 +109,7 @@ class AttachmentTypeSelectorView(context: Context, animateButtonIn(views.attachmentAudioButton, 0) animateButtonIn(views.attachmentContactButton, ANIMATION_DURATION / 4) animateButtonIn(views.attachmentStickersButton, ANIMATION_DURATION / 2) + animateButtonIn(views.attachmentPollButton, ANIMATION_DURATION / 4) } override fun dismiss() { @@ -212,6 +214,7 @@ class AttachmentTypeSelectorView(context: Context, FILE(PERMISSIONS_EMPTY), STICKER(PERMISSIONS_EMPTY), AUDIO(PERMISSIONS_EMPTY), - CONTACT(PERMISSIONS_FOR_PICKING_CONTACT) + CONTACT(PERMISSIONS_FOR_PICKING_CONTACT), + POLL(PERMISSIONS_EMPTY) } } diff --git a/vector/src/main/java/im/vector/app/features/command/Command.kt b/vector/src/main/java/im/vector/app/features/command/Command.kt index ccabd25ff48..1950038691c 100644 --- a/vector/src/main/java/im/vector/app/features/command/Command.kt +++ b/vector/src/main/java/im/vector/app/features/command/Command.kt @@ -47,7 +47,6 @@ enum class Command(val command: String, val parameters: String, @StringRes val d RAINBOW_EMOTE("/rainbowme", "", R.string.command_description_rainbow_emote, false), CLEAR_SCALAR_TOKEN("/clear_scalar_token", "", R.string.command_description_clear_scalar_token, false), SPOILER("/spoiler", "", R.string.command_description_spoiler, false), - POLL("/poll", "Question | Option 1 | Option 2 ...", R.string.command_description_poll, false), SHRUG("/shrug", "", R.string.command_description_shrug, false), LENNY("/lenny", "", R.string.command_description_lenny, false), PLAIN("/plain", "", R.string.command_description_plain, false), diff --git a/vector/src/main/java/im/vector/app/features/command/CommandParser.kt b/vector/src/main/java/im/vector/app/features/command/CommandParser.kt index 3a6b005d6fc..4b2a4aa28ca 100644 --- a/vector/src/main/java/im/vector/app/features/command/CommandParser.kt +++ b/vector/src/main/java/im/vector/app/features/command/CommandParser.kt @@ -345,15 +345,6 @@ object CommandParser { ParsedCommand.SendLenny(message) } - Command.POLL.command -> { - val rawCommand = textMessage.substring(Command.POLL.command.length).trim() - val split = rawCommand.split("|").map { it.trim() } - if (split.size > 2) { - ParsedCommand.SendPoll(split[0], split.subList(1, split.size)) - } else { - ParsedCommand.ErrorSyntax(Command.POLL) - } - } Command.DISCARD_SESSION.command -> { ParsedCommand.DiscardSession } diff --git a/vector/src/main/java/im/vector/app/features/command/ParsedCommand.kt b/vector/src/main/java/im/vector/app/features/command/ParsedCommand.kt index 89aa8d91889..4f8d19abb63 100644 --- a/vector/src/main/java/im/vector/app/features/command/ParsedCommand.kt +++ b/vector/src/main/java/im/vector/app/features/command/ParsedCommand.kt @@ -61,7 +61,6 @@ sealed class ParsedCommand { class SendSpoiler(val message: String) : ParsedCommand() class SendShrug(val message: CharSequence) : ParsedCommand() class SendLenny(val message: CharSequence) : ParsedCommand() - class SendPoll(val question: String, val options: List) : ParsedCommand() object DiscardSession : ParsedCommand() class ShowUser(val userId: String) : ParsedCommand() class SendChatEffect(val chatEffect: ChatEffect, val message: String) : ParsedCommand() diff --git a/vector/src/main/java/im/vector/app/features/form/FormEditTextWithDeleteItem.kt b/vector/src/main/java/im/vector/app/features/form/FormEditTextWithDeleteItem.kt new file mode 100644 index 00000000000..abcd1429d47 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/form/FormEditTextWithDeleteItem.kt @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.form + +import android.text.Editable +import android.view.inputmethod.EditorInfo +import android.widget.ImageButton +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import com.google.android.material.textfield.TextInputEditText +import com.google.android.material.textfield.TextInputLayout +import im.vector.app.R +import im.vector.app.core.epoxy.ClickListener +import im.vector.app.core.epoxy.TextListener +import im.vector.app.core.epoxy.VectorEpoxyHolder +import im.vector.app.core.epoxy.VectorEpoxyModel +import im.vector.app.core.epoxy.addTextChangedListenerOnce +import im.vector.app.core.epoxy.onClick +import im.vector.app.core.extensions.setTextIfDifferent +import im.vector.app.core.platform.SimpleTextWatcher + +@EpoxyModelClass(layout = R.layout.item_form_text_input_with_delete) +abstract class FormEditTextWithDeleteItem : VectorEpoxyModel() { + + @EpoxyAttribute + var hint: String? = null + + @EpoxyAttribute + var value: String? = null + + @EpoxyAttribute + var enabled: Boolean = true + + @EpoxyAttribute + var singleLine: Boolean = true + + @EpoxyAttribute + var imeOptions: Int? = null + + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) + var onTextChange: TextListener? = null + + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) + var onDeleteClicked: ClickListener? = null + + private val onTextChangeListener = object : SimpleTextWatcher() { + override fun afterTextChanged(s: Editable) { + onTextChange?.invoke(s.toString()) + } + } + + override fun bind(holder: Holder) { + super.bind(holder) + holder.textInputLayout.isEnabled = enabled + holder.textInputLayout.hint = hint + + holder.textInputEditText.setTextIfDifferent(value) + + holder.textInputEditText.isEnabled = enabled + holder.textInputEditText.isSingleLine = singleLine + + holder.textInputEditText.imeOptions = + imeOptions ?: when (singleLine) { + true -> EditorInfo.IME_ACTION_NEXT + false -> EditorInfo.IME_ACTION_NONE + } + + holder.textInputEditText.addTextChangedListenerOnce(onTextChangeListener) + + holder.textInputDeleteButton.onClick(onDeleteClicked) + } + + override fun shouldSaveViewState(): Boolean { + return false + } + + override fun unbind(holder: Holder) { + super.unbind(holder) + holder.textInputEditText.removeTextChangedListener(onTextChangeListener) + } + + class Holder : VectorEpoxyHolder() { + val textInputLayout by bind(R.id.formTextInputTextInputLayout) + val textInputEditText by bind(R.id.formTextInputTextInputEditText) + val textInputDeleteButton by bind(R.id.formTextInputDeleteButton) + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index d20c9796d25..dab7fd9bd93 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -2144,6 +2144,7 @@ class RoomDetailFragment @Inject constructor( AttachmentTypeSelectorView.Type.AUDIO -> attachmentsHelper.selectAudio(attachmentAudioActivityResultLauncher) AttachmentTypeSelectorView.Type.CONTACT -> attachmentsHelper.selectContact(attachmentContactActivityResultLauncher) AttachmentTypeSelectorView.Type.STICKER -> roomDetailViewModel.handle(RoomDetailAction.SelectStickerAttachment) + AttachmentTypeSelectorView.Type.POLL -> navigator.openCreatePoll(requireContext(), roomDetailArgs.roomId) }.exhaustive } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt index e80f25de2f3..727eb2f6705 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt @@ -47,7 +47,6 @@ import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.RoomAvatarContent import org.matrix.android.sdk.api.session.room.model.RoomMemberContent import org.matrix.android.sdk.api.session.room.model.message.MessageType -import org.matrix.android.sdk.api.session.room.model.message.OptionItem import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.room.send.UserDraft import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent @@ -259,11 +258,6 @@ class TextComposerViewModel @AssistedInject constructor( _viewEvents.post(TextComposerViewEvents.SlashCommandResultOk()) popDraft() } - is ParsedCommand.SendPoll -> { - room.sendPoll(slashCommandResult.question, slashCommandResult.options.mapIndexed { index, s -> OptionItem(s, "$index. $s") }) - _viewEvents.post(TextComposerViewEvents.SlashCommandResultOk()) - popDraft() - } is ParsedCommand.ChangeTopic -> { handleChangeTopicSlashCommand(slashCommandResult) } diff --git a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt index debdf3739c9..89a05c88dab 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt @@ -64,6 +64,8 @@ import im.vector.app.features.media.VectorAttachmentViewerActivity import im.vector.app.features.pin.PinActivity import im.vector.app.features.pin.PinArgs import im.vector.app.features.pin.PinMode +import im.vector.app.features.poll.create.CreatePollActivity +import im.vector.app.features.poll.create.CreatePollArgs import im.vector.app.features.roomdirectory.RoomDirectoryActivity import im.vector.app.features.roomdirectory.RoomDirectoryData import im.vector.app.features.roomdirectory.createroom.CreateRoomActivity @@ -498,6 +500,14 @@ class DefaultNavigator @Inject constructor( context.startActivity(intent) } + override fun openCreatePoll(context: Context, roomId: String) { + val intent = CreatePollActivity.getIntent( + context, + CreatePollArgs(roomId = roomId) + ) + context.startActivity(intent) + } + private fun startActivity(context: Context, intent: Intent, buildTask: Boolean) { if (buildTask) { val stackBuilder = TaskStackBuilder.create(context) diff --git a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt index 612643c8041..264593fe180 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt @@ -140,4 +140,6 @@ interface Navigator { fun openDevTools(context: Context, roomId: String) fun openCallTransfer(context: Context, callId: String) + + fun openCreatePoll(context: Context, roomId: String) } diff --git a/vector/src/main/java/im/vector/app/features/poll/create/CreatePollAction.kt b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollAction.kt new file mode 100644 index 00000000000..182750fbd2b --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollAction.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.poll.create + +import im.vector.app.core.platform.VectorViewModelAction + +sealed class CreatePollAction : VectorViewModelAction { + data class OnQuestionChanged(val question: String) : CreatePollAction() + data class OnOptionChanged(val index: Int, val option: String) : CreatePollAction() + data class OnDeleteOption(val index: Int) : CreatePollAction() + object OnAddOption : CreatePollAction() + object OnCreatePoll : CreatePollAction() +} diff --git a/vector/src/main/java/im/vector/app/features/poll/create/CreatePollActivity.kt b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollActivity.kt new file mode 100644 index 00000000000..fc830ddae9a --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollActivity.kt @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.poll.create + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.view.View +import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.R +import im.vector.app.core.extensions.addFragment +import im.vector.app.core.platform.SimpleFragmentActivity + +@AndroidEntryPoint +class CreatePollActivity : SimpleFragmentActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + views.toolbar.visibility = View.GONE + + val createPollArgs: CreatePollArgs? = intent?.extras?.getParcelable(EXTRA_CREATE_POLL_ARGS) + + if (isFirstCreation()) { + addFragment( + R.id.container, + CreatePollFragment::class.java, + createPollArgs + ) + } + } + + companion object { + + private const val EXTRA_CREATE_POLL_ARGS = "EXTRA_CREATE_POLL_ARGS" + + fun getIntent(context: Context, createPollArgs: CreatePollArgs): Intent { + return Intent(context, CreatePollActivity::class.java).apply { + putExtra(EXTRA_CREATE_POLL_ARGS, createPollArgs) + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/poll/create/CreatePollController.kt b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollController.kt new file mode 100644 index 00000000000..515f59a38ca --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollController.kt @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.poll.create + +import android.view.Gravity +import android.view.inputmethod.EditorInfo +import com.airbnb.epoxy.EpoxyController +import im.vector.app.R +import im.vector.app.core.resources.ColorProvider +import im.vector.app.core.resources.StringProvider +import im.vector.app.core.ui.list.ItemStyle +import im.vector.app.core.ui.list.genericButtonItem +import im.vector.app.core.ui.list.genericItem +import im.vector.app.features.form.formEditTextItem +import im.vector.app.features.form.formEditTextWithDeleteItem +import javax.inject.Inject + +class CreatePollController @Inject constructor( + private val stringProvider: StringProvider, + private val colorProvider: ColorProvider +) : EpoxyController() { + + private var state: CreatePollViewState? = null + var callback: Callback? = null + + fun setData(state: CreatePollViewState) { + this.state = state + requestModelBuild() + } + + override fun buildModels() { + val currentState = state ?: return + val host = this + + genericItem { + id("question_title") + style(ItemStyle.BIG_TEXT) + title(host.stringProvider.getString(R.string.create_poll_question_title)) + } + + val questionImeAction = if (currentState.options.isEmpty()) EditorInfo.IME_ACTION_DONE else EditorInfo.IME_ACTION_NEXT + + formEditTextItem { + id("question") + value(currentState.question) + hint(host.stringProvider.getString(R.string.create_poll_question_hint)) + singleLine(true) + imeOptions(questionImeAction) + maxLength(500) + onTextChange { + host.callback?.onQuestionChanged(it) + } + } + + genericItem { + id("options_title") + style(ItemStyle.BIG_TEXT) + title(host.stringProvider.getString(R.string.create_poll_options_title)) + } + + currentState.options.forEachIndexed { index, option -> + val imeOptions = if (index == currentState.options.size - 1) EditorInfo.IME_ACTION_DONE else EditorInfo.IME_ACTION_NEXT + formEditTextWithDeleteItem { + id("option_$index") + value(option) + hint(host.stringProvider.getString(R.string.create_poll_options_hint, (index + 1))) + singleLine(true) + imeOptions(imeOptions) + onTextChange { + host.callback?.onOptionChanged(index, it) + } + onDeleteClicked { + host.callback?.onDeleteOption(index) + } + } + } + + if (currentState.canAddMoreOptions) { + genericButtonItem { + id("add_option") + text(host.stringProvider.getString(R.string.create_poll_add_option)) + textColor(host.colorProvider.getColor(R.color.palette_element_green)) + gravity(Gravity.START) + bold(true) + buttonClickAction { + host.callback?.onAddOption() + } + } + } + } + + interface Callback { + fun onQuestionChanged(question: String) + fun onOptionChanged(index: Int, option: String) + fun onDeleteOption(index: Int) + fun onAddOption() + } +} diff --git a/vector/src/main/java/im/vector/app/features/poll/create/CreatePollFragment.kt b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollFragment.kt new file mode 100644 index 00000000000..dc82579f15d --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollFragment.kt @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.poll.create + +import android.os.Bundle +import android.os.Parcelable +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.view.isVisible +import com.airbnb.mvrx.activityViewModel +import com.airbnb.mvrx.withState +import im.vector.app.R +import im.vector.app.core.extensions.configureWith +import im.vector.app.core.platform.VectorBaseFragment +import im.vector.app.databinding.FragmentCreatePollBinding +import kotlinx.parcelize.Parcelize +import javax.inject.Inject + +@Parcelize +data class CreatePollArgs( + val roomId: String, +) : Parcelable + +class CreatePollFragment @Inject constructor( + private val controller: CreatePollController +) : VectorBaseFragment(), CreatePollController.Callback { + + private val viewModel: CreatePollViewModel by activityViewModel() + + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentCreatePollBinding { + return FragmentCreatePollBinding.inflate(inflater, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + vectorBaseActivity.setSupportActionBar(views.createPollToolbar) + + views.createPollRecyclerView.configureWith(controller, disableItemAnimation = true) + controller.callback = this + + views.createPollClose.debouncedClicks { + requireActivity().finish() + } + + views.createPollButton.debouncedClicks { + viewModel.handle(CreatePollAction.OnCreatePoll) + } + + viewModel.onEach(CreatePollViewState::canCreatePoll) { canCreatePoll -> + views.createPollButton.isEnabled = canCreatePoll + } + + viewModel.observeViewEvents { + when (it) { + CreatePollViewEvents.Success -> handleSuccess() + CreatePollViewEvents.EmptyQuestionError -> handleEmptyQuestionError() + is CreatePollViewEvents.NotEnoughOptionsError -> handleNotEnoughOptionsError(it.requiredOptionsCount) + } + } + } + + override fun invalidate() = withState(viewModel) { + controller.setData(it) + } + + override fun onQuestionChanged(question: String) { + viewModel.handle(CreatePollAction.OnQuestionChanged(question)) + } + + override fun onOptionChanged(index: Int, option: String) { + viewModel.handle(CreatePollAction.OnOptionChanged(index, option)) + } + + override fun onDeleteOption(index: Int) { + viewModel.handle(CreatePollAction.OnDeleteOption(index)) + } + + override fun onAddOption() { + viewModel.handle(CreatePollAction.OnAddOption) + // Scroll to bottom to show "Add Option" button + views.createPollRecyclerView.apply { + postDelayed({ + smoothScrollToPosition(adapter?.itemCount?.minus(1) ?: 0) + }, 100) + } + } + + private fun handleSuccess() { + requireActivity().finish() + } + + private fun handleEmptyQuestionError() { + renderToast(getString(R.string.create_poll_empty_question_error)) + } + + private fun handleNotEnoughOptionsError(requiredOptionsCount: Int) { + renderToast( + resources.getQuantityString( + R.plurals.create_poll_not_enough_options_error, + requiredOptionsCount, + requiredOptionsCount + ) + ) + } + + private fun renderToast(message: String) { + views.createPollToast.removeCallbacks(hideToastRunnable) + views.createPollToast.text = message + views.createPollToast.isVisible = true + views.createPollToast.postDelayed(hideToastRunnable, 2_000) + } + + private val hideToastRunnable = Runnable { + views.createPollToast.isVisible = false + } +} diff --git a/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewEvents.kt b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewEvents.kt new file mode 100644 index 00000000000..fa06deea6e5 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewEvents.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.poll.create + +import im.vector.app.core.platform.VectorViewEvents + +sealed class CreatePollViewEvents : VectorViewEvents { + object Success : CreatePollViewEvents() + object EmptyQuestionError : CreatePollViewEvents() + data class NotEnoughOptionsError(val requiredOptionsCount: Int) : CreatePollViewEvents() +} diff --git a/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewModel.kt b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewModel.kt new file mode 100644 index 00000000000..eb4b441f18a --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewModel.kt @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.poll.create + +import com.airbnb.mvrx.MavericksViewModelFactory +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import im.vector.app.core.di.MavericksAssistedViewModelFactory +import im.vector.app.core.di.hiltMavericksViewModelFactory +import im.vector.app.core.platform.VectorViewModel +import org.matrix.android.sdk.api.session.Session + +class CreatePollViewModel @AssistedInject constructor( + @Assisted private val initialState: CreatePollViewState, + session: Session +) : VectorViewModel(initialState) { + + private val room = session.getRoom(initialState.roomId)!! + + @AssistedFactory + interface Factory : MavericksAssistedViewModelFactory { + override fun create(initialState: CreatePollViewState): CreatePollViewModel + } + + companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() { + + const val MIN_OPTIONS_COUNT = 2 + private const val MAX_OPTIONS_COUNT = 20 + } + + init { + observeState() + } + + private fun observeState() { + onEach( + CreatePollViewState::question, + CreatePollViewState::options + ) { question, options -> + setState { + copy( + canCreatePoll = canCreatePoll(question, options), + canAddMoreOptions = options.size < MAX_OPTIONS_COUNT + ) + } + } + } + + override fun handle(action: CreatePollAction) { + when (action) { + CreatePollAction.OnCreatePoll -> handleOnCreatePoll() + CreatePollAction.OnAddOption -> handleOnAddOption() + is CreatePollAction.OnDeleteOption -> handleOnDeleteOption(action.index) + is CreatePollAction.OnOptionChanged -> handleOnOptionChanged(action.index, action.option) + is CreatePollAction.OnQuestionChanged -> handleOnQuestionChanged(action.question) + } + } + + private fun handleOnCreatePoll() = withState { state -> + val nonEmptyOptions = state.options.filter { it.isNotEmpty() } + when { + state.question.isEmpty() -> { + _viewEvents.post(CreatePollViewEvents.EmptyQuestionError) + } + nonEmptyOptions.size < MIN_OPTIONS_COUNT -> { + _viewEvents.post(CreatePollViewEvents.NotEnoughOptionsError(requiredOptionsCount = MIN_OPTIONS_COUNT)) + } + else -> { + room.sendPoll(state.question, state.options) + _viewEvents.post(CreatePollViewEvents.Success) + } + } + } + + private fun handleOnAddOption() { + setState { + val extendedOptions = options + "" + copy( + options = extendedOptions + ) + } + } + + private fun handleOnDeleteOption(index: Int) { + setState { + val filteredOptions = options.filterIndexed { ind, _ -> ind != index } + copy( + options = filteredOptions + ) + } + } + + private fun handleOnOptionChanged(index: Int, option: String) { + setState { + val changedOptions = options.mapIndexed { ind, s -> if (ind == index) option else s } + copy( + options = changedOptions + ) + } + } + + private fun handleOnQuestionChanged(question: String) { + setState { + copy( + question = question + ) + } + } + + private fun canCreatePoll(question: String, options: List): Boolean { + return question.isNotEmpty() && + options.filter { it.isNotEmpty() }.size >= MIN_OPTIONS_COUNT + } +} diff --git a/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewState.kt b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewState.kt new file mode 100644 index 00000000000..a9060cc89f7 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewState.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.poll.create + +import com.airbnb.mvrx.MavericksState + +data class CreatePollViewState( + val roomId: String, + val question: String = "", + val options: List = List(CreatePollViewModel.MIN_OPTIONS_COUNT) { "" }, + val canCreatePoll: Boolean = false, + val canAddMoreOptions: Boolean = true +) : MavericksState { + + constructor(args: CreatePollArgs) : this( + roomId = args.roomId + ) +} diff --git a/vector/src/main/res/drawable/ic_attachment_poll_white_24dp.xml b/vector/src/main/res/drawable/ic_attachment_poll_white_24dp.xml new file mode 100644 index 00000000000..8cbcc6e47c1 --- /dev/null +++ b/vector/src/main/res/drawable/ic_attachment_poll_white_24dp.xml @@ -0,0 +1,10 @@ + + + diff --git a/vector/src/main/res/drawable/ic_delete_10dp.xml b/vector/src/main/res/drawable/ic_delete_10dp.xml new file mode 100644 index 00000000000..f8229a71d08 --- /dev/null +++ b/vector/src/main/res/drawable/ic_delete_10dp.xml @@ -0,0 +1,18 @@ + + + + diff --git a/vector/src/main/res/layout/fragment_create_poll.xml b/vector/src/main/res/layout/fragment_create_poll.xml new file mode 100644 index 00000000000..1911aaefc98 --- /dev/null +++ b/vector/src/main/res/layout/fragment_create_poll.xml @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + +