Merge branch 'feature/ons/poll' into feature/ons/poll_timeline
* feature/ons/poll: Design review fixes. Code review fixes. Make the poll option visible so that it can be tested from the PR Limit maximum number of poll options. Code review fixes. Fix UI issues. Remove poll command. Use unstable types. Create poll event content. Create poll UI implementation. Create poll fragment with a title. Create poll screen components implemented. Add poll icon to attachment type selector.
This commit is contained in:
commit
e0abd991c5
|
@ -0,0 +1 @@
|
|||
Poll Feature - Create Poll Screen
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="?vctr_content_tertiary" android:state_enabled="false" />
|
||||
<item android:color="?vctr_content_tertiary" android:state_focused="false" />
|
||||
<item android:color="?colorPrimary" />
|
||||
</selector>
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="?colorPrimary" android:state_focused="true"/>
|
||||
<item android:color="?colorPrimary" android:state_hovered="true"/>
|
||||
<item android:color="?colorPrimary" android:state_enabled="false"/>
|
||||
<item android:color="?vctr_content_quinary"/>
|
||||
</selector>
|
|
@ -19,4 +19,9 @@
|
|||
<item name="android:textColor">?vctr_message_text_color</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Vector.EditText.Form" parent="Widget.MaterialComponents.TextInputLayout.OutlinedBox">
|
||||
<item name="boxStrokeColor">@color/form_edit_text_stroke_color_selector</item>
|
||||
<item name="android:textColorHint">@color/form_edit_text_hint_color_selector</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<style name="Widget.Vector.Button.CreatePoll" parent="Widget.Vector.Button">
|
||||
<item name="android:backgroundTint">@color/button_background_tint_selector</item>
|
||||
<item name="android:textAppearance">@style/TextAppearance.Vector.Button</item>
|
||||
<item name="android:textColor">@android:color/white</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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
|
||||
)
|
|
@ -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
|
||||
)
|
|
@ -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<PollAnswer>? = null
|
||||
)
|
|
@ -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
|
||||
)
|
|
@ -84,10 +84,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<OptionItem>): Cancelable
|
||||
fun sendPoll(question: String, options: List<String>): Cancelable
|
||||
|
||||
/**
|
||||
* Method to send a poll response.
|
||||
|
|
|
@ -98,7 +98,7 @@ internal class DefaultSendService @AssistedInject constructor(
|
|||
.let { sendEvent(it) }
|
||||
}
|
||||
|
||||
override fun sendPoll(question: String, options: List<OptionItem>): Cancelable {
|
||||
override fun sendPoll(question: String, options: List<String>): Cancelable {
|
||||
return localEchoEventFactory.createPollEvent(roomId, question, options)
|
||||
.also { createLocalEcho(it) }
|
||||
.let { sendEvent(it) }
|
||||
|
|
|
@ -39,12 +39,16 @@ 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 +142,29 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
|
||||
fun createPollEvent(roomId: String,
|
||||
question: String,
|
||||
options: List<OptionItem>): 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<String>): 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,
|
||||
|
|
|
@ -339,6 +339,7 @@
|
|||
<activity android:name=".features.spaces.manage.SpaceManageActivity" />
|
||||
<activity android:name=".features.spaces.people.SpacePeopleActivity" />
|
||||
<activity android:name=".features.spaces.leave.SpaceLeaveAdvancedActivity" />
|
||||
<activity android:name=".features.poll.create.CreatePollActivity" />
|
||||
|
||||
<!-- Services -->
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ import dagger.hilt.android.components.ActivityComponent
|
|||
import dagger.multibindings.IntoMap
|
||||
import im.vector.app.features.attachments.preview.AttachmentsPreviewFragment
|
||||
import im.vector.app.features.contactsbook.ContactsBookFragment
|
||||
import im.vector.app.features.poll.create.CreatePollFragment
|
||||
import im.vector.app.features.crypto.keysbackup.settings.KeysBackupSettingsFragment
|
||||
import im.vector.app.features.crypto.quads.SharedSecuredStorageKeyFragment
|
||||
import im.vector.app.features.crypto.quads.SharedSecuredStoragePassphraseFragment
|
||||
|
@ -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
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ import im.vector.app.features.call.conference.JitsiCallViewModel
|
|||
import im.vector.app.features.call.transfer.CallTransferViewModel
|
||||
import im.vector.app.features.contactsbook.ContactsBookViewModel
|
||||
import im.vector.app.features.createdirect.CreateDirectRoomViewModel
|
||||
import im.vector.app.features.poll.create.CreatePollViewModel
|
||||
import im.vector.app.features.crypto.keysbackup.settings.KeysBackupSettingsViewModel
|
||||
import im.vector.app.features.crypto.quads.SharedSecureStorageViewModel
|
||||
import im.vector.app.features.crypto.recover.BootstrapSharedViewModel
|
||||
|
@ -546,4 +547,9 @@ interface MavericksViewModelModule {
|
|||
@IntoMap
|
||||
@MavericksViewModelKey(VerificationBottomSheetViewModel::class)
|
||||
fun verificationBottomSheetViewModelFactory(factory: VerificationBottomSheetViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@MavericksViewModelKey(CreatePollViewModel::class)
|
||||
fun createPollViewModelFactory(factory: CreatePollViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
||||
}
|
||||
|
|
|
@ -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<GenericButtonItem.Holder>()
|
|||
@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<GenericButtonItem.Holder>()
|
|||
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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,7 +47,6 @@ enum class Command(val command: String, val parameters: String, @StringRes val d
|
|||
RAINBOW_EMOTE("/rainbowme", "<message>", R.string.command_description_rainbow_emote, false),
|
||||
CLEAR_SCALAR_TOKEN("/clear_scalar_token", "", R.string.command_description_clear_scalar_token, false),
|
||||
SPOILER("/spoiler", "<message>", R.string.command_description_spoiler, false),
|
||||
POLL("/poll", "Question | Option 1 | Option 2 ...", R.string.command_description_poll, false),
|
||||
SHRUG("/shrug", "<message>", R.string.command_description_shrug, false),
|
||||
LENNY("/lenny", "<message>", R.string.command_description_lenny, false),
|
||||
PLAIN("/plain", "<message>", R.string.command_description_plain, false),
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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<String>) : ParsedCommand()
|
||||
object DiscardSession : ParsedCommand()
|
||||
class ShowUser(val userId: String) : ParsedCommand()
|
||||
class SendChatEffect(val chatEffect: ChatEffect, val message: String) : ParsedCommand()
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* 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.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<FormEditTextWithDeleteItem.Holder>() {
|
||||
|
||||
@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
|
||||
if (singleLine) {
|
||||
holder.textInputEditText.setSingleLine()
|
||||
}
|
||||
imeOptions?.let {
|
||||
holder.textInputEditText.imeOptions = it
|
||||
}
|
||||
|
||||
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<TextInputLayout>(R.id.formTextInputTextInputLayout)
|
||||
val textInputEditText by bind<TextInputEditText>(R.id.formTextInputTextInputEditText)
|
||||
val textInputDeleteButton by bind<ImageButton>(R.id.formTextInputDeleteButton)
|
||||
}
|
||||
}
|
|
@ -2158,6 +2158,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
|
||||
}
|
||||
|
||||
|
|
|
@ -259,11 +259,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)
|
||||
}
|
||||
|
|
|
@ -40,6 +40,9 @@ import im.vector.app.features.call.conference.JitsiCallViewModel
|
|||
import im.vector.app.features.call.conference.VectorJitsiActivity
|
||||
import im.vector.app.features.call.transfer.CallTransferActivity
|
||||
import im.vector.app.features.createdirect.CreateDirectRoomActivity
|
||||
import im.vector.app.features.poll.create.CreatePollActivity
|
||||
import im.vector.app.features.poll.create.CreatePollArgs
|
||||
import im.vector.app.features.poll.create.CreatePollViewModel
|
||||
import im.vector.app.features.crypto.keysbackup.settings.KeysBackupManageActivity
|
||||
import im.vector.app.features.crypto.keysbackup.setup.KeysBackupSetupActivity
|
||||
import im.vector.app.features.crypto.recover.BootstrapBottomSheet
|
||||
|
@ -498,6 +501,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)
|
||||
|
|
|
@ -140,4 +140,6 @@ interface Navigator {
|
|||
fun openDevTools(context: Context, roomId: String)
|
||||
|
||||
fun openCallTransfer(context: Context, callId: String)
|
||||
|
||||
fun openCreatePoll(context: Context, roomId: String)
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
|
@ -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 im.vector.app.R
|
||||
import im.vector.app.core.extensions.addFragment
|
||||
import im.vector.app.core.platform.SimpleFragmentActivity
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* 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))
|
||||
}
|
||||
|
||||
formEditTextItem {
|
||||
id("question")
|
||||
value(currentState.question)
|
||||
hint(host.stringProvider.getString(R.string.create_poll_question_hint))
|
||||
singleLine(false)
|
||||
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()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
* 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<FragmentCreatePollBinding>(), 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)
|
||||
controller.callback = this
|
||||
|
||||
views.createPollClose.debouncedClicks {
|
||||
requireActivity().finish()
|
||||
}
|
||||
|
||||
views.createPollButton.debouncedClicks {
|
||||
viewModel.handle(CreatePollAction.OnCreatePoll)
|
||||
}
|
||||
|
||||
viewModel.subscribe(this) {
|
||||
views.createPollButton.isEnabled = it.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)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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<CreatePollViewState, CreatePollAction, CreatePollViewEvents>(initialState) {
|
||||
|
||||
private val room = session.getRoom(initialState.roomId)!!
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : MavericksAssistedViewModelFactory<CreatePollViewModel, CreatePollViewState> {
|
||||
override fun create(initialState: CreatePollViewState): CreatePollViewModel
|
||||
}
|
||||
|
||||
companion object : MavericksViewModelFactory<CreatePollViewModel, CreatePollViewState> 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<String>): Boolean {
|
||||
return question.isNotEmpty() &&
|
||||
options.filter { it.isNotEmpty() }.size >= MIN_OPTIONS_COUNT
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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<String> = List(CreatePollViewModel.MIN_OPTIONS_COUNT) { "" },
|
||||
val canCreatePoll: Boolean = false,
|
||||
val canAddMoreOptions: Boolean = true
|
||||
) : MavericksState {
|
||||
|
||||
constructor(args: CreatePollArgs) : this(
|
||||
roomId = args.roomId
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M10.5,2C10.2239,2 10,2.2239 10,2.5V22H14V2.5C14,2.2239 13.7761,2 13.5,2H10.5ZM3,9.5C3,9.2239 3.2239,9 3.5,9H6.5C6.7761,9 7,9.2239 7,9.5V22H3V9.5ZM17,13.5C17,13.2239 17.2239,13 17.5,13H20.5C20.7761,13 21,13.2239 21,13.5V22H17V13.5Z"
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
|
@ -0,0 +1,18 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="10dp"
|
||||
android:height="10dp"
|
||||
android:viewportWidth="10"
|
||||
android:viewportHeight="10">
|
||||
<path
|
||||
android:pathData="M0.9998,0.9997L8.9998,8.9997"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#737D8C"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M9.0005,0.9997L1.0005,8.9997"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#737D8C"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
|
@ -0,0 +1,98 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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="match_parent">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appBarLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/createPollToolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?actionBarSize"
|
||||
app:contentInsetStart="0dp">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/createPollClose"
|
||||
android:layout_width="@dimen/layout_touch_size"
|
||||
android:layout_height="@dimen/layout_touch_size"
|
||||
android:clickable="true"
|
||||
android:contentDescription="@string/action_close"
|
||||
android:focusable="true"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/ic_x_18dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:tint="?vctr_content_secondary"
|
||||
tools:ignore="MissingPrefix" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/createPollTitle"
|
||||
style="@style/Widget.Vector.TextView.HeadlineMedium"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:text="@string/create_poll_title"
|
||||
android:textColor="?vctr_content_primary"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toEndOf="@+id/createPollClose"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</com.google.android.material.appbar.MaterialToolbar>
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/createPollRecyclerView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:overScrollMode="always"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
app:layout_constraintBottom_toTopOf="@id/createPollButton"
|
||||
app:layout_constraintTop_toBottomOf="@+id/appBarLayout"
|
||||
tools:listitem="@layout/item_profile_action" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/createPollButton"
|
||||
style="@style/Widget.Vector.Button.CreatePoll"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:layout_margin="16dp"
|
||||
android:text="@string/create_poll_button"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
tools:enabled="false" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/createPollToast"
|
||||
style="@style/Widget.Vector.TextView.Caption.Toast"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginBottom="84dp"
|
||||
android:accessibilityLiveRegion="polite"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
tools:text="@string/voice_message_release_to_send_toast"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/formTextInputTextInputLayout"
|
||||
style="@style/Widget.Vector.EditText.Form"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/layout_horizontal_margin"
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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"
|
||||
android:background="?android:colorBackground"
|
||||
android:minHeight="@dimen/item_form_min_height">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/formTextInputTextInputLayout"
|
||||
style="@style/Widget.Vector.EditText.Form"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/layout_horizontal_margin"
|
||||
android:layout_marginEnd="@dimen/layout_horizontal_margin"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/formTextInputDeleteButton"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/formTextInputTextInputEditText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:hint="@string/create_room_name_hint" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/formTextInputDeleteButton"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginEnd="@dimen/layout_horizontal_margin"
|
||||
android:background="@drawable/circle"
|
||||
android:contentDescription="@string/delete"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/ic_delete_10dp"
|
||||
app:layout_constraintBottom_toBottomOf="@id/formTextInputTextInputLayout"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/formTextInputTextInputLayout"
|
||||
app:tint="?vctr_content_secondary" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -163,5 +163,36 @@
|
|||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="16dp"
|
||||
android:baselineAligned="false"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="visible"
|
||||
android:weightSum="3">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/attachmentPollButton"
|
||||
style="@style/AttachmentTypeSelectorButton"
|
||||
android:contentDescription="@string/attachment_type_poll"
|
||||
android:src="@drawable/ic_attachment_poll_white_24dp"
|
||||
tools:background="?colorPrimary" />
|
||||
|
||||
<TextView
|
||||
style="@style/AttachmentTypeSelectorLabel"
|
||||
android:importantForAccessibility="no"
|
||||
android:text="@string/attachment_type_poll" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
|
|
|
@ -2435,6 +2435,7 @@
|
|||
<string name="attachment_type_audio">"Audio"</string>
|
||||
<string name="attachment_type_gallery">"Gallery"</string>
|
||||
<string name="attachment_type_sticker">"Sticker"</string>
|
||||
<string name="attachment_type_poll">Poll</string>
|
||||
<string name="rotate_and_crop_screen_title">Rotate and crop</string>
|
||||
<string name="error_handling_incoming_share">Couldn\'t handle share data</string>
|
||||
|
||||
|
@ -3630,4 +3631,18 @@
|
|||
<string name="link_this_email_settings_link">Link this email with your account</string>
|
||||
<!-- %s will be replaced by the value of link_this_email_settings_link and styled as a link -->
|
||||
<string name="link_this_email_with_your_account">%s in Settings to receive invites directly in Element.</string>
|
||||
|
||||
<!-- Poll -->
|
||||
<string name="create_poll_title">Create Poll</string>
|
||||
<string name="create_poll_question_title">Poll question or topic</string>
|
||||
<string name="create_poll_question_hint">Question or topic</string>
|
||||
<string name="create_poll_options_title">Create options</string>
|
||||
<string name="create_poll_options_hint">Option %1$d</string>
|
||||
<string name="create_poll_add_option">ADD OPTION</string>
|
||||
<string name="create_poll_button">CREATE POLL</string>
|
||||
<string name="create_poll_empty_question_error">Question cannot be empty</string>
|
||||
<plurals name="create_poll_not_enough_options_error">
|
||||
<item quantity="one">At least %1$s option is required</item>
|
||||
<item quantity="other">At least %1$s options are required</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
|
|
Loading…
Reference in New Issue