Merge pull request #4360 from vector-im/feature/ons/poll
Poll Feature - Create
This commit is contained in:
commit
35e2a1083b
|
@ -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>
|
<item name="android:textColor">?vctr_message_text_color</item>
|
||||||
</style>
|
</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>
|
</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
|
// Relation Events
|
||||||
const val REACTION = "m.reaction"
|
const val REACTION = "m.reaction"
|
||||||
|
|
||||||
|
// Poll
|
||||||
|
const val POLL_START = "org.matrix.msc3381.poll.start"
|
||||||
|
|
||||||
// Unwedging
|
// Unwedging
|
||||||
internal const val DUMMY = "m.dummy"
|
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
|
||||||
|
)
|
|
@ -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.Content
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
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.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.session.room.timeline.TimelineEvent
|
||||||
import org.matrix.android.sdk.api.util.Cancelable
|
import org.matrix.android.sdk.api.util.Cancelable
|
||||||
|
|
||||||
|
@ -84,10 +83,10 @@ interface SendService {
|
||||||
/**
|
/**
|
||||||
* Send a poll to the room.
|
* Send a poll to the room.
|
||||||
* @param question the question
|
* @param question the question
|
||||||
* @param options list of (label, value)
|
* @param options list of options
|
||||||
* @return a [Cancelable]
|
* @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.
|
* Method to send a poll response.
|
||||||
|
|
|
@ -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.MessageImageContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent
|
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.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.model.message.getFileUrl
|
||||||
import org.matrix.android.sdk.api.session.room.send.SendService
|
import org.matrix.android.sdk.api.session.room.send.SendService
|
||||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||||
|
@ -98,7 +97,7 @@ internal class DefaultSendService @AssistedInject constructor(
|
||||||
.let { sendEvent(it) }
|
.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)
|
return localEchoEventFactory.createPollEvent(roomId, question, options)
|
||||||
.also { createLocalEcho(it) }
|
.also { createLocalEcho(it) }
|
||||||
.let { sendEvent(it) }
|
.let { sendEvent(it) }
|
||||||
|
|
|
@ -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.MessageFileContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageFormat
|
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.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.MessagePollResponseContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
|
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.MessageType
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent
|
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.PollAnswer
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.OptionItem
|
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.ThumbnailInfo
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.VideoInfo
|
import org.matrix.android.sdk.api.session.room.model.message.VideoInfo
|
||||||
import org.matrix.android.sdk.api.session.room.model.relation.ReactionContent
|
import org.matrix.android.sdk.api.session.room.model.relation.ReactionContent
|
||||||
|
@ -138,24 +139,29 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
|
|
||||||
fun createPollEvent(roomId: String,
|
fun createPollEvent(roomId: String,
|
||||||
question: String,
|
question: String,
|
||||||
options: List<OptionItem>): Event {
|
options: List<String>): Event {
|
||||||
val compatLabel = buildString {
|
val content = MessagePollContent(
|
||||||
append("[Poll] ")
|
pollCreationInfo = PollCreationInfo(
|
||||||
append(question)
|
question = PollQuestion(
|
||||||
options.forEach {
|
question = question
|
||||||
append("\n")
|
),
|
||||||
append(it.value)
|
answers = options.mapIndexed { index, option ->
|
||||||
|
PollAnswer(
|
||||||
|
id = index.toString(),
|
||||||
|
answer = option
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return createMessageEvent(
|
|
||||||
roomId,
|
|
||||||
MessageOptionsContent(
|
|
||||||
body = compatLabel,
|
|
||||||
label = question,
|
|
||||||
optionType = OPTION_TYPE_POLL,
|
|
||||||
options = options.toList()
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
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,
|
fun createReplaceTextOfReply(roomId: String,
|
||||||
|
|
|
@ -339,6 +339,7 @@
|
||||||
<activity android:name=".features.spaces.manage.SpaceManageActivity" />
|
<activity android:name=".features.spaces.manage.SpaceManageActivity" />
|
||||||
<activity android:name=".features.spaces.people.SpacePeopleActivity" />
|
<activity android:name=".features.spaces.people.SpacePeopleActivity" />
|
||||||
<activity android:name=".features.spaces.leave.SpaceLeaveAdvancedActivity" />
|
<activity android:name=".features.spaces.leave.SpaceLeaveAdvancedActivity" />
|
||||||
|
<activity android:name=".features.poll.create.CreatePollActivity" />
|
||||||
|
|
||||||
<!-- Services -->
|
<!-- Services -->
|
||||||
|
|
||||||
|
|
|
@ -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.MatrixToRoomSpaceFragment
|
||||||
import im.vector.app.features.matrixto.MatrixToUserFragment
|
import im.vector.app.features.matrixto.MatrixToUserFragment
|
||||||
import im.vector.app.features.pin.PinFragment
|
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.qrcode.QrCodeScannerFragment
|
||||||
import im.vector.app.features.reactions.EmojiChooserFragment
|
import im.vector.app.features.reactions.EmojiChooserFragment
|
||||||
import im.vector.app.features.reactions.EmojiSearchResultFragment
|
import im.vector.app.features.reactions.EmojiSearchResultFragment
|
||||||
|
@ -837,4 +838,9 @@ interface FragmentModule {
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@FragmentKey(SpaceLeaveAdvancedFragment::class)
|
@FragmentKey(SpaceLeaveAdvancedFragment::class)
|
||||||
fun bindSpaceLeaveAdvancedFragment(fragment: SpaceLeaveAdvancedFragment): Fragment
|
fun bindSpaceLeaveAdvancedFragment(fragment: SpaceLeaveAdvancedFragment): Fragment
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@FragmentKey(CreatePollFragment::class)
|
||||||
|
fun bindCreatePollFragment(fragment: CreatePollFragment): Fragment
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,6 +54,7 @@ import im.vector.app.features.login.LoginViewModel
|
||||||
import im.vector.app.features.login2.LoginViewModel2
|
import im.vector.app.features.login2.LoginViewModel2
|
||||||
import im.vector.app.features.login2.created.AccountCreatedViewModel
|
import im.vector.app.features.login2.created.AccountCreatedViewModel
|
||||||
import im.vector.app.features.matrixto.MatrixToBottomSheetViewModel
|
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.rageshake.BugReportViewModel
|
||||||
import im.vector.app.features.reactions.EmojiSearchResultViewModel
|
import im.vector.app.features.reactions.EmojiSearchResultViewModel
|
||||||
import im.vector.app.features.room.RequireActiveMembershipViewModel
|
import im.vector.app.features.room.RequireActiveMembershipViewModel
|
||||||
|
@ -546,4 +547,9 @@ interface MavericksViewModelModule {
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@MavericksViewModelKey(VerificationBottomSheetViewModel::class)
|
@MavericksViewModelKey(VerificationBottomSheetViewModel::class)
|
||||||
fun verificationBottomSheetViewModelFactory(factory: VerificationBottomSheetViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
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
|
package im.vector.app.core.ui.list
|
||||||
|
|
||||||
|
import android.graphics.Typeface
|
||||||
|
import android.view.Gravity
|
||||||
import androidx.annotation.ColorInt
|
import androidx.annotation.ColorInt
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import com.airbnb.epoxy.EpoxyAttribute
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
|
@ -47,6 +49,12 @@ abstract class GenericButtonItem : VectorEpoxyModel<GenericButtonItem.Holder>()
|
||||||
@DrawableRes
|
@DrawableRes
|
||||||
var iconRes: Int? = null
|
var iconRes: Int? = null
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var gravity: Int = Gravity.CENTER
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var bold: Boolean = false
|
||||||
|
|
||||||
override fun bind(holder: Holder) {
|
override fun bind(holder: Holder) {
|
||||||
super.bind(holder)
|
super.bind(holder)
|
||||||
holder.button.text = text
|
holder.button.text = text
|
||||||
|
@ -58,6 +66,10 @@ abstract class GenericButtonItem : VectorEpoxyModel<GenericButtonItem.Holder>()
|
||||||
holder.button.icon = null
|
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)
|
holder.button.onClick(buttonClickAction)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -75,6 +75,7 @@ class AttachmentTypeSelectorView(context: Context,
|
||||||
views.attachmentStickersButton.configure(Type.STICKER)
|
views.attachmentStickersButton.configure(Type.STICKER)
|
||||||
views.attachmentAudioButton.configure(Type.AUDIO)
|
views.attachmentAudioButton.configure(Type.AUDIO)
|
||||||
views.attachmentContactButton.configure(Type.CONTACT)
|
views.attachmentContactButton.configure(Type.CONTACT)
|
||||||
|
views.attachmentPollButton.configure(Type.POLL)
|
||||||
width = LinearLayout.LayoutParams.MATCH_PARENT
|
width = LinearLayout.LayoutParams.MATCH_PARENT
|
||||||
height = LinearLayout.LayoutParams.WRAP_CONTENT
|
height = LinearLayout.LayoutParams.WRAP_CONTENT
|
||||||
animationStyle = 0
|
animationStyle = 0
|
||||||
|
@ -108,6 +109,7 @@ class AttachmentTypeSelectorView(context: Context,
|
||||||
animateButtonIn(views.attachmentAudioButton, 0)
|
animateButtonIn(views.attachmentAudioButton, 0)
|
||||||
animateButtonIn(views.attachmentContactButton, ANIMATION_DURATION / 4)
|
animateButtonIn(views.attachmentContactButton, ANIMATION_DURATION / 4)
|
||||||
animateButtonIn(views.attachmentStickersButton, ANIMATION_DURATION / 2)
|
animateButtonIn(views.attachmentStickersButton, ANIMATION_DURATION / 2)
|
||||||
|
animateButtonIn(views.attachmentPollButton, ANIMATION_DURATION / 4)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun dismiss() {
|
override fun dismiss() {
|
||||||
|
@ -212,6 +214,7 @@ class AttachmentTypeSelectorView(context: Context,
|
||||||
FILE(PERMISSIONS_EMPTY),
|
FILE(PERMISSIONS_EMPTY),
|
||||||
STICKER(PERMISSIONS_EMPTY),
|
STICKER(PERMISSIONS_EMPTY),
|
||||||
AUDIO(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),
|
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),
|
CLEAR_SCALAR_TOKEN("/clear_scalar_token", "", R.string.command_description_clear_scalar_token, false),
|
||||||
SPOILER("/spoiler", "<message>", R.string.command_description_spoiler, 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),
|
SHRUG("/shrug", "<message>", R.string.command_description_shrug, false),
|
||||||
LENNY("/lenny", "<message>", R.string.command_description_lenny, false),
|
LENNY("/lenny", "<message>", R.string.command_description_lenny, false),
|
||||||
PLAIN("/plain", "<message>", R.string.command_description_plain, false),
|
PLAIN("/plain", "<message>", R.string.command_description_plain, false),
|
||||||
|
|
|
@ -345,15 +345,6 @@ object CommandParser {
|
||||||
|
|
||||||
ParsedCommand.SendLenny(message)
|
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 -> {
|
Command.DISCARD_SESSION.command -> {
|
||||||
ParsedCommand.DiscardSession
|
ParsedCommand.DiscardSession
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,7 +61,6 @@ sealed class ParsedCommand {
|
||||||
class SendSpoiler(val message: String) : ParsedCommand()
|
class SendSpoiler(val message: String) : ParsedCommand()
|
||||||
class SendShrug(val message: CharSequence) : ParsedCommand()
|
class SendShrug(val message: CharSequence) : ParsedCommand()
|
||||||
class SendLenny(val message: CharSequence) : ParsedCommand()
|
class SendLenny(val message: CharSequence) : ParsedCommand()
|
||||||
class SendPoll(val question: String, val options: List<String>) : ParsedCommand()
|
|
||||||
object DiscardSession : ParsedCommand()
|
object DiscardSession : ParsedCommand()
|
||||||
class ShowUser(val userId: String) : ParsedCommand()
|
class ShowUser(val userId: String) : ParsedCommand()
|
||||||
class SendChatEffect(val chatEffect: ChatEffect, val message: String) : ParsedCommand()
|
class SendChatEffect(val chatEffect: ChatEffect, val message: String) : ParsedCommand()
|
||||||
|
|
|
@ -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<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
|
||||||
|
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<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.AUDIO -> attachmentsHelper.selectAudio(attachmentAudioActivityResultLauncher)
|
||||||
AttachmentTypeSelectorView.Type.CONTACT -> attachmentsHelper.selectContact(attachmentContactActivityResultLauncher)
|
AttachmentTypeSelectorView.Type.CONTACT -> attachmentsHelper.selectContact(attachmentContactActivityResultLauncher)
|
||||||
AttachmentTypeSelectorView.Type.STICKER -> roomDetailViewModel.handle(RoomDetailAction.SelectStickerAttachment)
|
AttachmentTypeSelectorView.Type.STICKER -> roomDetailViewModel.handle(RoomDetailAction.SelectStickerAttachment)
|
||||||
|
AttachmentTypeSelectorView.Type.POLL -> navigator.openCreatePoll(requireContext(), roomDetailArgs.roomId)
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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.RoomAvatarContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
|
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.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.powerlevels.PowerLevelsHelper
|
||||||
import org.matrix.android.sdk.api.session.room.send.UserDraft
|
import org.matrix.android.sdk.api.session.room.send.UserDraft
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
|
import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
|
||||||
|
@ -259,11 +258,6 @@ class TextComposerViewModel @AssistedInject constructor(
|
||||||
_viewEvents.post(TextComposerViewEvents.SlashCommandResultOk())
|
_viewEvents.post(TextComposerViewEvents.SlashCommandResultOk())
|
||||||
popDraft()
|
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 -> {
|
is ParsedCommand.ChangeTopic -> {
|
||||||
handleChangeTopicSlashCommand(slashCommandResult)
|
handleChangeTopicSlashCommand(slashCommandResult)
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,6 +64,8 @@ import im.vector.app.features.media.VectorAttachmentViewerActivity
|
||||||
import im.vector.app.features.pin.PinActivity
|
import im.vector.app.features.pin.PinActivity
|
||||||
import im.vector.app.features.pin.PinArgs
|
import im.vector.app.features.pin.PinArgs
|
||||||
import im.vector.app.features.pin.PinMode
|
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.RoomDirectoryActivity
|
||||||
import im.vector.app.features.roomdirectory.RoomDirectoryData
|
import im.vector.app.features.roomdirectory.RoomDirectoryData
|
||||||
import im.vector.app.features.roomdirectory.createroom.CreateRoomActivity
|
import im.vector.app.features.roomdirectory.createroom.CreateRoomActivity
|
||||||
|
@ -498,6 +500,14 @@ class DefaultNavigator @Inject constructor(
|
||||||
context.startActivity(intent)
|
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) {
|
private fun startActivity(context: Context, intent: Intent, buildTask: Boolean) {
|
||||||
if (buildTask) {
|
if (buildTask) {
|
||||||
val stackBuilder = TaskStackBuilder.create(context)
|
val stackBuilder = TaskStackBuilder.create(context)
|
||||||
|
|
|
@ -140,4 +140,6 @@ interface Navigator {
|
||||||
fun openDevTools(context: Context, roomId: String)
|
fun openDevTools(context: Context, roomId: String)
|
||||||
|
|
||||||
fun openCallTransfer(context: Context, callId: 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 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<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, 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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,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<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
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/formTextInputTextInputLayout"
|
android:id="@+id/formTextInputTextInputLayout"
|
||||||
|
style="@style/Widget.Vector.EditText.Form"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="@dimen/layout_horizontal_margin"
|
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>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
android:baselineAligned="false"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:weightSum="3">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
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>
|
</LinearLayout>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
|
@ -2438,6 +2438,7 @@
|
||||||
<string name="attachment_type_audio">"Audio"</string>
|
<string name="attachment_type_audio">"Audio"</string>
|
||||||
<string name="attachment_type_gallery">"Gallery"</string>
|
<string name="attachment_type_gallery">"Gallery"</string>
|
||||||
<string name="attachment_type_sticker">"Sticker"</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="rotate_and_crop_screen_title">Rotate and crop</string>
|
||||||
<string name="error_handling_incoming_share">Couldn\'t handle share data</string>
|
<string name="error_handling_incoming_share">Couldn\'t handle share data</string>
|
||||||
|
|
||||||
|
@ -3633,4 +3634,18 @@
|
||||||
<string name="link_this_email_settings_link">Link this email with your account</string>
|
<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 -->
|
<!-- %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>
|
<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>
|
</resources>
|
||||||
|
|
Loading…
Reference in New Issue