mirror of
https://github.com/element-hq/element-android
synced 2024-11-24 02:15:35 +03:00
Widget: handle sticker
This commit is contained in:
parent
dbe4c0c8e4
commit
4b37ede8c2
34 changed files with 384 additions and 277 deletions
|
@ -18,9 +18,28 @@ package im.vector.matrix.android.api.session.integrationmanager
|
|||
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
import im.vector.matrix.android.internal.session.integrationmanager.IntegrationManager
|
||||
|
||||
interface IntegrationManagerService {
|
||||
|
||||
interface Listener {
|
||||
fun onIsEnabledChanged(enabled: Boolean) {
|
||||
//No-op
|
||||
}
|
||||
|
||||
fun onConfigurationChanged(config: IntegrationManagerConfig) {
|
||||
//No-op
|
||||
}
|
||||
|
||||
fun onWidgetPermissionsChanged(widgets: Map<String, Boolean>) {
|
||||
//No-op
|
||||
}
|
||||
}
|
||||
|
||||
fun addListener(listener: Listener)
|
||||
|
||||
fun removeListener(listener: Listener)
|
||||
|
||||
fun getOrderedConfigs(): List<IntegrationManagerConfig>
|
||||
|
||||
fun getPreferredConfig(): IntegrationManagerConfig
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package im.vector.matrix.android.api.session.room.send
|
||||
|
||||
import im.vector.matrix.android.api.session.content.ContentAttachmentData
|
||||
import im.vector.matrix.android.api.session.events.model.Content
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
||||
import im.vector.matrix.android.api.session.room.model.message.OptionItem
|
||||
|
@ -28,6 +29,14 @@ import im.vector.matrix.android.api.util.Cancelable
|
|||
*/
|
||||
interface SendService {
|
||||
|
||||
/**
|
||||
* Method to send a generic event asynchronously. If you want to send a state event, please use [StateService] instead.
|
||||
* @param eventType the type of the event
|
||||
* @param content the optional body as a json dict.
|
||||
* @return a [Cancelable]
|
||||
*/
|
||||
fun sendEvent(eventType: String, content: Content?): Cancelable
|
||||
|
||||
/**
|
||||
* Method to send a text message asynchronously.
|
||||
* The text to send can be a Spannable and contains special spans (MatrixItemSpan) that will be translated
|
||||
|
|
|
@ -24,6 +24,14 @@ import javax.inject.Inject
|
|||
|
||||
internal class DefaultIntegrationManagerService @Inject constructor(private val integrationManager: IntegrationManager) : IntegrationManagerService {
|
||||
|
||||
override fun addListener(listener: IntegrationManagerService.Listener) {
|
||||
integrationManager.addListener(listener)
|
||||
}
|
||||
|
||||
override fun removeListener(listener: IntegrationManagerService.Listener) {
|
||||
integrationManager.removeListener(listener)
|
||||
}
|
||||
|
||||
override fun getOrderedConfigs(): List<IntegrationManagerConfig> {
|
||||
return integrationManager.getOrderedConfigs()
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import im.vector.matrix.android.R
|
|||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.integrationmanager.IntegrationManagerConfig
|
||||
import im.vector.matrix.android.api.session.integrationmanager.IntegrationManagerService
|
||||
import im.vector.matrix.android.api.session.widgets.model.WidgetContent
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
import im.vector.matrix.android.api.util.NoOpCancellable
|
||||
|
@ -59,27 +60,14 @@ internal class IntegrationManager @Inject constructor(private val taskExecutor:
|
|||
private val accountDataDataSource: AccountDataDataSource,
|
||||
private val configExtractor: IntegrationManagerConfigExtractor) {
|
||||
|
||||
interface Listener {
|
||||
fun onIsEnabledChanged(enabled: Boolean) {
|
||||
//No-op
|
||||
}
|
||||
|
||||
fun onConfigurationChanged(config: IntegrationManagerConfig) {
|
||||
//No-op
|
||||
}
|
||||
|
||||
fun onWidgetPermissionsChanged(widgets: Map<String, Boolean>) {
|
||||
//No-op
|
||||
}
|
||||
}
|
||||
|
||||
private val currentConfigs = ArrayList<IntegrationManagerConfig>()
|
||||
private val lifecycleOwner: LifecycleOwner = LifecycleOwner { lifecycleRegistry }
|
||||
private val lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(lifecycleOwner)
|
||||
|
||||
private val listeners = HashSet<Listener>()
|
||||
fun addListener(listener: Listener) = synchronized(listeners) { listeners.add(listener) }
|
||||
fun removeListener(listener: Listener) = synchronized(listeners) { listeners.remove(listener) }
|
||||
private val listeners = HashSet<IntegrationManagerService.Listener>()
|
||||
fun addListener(listener: IntegrationManagerService.Listener) = synchronized(listeners) { listeners.add(listener) }
|
||||
fun removeListener(listener: IntegrationManagerService.Listener) = synchronized(listeners) { listeners.remove(listener) }
|
||||
|
||||
init {
|
||||
val defaultConfig = IntegrationManagerConfig(
|
||||
|
|
|
@ -33,6 +33,7 @@ import im.vector.matrix.android.api.session.room.send.SendState
|
|||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
import im.vector.matrix.android.api.util.CancelableBag
|
||||
import im.vector.matrix.android.api.util.JsonDict
|
||||
import im.vector.matrix.android.internal.di.SessionId
|
||||
import im.vector.matrix.android.internal.di.WorkManagerProvider
|
||||
import im.vector.matrix.android.internal.session.content.UploadContentWorker
|
||||
|
@ -67,6 +68,12 @@ internal class DefaultSendService @AssistedInject constructor(
|
|||
|
||||
private val workerFutureListenerExecutor = Executors.newSingleThreadExecutor()
|
||||
|
||||
override fun sendEvent(eventType: String, content: JsonDict?): Cancelable {
|
||||
return localEchoEventFactory.createEvent(roomId, eventType, content)
|
||||
.also { createLocalEcho(it) }
|
||||
.let { sendEvent(it) }
|
||||
}
|
||||
|
||||
override fun sendTextMessage(text: CharSequence, msgType: String, autoMarkdown: Boolean): Cancelable {
|
||||
return localEchoEventFactory.createTextEvent(roomId, msgType, text, autoMarkdown)
|
||||
.also { createLocalEcho(it) }
|
||||
|
|
|
@ -23,6 +23,7 @@ import androidx.exifinterface.media.ExifInterface
|
|||
import im.vector.matrix.android.R
|
||||
import im.vector.matrix.android.api.permalinks.PermalinkFactory
|
||||
import im.vector.matrix.android.api.session.content.ContentAttachmentData
|
||||
import im.vector.matrix.android.api.session.events.model.Content
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.LocalEcho
|
||||
|
@ -56,6 +57,7 @@ import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultC
|
|||
import im.vector.matrix.android.api.session.room.model.relation.ReplyToContent
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent
|
||||
import im.vector.matrix.android.api.util.JsonDict
|
||||
import im.vector.matrix.android.internal.di.UserId
|
||||
import im.vector.matrix.android.internal.extensions.subStringBetween
|
||||
import im.vector.matrix.android.internal.session.content.ThumbnailExtractor
|
||||
|
@ -95,7 +97,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
return createFormattedTextEvent(roomId, createTextContent(text, autoMarkdown), msgType)
|
||||
}
|
||||
val content = MessageTextContent(msgType = msgType, body = text.toString())
|
||||
return createEvent(roomId, content)
|
||||
return createMessageEvent(roomId, content)
|
||||
}
|
||||
|
||||
private fun createTextContent(text: CharSequence, autoMarkdown: Boolean): TextContent {
|
||||
|
@ -129,7 +131,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
text != htmlText && htmlText != "<p>${text.trim()}</p>\n"
|
||||
|
||||
fun createFormattedTextEvent(roomId: String, textContent: TextContent, msgType: String): Event {
|
||||
return createEvent(roomId, textContent.toMessageTextContent(msgType))
|
||||
return createMessageEvent(roomId, textContent.toMessageTextContent(msgType))
|
||||
}
|
||||
|
||||
fun createReplaceTextEvent(roomId: String,
|
||||
|
@ -138,7 +140,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
newBodyAutoMarkdown: Boolean,
|
||||
msgType: String,
|
||||
compatibilityText: String): Event {
|
||||
return createEvent(roomId,
|
||||
return createMessageEvent(roomId,
|
||||
MessageTextContent(
|
||||
msgType = msgType,
|
||||
body = compatibilityText,
|
||||
|
@ -153,7 +155,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
pollEventId: String,
|
||||
optionIndex: Int,
|
||||
optionLabel: String): Event {
|
||||
return createEvent(roomId,
|
||||
return createMessageEvent(roomId,
|
||||
MessagePollResponseContent(
|
||||
body = optionLabel,
|
||||
relatesTo = RelationDefaultContent(
|
||||
|
@ -175,7 +177,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
append(it.value)
|
||||
}
|
||||
}
|
||||
return createEvent(
|
||||
return createMessageEvent(
|
||||
roomId,
|
||||
MessageOptionsContent(
|
||||
body = compatLabel,
|
||||
|
@ -211,7 +213,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
//
|
||||
val replyFallback = buildReplyFallback(body, originalEvent.root.senderId ?: "", newBodyText)
|
||||
|
||||
return createEvent(roomId,
|
||||
return createMessageEvent(roomId,
|
||||
MessageTextContent(
|
||||
msgType = msgType,
|
||||
body = compatibilityText,
|
||||
|
@ -280,7 +282,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
),
|
||||
url = attachment.queryUri.toString()
|
||||
)
|
||||
return createEvent(roomId, content)
|
||||
return createMessageEvent(roomId, content)
|
||||
}
|
||||
|
||||
private fun createVideoEvent(roomId: String, attachment: ContentAttachmentData): Event {
|
||||
|
@ -316,7 +318,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
),
|
||||
url = attachment.queryUri.toString()
|
||||
)
|
||||
return createEvent(roomId, content)
|
||||
return createMessageEvent(roomId, content)
|
||||
}
|
||||
|
||||
private fun createAudioEvent(roomId: String, attachment: ContentAttachmentData): Event {
|
||||
|
@ -329,7 +331,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
),
|
||||
url = attachment.queryUri.toString()
|
||||
)
|
||||
return createEvent(roomId, content)
|
||||
return createMessageEvent(roomId, content)
|
||||
}
|
||||
|
||||
private fun createFileEvent(roomId: String, attachment: ContentAttachmentData): Event {
|
||||
|
@ -342,18 +344,22 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
),
|
||||
url = attachment.queryUri.toString()
|
||||
)
|
||||
return createEvent(roomId, content)
|
||||
return createMessageEvent(roomId, content)
|
||||
}
|
||||
|
||||
private fun createEvent(roomId: String, content: Any? = null): Event {
|
||||
private fun createMessageEvent(roomId: String, content: Any? = null): Event {
|
||||
return createEvent(roomId, EventType.MESSAGE, content.toContent())
|
||||
}
|
||||
|
||||
fun createEvent(roomId: String, type: String, content: Content?): Event {
|
||||
val localId = LocalEcho.createLocalEchoId()
|
||||
return Event(
|
||||
roomId = roomId,
|
||||
originServerTs = dummyOriginServerTs(),
|
||||
senderId = userId,
|
||||
eventId = localId,
|
||||
type = EventType.MESSAGE,
|
||||
content = content.toContent(),
|
||||
type = type,
|
||||
content = content,
|
||||
unsignedData = UnsignedData(age = null, transactionId = localId)
|
||||
)
|
||||
}
|
||||
|
@ -410,7 +416,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
formattedBody = replyFormatted,
|
||||
relatesTo = RelationDefaultContent(null, null, ReplyToContent(eventId))
|
||||
)
|
||||
return createEvent(roomId, content)
|
||||
return createMessageEvent(roomId, content)
|
||||
}
|
||||
|
||||
private fun buildReplyFallback(body: TextContent, originalSenderId: String?, newBodyText: String): String {
|
||||
|
|
|
@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.session.widgets
|
|||
|
||||
import im.vector.matrix.android.R
|
||||
import im.vector.matrix.android.api.session.integrationmanager.IntegrationManagerConfig
|
||||
import im.vector.matrix.android.api.session.integrationmanager.IntegrationManagerService
|
||||
import im.vector.matrix.android.api.session.widgets.WidgetURLFormatter
|
||||
import im.vector.matrix.android.internal.session.SessionScope
|
||||
import im.vector.matrix.android.internal.session.integrationmanager.IntegrationManager
|
||||
|
@ -30,9 +31,9 @@ import javax.inject.Inject
|
|||
internal class DefaultWidgetURLFormatter @Inject constructor(private val integrationManager: IntegrationManager,
|
||||
private val getScalarTokenTask: GetScalarTokenTask,
|
||||
private val stringProvider: StringProvider
|
||||
) : IntegrationManager.Listener, WidgetURLFormatter {
|
||||
) : IntegrationManagerService.Listener, WidgetURLFormatter {
|
||||
|
||||
private var currentConfig = integrationManager.getPreferredConfig()
|
||||
private lateinit var currentConfig: IntegrationManagerConfig
|
||||
private var whiteListedUrls: List<String> = emptyList()
|
||||
|
||||
fun start() {
|
||||
|
@ -50,7 +51,7 @@ internal class DefaultWidgetURLFormatter @Inject constructor(private val integra
|
|||
|
||||
private fun setupWithConfiguration() {
|
||||
val preferredConfig = integrationManager.getPreferredConfig()
|
||||
if (currentConfig != preferredConfig) {
|
||||
if (!this::currentConfig.isInitialized || preferredConfig != currentConfig) {
|
||||
currentConfig = preferredConfig
|
||||
val defaultWhiteList = stringProvider.getStringArray(R.array.integrations_widgets_urls).asList()
|
||||
whiteListedUrls = when (preferredConfig.kind) {
|
||||
|
|
|
@ -21,6 +21,7 @@ import im.vector.matrix.android.api.session.widgets.model.WidgetContent
|
|||
|
||||
data class Widget(
|
||||
val widgetContent: WidgetContent,
|
||||
val event: Event? = null
|
||||
val event: Event? = null,
|
||||
val widgetId: String? = null
|
||||
)
|
||||
|
||||
|
|
|
@ -21,16 +21,16 @@ import javax.inject.Inject
|
|||
|
||||
internal class WidgetDependenciesHolder @Inject constructor(private val integrationManager: IntegrationManager,
|
||||
private val widgetManager: WidgetManager,
|
||||
private val widgetURLBuilder: DefaultWidgetURLFormatter) {
|
||||
private val widgetURLFormatter: DefaultWidgetURLFormatter) {
|
||||
|
||||
fun start() {
|
||||
integrationManager.start()
|
||||
widgetManager.start()
|
||||
widgetURLBuilder.start()
|
||||
widgetURLFormatter.start()
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
widgetURLBuilder.stop()
|
||||
widgetURLFormatter.stop()
|
||||
widgetManager.stop()
|
||||
integrationManager.stop()
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ import im.vector.matrix.android.api.session.events.model.Content
|
|||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.integrationmanager.IntegrationManagerService
|
||||
import im.vector.matrix.android.api.session.room.model.PowerLevelsContent
|
||||
import im.vector.matrix.android.api.session.room.powerlevels.PowerLevelsHelper
|
||||
import im.vector.matrix.android.api.session.widgets.WidgetService
|
||||
|
@ -51,7 +52,7 @@ internal class WidgetManager @Inject constructor(private val integrationManager:
|
|||
private val stateEventDataSource: StateEventDataSource,
|
||||
private val taskExecutor: TaskExecutor,
|
||||
private val createWidgetTask: CreateWidgetTask,
|
||||
@UserId private val userId: String) : IntegrationManager.Listener {
|
||||
@UserId private val userId: String) : IntegrationManagerService.Listener {
|
||||
|
||||
private val lifecycleOwner: LifecycleOwner = LifecycleOwner { lifecycleRegistry }
|
||||
private val lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(lifecycleOwner)
|
||||
|
@ -114,7 +115,7 @@ internal class WidgetManager @Inject constructor(private val integrationManager:
|
|||
}
|
||||
// widgetEvent.stateKey = widget id
|
||||
if (widgetEvent.stateKey != null && !widgets.containsKey(widgetEvent.stateKey)) {
|
||||
val widget = Widget(widgetContent, widgetEvent)
|
||||
val widget = Widget(widgetContent, widgetEvent, widgetEvent.stateKey)
|
||||
widgets[widgetEvent.stateKey] = widget
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ internal fun UserAccountDataEvent.extractWidgetSequence(): Sequence<Widget> {
|
|||
if (content == null) {
|
||||
null
|
||||
} else {
|
||||
Widget(content, event)
|
||||
Widget(content, event, event.stateKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -102,8 +102,7 @@ import im.vector.riotx.features.signout.soft.SoftLogoutFragment
|
|||
import im.vector.riotx.features.terms.ReviewTermsFragment
|
||||
import im.vector.riotx.features.userdirectory.KnownUsersFragment
|
||||
import im.vector.riotx.features.userdirectory.UserDirectoryFragment
|
||||
import im.vector.riotx.features.widgets.admin.AdminWidgetFragment
|
||||
import im.vector.riotx.features.widgets.room.RoomWidgetFragment
|
||||
import im.vector.riotx.features.widgets.WidgetFragment
|
||||
|
||||
@Module
|
||||
interface FragmentModule {
|
||||
|
@ -515,11 +514,7 @@ interface FragmentModule {
|
|||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FragmentKey(RoomWidgetFragment::class)
|
||||
fun bindRoomWidgetFragment(fragment: RoomWidgetFragment): Fragment
|
||||
@FragmentKey(WidgetFragment::class)
|
||||
fun bindWidgetFragment(fragment: WidgetFragment): Fragment
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FragmentKey(AdminWidgetFragment::class)
|
||||
fun bindAdminWidgetFragment(fragment: AdminWidgetFragment): Fragment
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package im.vector.riotx.features.attachments
|
||||
|
||||
import im.vector.matrix.android.api.session.content.ContentAttachmentData
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageStickerContent
|
||||
import im.vector.riotx.multipicker.entity.MultiPickerAudioType
|
||||
import im.vector.riotx.multipicker.entity.MultiPickerBaseType
|
||||
import im.vector.riotx.multipicker.entity.MultiPickerContactType
|
||||
|
|
|
@ -19,6 +19,7 @@ package im.vector.riotx.features.home.room.detail
|
|||
import im.vector.matrix.android.api.session.content.ContentAttachmentData
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageFileContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageStickerContent
|
||||
import im.vector.matrix.android.api.session.room.timeline.Timeline
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.riotx.core.platform.VectorViewModelAction
|
||||
|
@ -26,6 +27,7 @@ import im.vector.riotx.core.platform.VectorViewModelAction
|
|||
sealed class RoomDetailAction : VectorViewModelAction {
|
||||
data class UserIsTyping(val isTyping: Boolean) : RoomDetailAction()
|
||||
data class SaveDraft(val draft: String) : RoomDetailAction()
|
||||
data class SendSticker(val stickerContent: MessageStickerContent) : RoomDetailAction()
|
||||
data class SendMessage(val text: CharSequence, val autoMarkdown: Boolean) : RoomDetailAction()
|
||||
data class SendMedia(val attachments: List<ContentAttachmentData>, val compressBeforeSending: Boolean) : RoomDetailAction()
|
||||
data class TimelineEventTurnsVisible(val event: TimelineEvent) : RoomDetailAction()
|
||||
|
@ -72,4 +74,6 @@ sealed class RoomDetailAction : VectorViewModelAction {
|
|||
data class RequestVerification(val userId: String) : RoomDetailAction()
|
||||
data class ResumeVerification(val transactionId: String, val otherUserId: String?) : RoomDetailAction()
|
||||
data class ReRequestKeys(val eventId: String) : RoomDetailAction()
|
||||
|
||||
object SelectStickerAttachment : RoomDetailAction()
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ import android.os.Bundle
|
|||
import android.os.Parcelable
|
||||
import android.text.Spannable
|
||||
import android.view.HapticFeedbackConstants
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
|
@ -65,6 +66,7 @@ import im.vector.matrix.android.api.permalinks.PermalinkFactory
|
|||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.content.ContentAttachmentData
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.file.FileService
|
||||
import im.vector.matrix.android.api.session.room.model.Membership
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageAudioContent
|
||||
|
@ -72,6 +74,7 @@ import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
|||
import im.vector.matrix.android.api.session.room.model.message.MessageFileContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageFormat
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageImageInfoContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageStickerContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent
|
||||
|
@ -131,6 +134,7 @@ import im.vector.riotx.features.crypto.verification.VerificationBottomSheet
|
|||
import im.vector.riotx.features.home.AvatarRenderer
|
||||
import im.vector.riotx.features.home.room.detail.composer.TextComposerView
|
||||
import im.vector.riotx.features.home.room.detail.readreceipts.DisplayReadReceiptsBottomSheet
|
||||
import im.vector.riotx.features.home.room.detail.sticker.StickerPickerConstants
|
||||
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
|
||||
import im.vector.riotx.features.home.room.detail.timeline.action.EventSharedAction
|
||||
import im.vector.riotx.features.home.room.detail.timeline.action.MessageActionsBottomSheet
|
||||
|
@ -155,6 +159,7 @@ import im.vector.riotx.features.reactions.EmojiReactionPickerActivity
|
|||
import im.vector.riotx.features.settings.VectorPreferences
|
||||
import im.vector.riotx.features.share.SharedData
|
||||
import im.vector.riotx.features.themes.ThemeUtils
|
||||
import im.vector.riotx.features.widgets.WidgetActivity
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
|
@ -300,10 +305,37 @@ class RoomDetailFragment @Inject constructor(
|
|||
is RoomDetailViewEvents.DownloadFileState -> handleDownloadFileState(it)
|
||||
is RoomDetailViewEvents.JoinRoomCommandSuccess -> handleJoinedToAnotherRoom(it)
|
||||
is RoomDetailViewEvents.SendMessageResult -> renderSendMessageResult(it)
|
||||
RoomDetailViewEvents.DisplayPromptForIntegrationManager -> displayPromptForIntegrationManager()
|
||||
is RoomDetailViewEvents.OpenStickerPicker -> openStickerPicker(it)
|
||||
}.exhaustive
|
||||
}
|
||||
}
|
||||
|
||||
private fun openStickerPicker(event: RoomDetailViewEvents.OpenStickerPicker) {
|
||||
navigator.openStickerPicker(this, roomDetailArgs.roomId, event.widget)
|
||||
}
|
||||
|
||||
private fun displayPromptForIntegrationManager() {
|
||||
// The Sticker picker widget is not installed yet. Propose the user to install it
|
||||
val builder = AlertDialog.Builder(requireContext())
|
||||
// Use the builder context
|
||||
// Use the builder context
|
||||
val v: View = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_no_sticker_pack, null)
|
||||
builder
|
||||
.setView(v)
|
||||
.setPositiveButton(R.string.yes) { _, _->
|
||||
// Open integration manager, to the sticker installation page
|
||||
navigator.openIntegrationManager(
|
||||
context = requireContext(),
|
||||
roomId = roomDetailArgs.roomId,
|
||||
integId = null,
|
||||
screenId = "type_${StickerPickerConstants.WIDGET_NAME}"
|
||||
)
|
||||
}
|
||||
.setNegativeButton(R.string.no, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun handleJoinedToAnotherRoom(action: RoomDetailViewEvents.JoinRoomCommandSuccess) {
|
||||
updateComposerText("")
|
||||
lockSendButton = false
|
||||
|
@ -530,6 +562,10 @@ class RoomDetailFragment @Inject constructor(
|
|||
val (eventId, reaction) = EmojiReactionPickerActivity.getOutput(data) ?: return
|
||||
roomDetailViewModel.handle(RoomDetailAction.SendReaction(eventId, reaction))
|
||||
}
|
||||
StickerPickerConstants.STICKER_PICKER_REQUEST_CODE -> {
|
||||
val content = WidgetActivity.getOutput(data).toModel<MessageStickerContent>() ?: return
|
||||
roomDetailViewModel.handle(RoomDetailAction.SendSticker(content))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1386,7 +1422,7 @@ class RoomDetailFragment @Inject constructor(
|
|||
AttachmentTypeSelectorView.Type.GALLERY -> attachmentsHelper.selectGallery(this)
|
||||
AttachmentTypeSelectorView.Type.AUDIO -> attachmentsHelper.selectAudio(this)
|
||||
AttachmentTypeSelectorView.Type.CONTACT -> attachmentsHelper.selectContact(this)
|
||||
AttachmentTypeSelectorView.Type.STICKER -> vectorBaseActivity.notImplemented("Adding stickers")
|
||||
AttachmentTypeSelectorView.Type.STICKER -> roomDetailViewModel.handle(RoomDetailAction.SelectStickerAttachment)
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package im.vector.riotx.features.home.room.detail
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import im.vector.matrix.android.internal.session.widgets.Widget
|
||||
import im.vector.riotx.core.platform.VectorViewEvents
|
||||
import im.vector.riotx.features.command.Command
|
||||
import java.io.File
|
||||
|
@ -49,6 +50,10 @@ sealed class RoomDetailViewEvents : VectorViewEvents {
|
|||
|
||||
abstract class SendMessageResult : RoomDetailViewEvents()
|
||||
|
||||
object DisplayPromptForIntegrationManager: RoomDetailViewEvents()
|
||||
|
||||
data class OpenStickerPicker(val widget: Widget): RoomDetailViewEvents()
|
||||
|
||||
object MessageSent : SendMessageResult()
|
||||
data class JoinRoomCommandSuccess(val roomId: String) : SendMessageResult()
|
||||
class SlashCommandError(val command: Command) : SendMessageResult()
|
||||
|
|
|
@ -18,6 +18,7 @@ package im.vector.riotx.features.home.room.detail
|
|||
|
||||
import android.net.Uri
|
||||
import androidx.annotation.IdRes
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.airbnb.mvrx.FragmentViewModelContext
|
||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||
import com.airbnb.mvrx.Success
|
||||
|
@ -34,6 +35,7 @@ import im.vector.matrix.android.api.session.Session
|
|||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.isImageMessage
|
||||
import im.vector.matrix.android.api.session.events.model.isTextMessage
|
||||
import im.vector.matrix.android.api.session.events.model.toContent
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.file.FileService
|
||||
import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilities
|
||||
|
@ -67,6 +69,7 @@ import im.vector.riotx.features.command.CommandParser
|
|||
import im.vector.riotx.features.command.ParsedCommand
|
||||
import im.vector.riotx.features.crypto.verification.SupportedVerificationMethodsProvider
|
||||
import im.vector.riotx.features.home.room.detail.composer.rainbow.RainbowGenerator
|
||||
import im.vector.riotx.features.home.room.detail.sticker.StickerPickerActionHandler
|
||||
import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineDisplayableEvents
|
||||
import im.vector.riotx.features.home.room.typing.TypingHelper
|
||||
import im.vector.riotx.features.settings.VectorPreferences
|
||||
|
@ -74,6 +77,7 @@ import io.reactivex.Observable
|
|||
import io.reactivex.functions.BiFunction
|
||||
import io.reactivex.rxkotlin.subscribeBy
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.commonmark.parser.Parser
|
||||
import org.commonmark.renderer.html.HtmlRenderer
|
||||
import timber.log.Timber
|
||||
|
@ -89,7 +93,8 @@ class RoomDetailViewModel @AssistedInject constructor(
|
|||
private val typingHelper: TypingHelper,
|
||||
private val rainbowGenerator: RainbowGenerator,
|
||||
private val session: Session,
|
||||
private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider
|
||||
private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider,
|
||||
private val stickerPickerActionHandler: StickerPickerActionHandler
|
||||
) : VectorViewModel<RoomDetailViewState, RoomDetailAction, RoomDetailViewEvents>(initialState), Timeline.Listener {
|
||||
|
||||
private val room = session.getRoom(initialState.roomId)!!
|
||||
|
@ -183,6 +188,7 @@ class RoomDetailViewModel @AssistedInject constructor(
|
|||
is RoomDetailAction.SaveDraft -> handleSaveDraft(action)
|
||||
is RoomDetailAction.SendMessage -> handleSendMessage(action)
|
||||
is RoomDetailAction.SendMedia -> handleSendMedia(action)
|
||||
is RoomDetailAction.SendSticker -> handleSendSticker(action)
|
||||
is RoomDetailAction.TimelineEventTurnsVisible -> handleEventVisible(action)
|
||||
is RoomDetailAction.TimelineEventTurnsInvisible -> handleEventInvisible(action)
|
||||
is RoomDetailAction.LoadMoreTimelineEvents -> handleLoadMore(action)
|
||||
|
@ -214,6 +220,18 @@ class RoomDetailViewModel @AssistedInject constructor(
|
|||
is RoomDetailAction.RequestVerification -> handleRequestVerification(action)
|
||||
is RoomDetailAction.ResumeVerification -> handleResumeRequestVerification(action)
|
||||
is RoomDetailAction.ReRequestKeys -> handleReRequestKeys(action)
|
||||
is RoomDetailAction.SelectStickerAttachment -> handleSelectStickerAttachment()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSendSticker(action: RoomDetailAction.SendSticker) {
|
||||
room.sendEvent(EventType.STICKER, action.stickerContent.toContent())
|
||||
}
|
||||
|
||||
private fun handleSelectStickerAttachment() {
|
||||
viewModelScope.launch {
|
||||
val viewEvent = stickerPickerActionHandler.handle()
|
||||
_viewEvents.post(viewEvent)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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.riotx.features.home.room.detail.sticker
|
||||
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.riotx.features.home.room.detail.RoomDetailViewEvents
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
|
||||
class StickerPickerActionHandler @Inject constructor(private val session: Session) {
|
||||
|
||||
suspend fun handle(): RoomDetailViewEvents = withContext(Dispatchers.Default) {
|
||||
// Search for the sticker picker widget in the user account
|
||||
val stickerWidget = session.widgetService().getUserWidgets(setOf(StickerPickerConstants.WIDGET_NAME)).firstOrNull()
|
||||
if (stickerWidget == null || stickerWidget.widgetContent.url.isNullOrBlank()) {
|
||||
RoomDetailViewEvents.DisplayPromptForIntegrationManager
|
||||
} else {
|
||||
RoomDetailViewEvents.OpenStickerPicker(
|
||||
widget = stickerWidget
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,8 +14,9 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotx.features.widgets.admin
|
||||
package im.vector.riotx.features.home.room.detail.sticker
|
||||
|
||||
import im.vector.riotx.core.platform.VectorViewModelAction
|
||||
|
||||
sealed class AdminWidgetAction : VectorViewModelAction
|
||||
object StickerPickerConstants {
|
||||
const val WIDGET_NAME = "m.stickerpicker"
|
||||
const val STICKER_PICKER_REQUEST_CODE = 16000
|
||||
}
|
|
@ -31,6 +31,7 @@ import im.vector.matrix.android.api.session.crypto.verification.IncomingSasVerif
|
|||
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom
|
||||
import im.vector.matrix.android.api.session.terms.TermsService
|
||||
import im.vector.matrix.android.api.util.MatrixItem
|
||||
import im.vector.matrix.android.internal.session.widgets.Widget
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.di.ActiveSessionHolder
|
||||
import im.vector.riotx.core.error.fatalError
|
||||
|
@ -45,6 +46,7 @@ import im.vector.riotx.features.crypto.verification.VerificationBottomSheet
|
|||
import im.vector.riotx.features.debug.DebugMenuActivity
|
||||
import im.vector.riotx.features.home.room.detail.RoomDetailActivity
|
||||
import im.vector.riotx.features.home.room.detail.RoomDetailArgs
|
||||
import im.vector.riotx.features.home.room.detail.sticker.StickerPickerConstants
|
||||
import im.vector.riotx.features.home.room.filtered.FilteredRoomsActivity
|
||||
import im.vector.riotx.features.invite.InviteUsersToRoomActivity
|
||||
import im.vector.riotx.features.media.BigImageViewerActivity
|
||||
|
@ -66,6 +68,7 @@ import im.vector.riotx.features.widgets.WidgetActivity
|
|||
import im.vector.riotx.features.widgets.WidgetArgsBuilder
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class DefaultNavigator @Inject constructor(
|
||||
private val sessionHolder: ActiveSessionHolder,
|
||||
|
@ -224,6 +227,12 @@ class DefaultNavigator @Inject constructor(
|
|||
fragment.startActivityForResult(intent, requestCode)
|
||||
}
|
||||
|
||||
override fun openStickerPicker(fragment: Fragment, roomId: String, widget: Widget, requestCode: Int) {
|
||||
val widgetArgs = widgetArgsBuilder.buildStickerPickerArgs(roomId, widget)
|
||||
val intent = WidgetActivity.newIntent(fragment.requireContext(), widgetArgs)
|
||||
fragment.startActivityForResult(intent, StickerPickerConstants.STICKER_PICKER_REQUEST_CODE)
|
||||
}
|
||||
|
||||
override fun openIntegrationManager(context: Context, roomId: String, integId: String?, screenId: String?) {
|
||||
val widgetArgs = widgetArgsBuilder.buildIntegrationManagerArgs(roomId, integId, screenId)
|
||||
context.startActivity(WidgetActivity.newIntent(context, widgetArgs))
|
||||
|
|
|
@ -24,6 +24,8 @@ import androidx.fragment.app.Fragment
|
|||
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom
|
||||
import im.vector.matrix.android.api.session.terms.TermsService
|
||||
import im.vector.matrix.android.api.util.MatrixItem
|
||||
import im.vector.matrix.android.internal.session.widgets.Widget
|
||||
import im.vector.riotx.features.home.room.detail.sticker.StickerPickerConstants
|
||||
import im.vector.riotx.features.media.ImageContentRenderer
|
||||
import im.vector.riotx.features.media.VideoContentRenderer
|
||||
import im.vector.riotx.features.settings.VectorSettingsActivity
|
||||
|
@ -80,6 +82,11 @@ interface Navigator {
|
|||
token: String?,
|
||||
requestCode: Int = ReviewTermsActivity.TERMS_REQUEST_CODE)
|
||||
|
||||
fun openStickerPicker(fragment: Fragment,
|
||||
roomId: String,
|
||||
widget: Widget,
|
||||
requestCode: Int = StickerPickerConstants.STICKER_PICKER_REQUEST_CODE)
|
||||
|
||||
fun openIntegrationManager(context: Context, roomId: String, integId: String?, screenId: String?)
|
||||
|
||||
fun openImageViewer(activity: Activity, mediaData: ImageContentRenderer.Data, view: View, options: ((MutableList<Pair<View, String>>) -> Unit)?)
|
||||
|
|
|
@ -14,13 +14,13 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotx.features.widgets.room
|
||||
package im.vector.riotx.features.widgets
|
||||
|
||||
import im.vector.riotx.core.platform.VectorViewModelAction
|
||||
|
||||
sealed class RoomWidgetAction : VectorViewModelAction {
|
||||
data class OnWebViewStartedToLoad(val url: String) : RoomWidgetAction()
|
||||
data class OnWebViewLoadingError(val url: String, val isHttpError: Boolean, val errorCode: Int, val errorDescription: String) : RoomWidgetAction()
|
||||
data class OnWebViewLoadingSuccess(val url: String) : RoomWidgetAction()
|
||||
object OnTermsReviewed: RoomWidgetAction()
|
||||
sealed class WidgetAction : VectorViewModelAction {
|
||||
data class OnWebViewStartedToLoad(val url: String) : WidgetAction()
|
||||
data class OnWebViewLoadingError(val url: String, val isHttpError: Boolean, val errorCode: Int, val errorDescription: String) : WidgetAction()
|
||||
data class OnWebViewLoadingSuccess(val url: String) : WidgetAction()
|
||||
object OnTermsReviewed: WidgetAction()
|
||||
}
|
|
@ -19,17 +19,18 @@ package im.vector.riotx.features.widgets
|
|||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import im.vector.matrix.android.api.session.events.model.Content
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.extensions.addFragment
|
||||
import im.vector.riotx.core.platform.ToolbarConfigurable
|
||||
import im.vector.riotx.core.platform.VectorBaseActivity
|
||||
import im.vector.riotx.features.widgets.room.RoomWidgetFragment
|
||||
import im.vector.riotx.features.widgets.room.WidgetArgs
|
||||
import java.io.Serializable
|
||||
|
||||
class WidgetActivity : VectorBaseActivity(), ToolbarConfigurable {
|
||||
|
||||
companion object {
|
||||
|
||||
private const val EXTRA_RESULT = "EXTRA_RESULT"
|
||||
private const val EXTRA_FRAGMENT_ARGS = "EXTRA_FRAGMENT_ARGS"
|
||||
|
||||
fun newIntent(context: Context, args: WidgetArgs): Intent {
|
||||
|
@ -37,6 +38,17 @@ class WidgetActivity : VectorBaseActivity(), ToolbarConfigurable {
|
|||
putExtra(EXTRA_FRAGMENT_ARGS, args)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun getOutput(intent: Intent): Content? {
|
||||
return intent.extras?.getSerializable(EXTRA_RESULT) as? Content
|
||||
}
|
||||
|
||||
fun createResultIntent(content: Content): Intent {
|
||||
return Intent().apply {
|
||||
putExtra(EXTRA_RESULT, content as Serializable)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getLayoutRes() = R.layout.activity_simple
|
||||
|
@ -45,7 +57,7 @@ class WidgetActivity : VectorBaseActivity(), ToolbarConfigurable {
|
|||
if (isFirstCreation()) {
|
||||
val fragmentArgs: WidgetArgs = intent?.extras?.getParcelable(EXTRA_FRAGMENT_ARGS)
|
||||
?: return
|
||||
addFragment(R.id.simpleFragmentContainer, RoomWidgetFragment::class.java, fragmentArgs)
|
||||
addFragment(R.id.simpleFragmentContainer, WidgetFragment::class.java, fragmentArgs)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,9 +16,8 @@
|
|||
|
||||
package im.vector.riotx.features.widgets
|
||||
|
||||
import im.vector.matrix.android.internal.session.widgets.Widget
|
||||
import im.vector.riotx.core.di.ActiveSessionHolder
|
||||
import im.vector.riotx.features.widgets.room.WidgetArgs
|
||||
import im.vector.riotx.features.widgets.room.WidgetKind
|
||||
import javax.inject.Inject
|
||||
|
||||
class WidgetArgsBuilder @Inject constructor(private val sessionHolder: ActiveSessionHolder) {
|
||||
|
@ -35,7 +34,28 @@ class WidgetArgsBuilder @Inject constructor(private val sessionHolder: ActiveSes
|
|||
"screen" to screenId,
|
||||
"integ_id" to integId,
|
||||
"room_id" to roomId
|
||||
).filterValues { it != null } as Map<String, String>
|
||||
).filterNotNull()
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun buildStickerPickerArgs(roomId: String, widget: Widget): WidgetArgs {
|
||||
val widgetId = widget.widgetId
|
||||
val baseUrl = widget.widgetContent.url ?: throw IllegalStateException()
|
||||
return WidgetArgs(
|
||||
baseUrl = baseUrl,
|
||||
kind = WidgetKind.USER,
|
||||
roomId = roomId,
|
||||
widgetId = widgetId,
|
||||
urlParams = mapOf(
|
||||
"widgetId" to widgetId,
|
||||
"room_id" to roomId
|
||||
).filterNotNull()
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun Map<String, String?>.filterNotNull(): Map<String, String>{
|
||||
return filterValues { it != null } as Map<String, String>
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotx.features.widgets.room
|
||||
package im.vector.riotx.features.widgets
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
|
@ -51,12 +51,12 @@ data class WidgetArgs(
|
|||
val urlParams: Map<String, String> = emptyMap()
|
||||
) : Parcelable
|
||||
|
||||
class RoomWidgetFragment @Inject constructor(
|
||||
private val viewModelFactory: RoomWidgetViewModel.Factory
|
||||
) : VectorBaseFragment(), RoomWidgetViewModel.Factory by viewModelFactory, WebViewEventListener {
|
||||
class WidgetFragment @Inject constructor(
|
||||
private val viewModelFactory: WidgetViewModel.Factory
|
||||
) : VectorBaseFragment(), WidgetViewModel.Factory by viewModelFactory, WebViewEventListener {
|
||||
|
||||
private val fragmentArgs: WidgetArgs by args()
|
||||
private val viewModel: RoomWidgetViewModel by fragmentViewModel()
|
||||
private val viewModel: WidgetViewModel by fragmentViewModel()
|
||||
|
||||
override fun getLayoutResId() = R.layout.fragment_room_widget
|
||||
|
||||
|
@ -68,15 +68,10 @@ class RoomWidgetFragment @Inject constructor(
|
|||
}
|
||||
viewModel.observeViewEvents {
|
||||
when (it) {
|
||||
is RoomWidgetViewEvents.DisplayTerms -> displayTerms(it)
|
||||
is RoomWidgetViewEvents.LoadFormattedURL -> loadFormattedUrl(it)
|
||||
is RoomWidgetViewEvents.Close -> vectorBaseActivity.finish()
|
||||
is RoomWidgetViewEvents.DisplayIntegrationManager -> navigator.openIntegrationManager(
|
||||
context = vectorBaseActivity,
|
||||
roomId = fragmentArgs.roomId,
|
||||
integId = it.integId,
|
||||
screenId = it.integType
|
||||
)
|
||||
is WidgetViewEvents.DisplayTerms -> displayTerms(it)
|
||||
is WidgetViewEvents.LoadFormattedURL -> loadFormattedUrl(it)
|
||||
is WidgetViewEvents.Close -> handleClose(it)
|
||||
is WidgetViewEvents.DisplayIntegrationManager -> displayIntegrationManager(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -84,7 +79,7 @@ class RoomWidgetFragment @Inject constructor(
|
|||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
if (requestCode == ReviewTermsActivity.TERMS_REQUEST_CODE) {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
viewModel.handle(RoomWidgetAction.OnTermsReviewed)
|
||||
viewModel.handle(WidgetAction.OnTermsReviewed)
|
||||
} else {
|
||||
vectorBaseActivity.finish()
|
||||
}
|
||||
|
@ -169,7 +164,23 @@ class RoomWidgetFragment @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun displayTerms(displayTerms: RoomWidgetViewEvents.DisplayTerms) {
|
||||
override fun onPageStarted(url: String) {
|
||||
viewModel.handle(WidgetAction.OnWebViewStartedToLoad(url))
|
||||
}
|
||||
|
||||
override fun onPageFinished(url: String) {
|
||||
viewModel.handle(WidgetAction.OnWebViewLoadingSuccess(url))
|
||||
}
|
||||
|
||||
override fun onPageError(url: String, errorCode: Int, description: String) {
|
||||
viewModel.handle(WidgetAction.OnWebViewLoadingError(url, false, errorCode, description))
|
||||
}
|
||||
|
||||
override fun onHttpError(url: String, errorCode: Int, description: String) {
|
||||
viewModel.handle(WidgetAction.OnWebViewLoadingError(url, true, errorCode, description))
|
||||
}
|
||||
|
||||
private fun displayTerms(displayTerms: WidgetViewEvents.DisplayTerms) {
|
||||
navigator.openTerms(
|
||||
fragment = this,
|
||||
serviceType = TermsService.ServiceType.IntegrationManager,
|
||||
|
@ -178,7 +189,7 @@ class RoomWidgetFragment @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun loadFormattedUrl(loadFormattedUrl: RoomWidgetViewEvents.LoadFormattedURL) {
|
||||
private fun loadFormattedUrl(loadFormattedUrl: WidgetViewEvents.LoadFormattedURL) {
|
||||
widgetWebView.clearHistory()
|
||||
widgetWebView.loadUrl(loadFormattedUrl.formattedURL)
|
||||
}
|
||||
|
@ -195,19 +206,20 @@ class RoomWidgetFragment @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun onPageStarted(url: String) {
|
||||
viewModel.handle(RoomWidgetAction.OnWebViewStartedToLoad(url))
|
||||
private fun displayIntegrationManager(event: WidgetViewEvents.DisplayIntegrationManager) {
|
||||
navigator.openIntegrationManager(
|
||||
context = vectorBaseActivity,
|
||||
roomId = fragmentArgs.roomId,
|
||||
integId = event.integId,
|
||||
screenId = event.integType
|
||||
)
|
||||
}
|
||||
|
||||
override fun onPageFinished(url: String) {
|
||||
viewModel.handle(RoomWidgetAction.OnWebViewLoadingSuccess(url))
|
||||
private fun handleClose(event: WidgetViewEvents.Close) {
|
||||
if (event.content != null) {
|
||||
val intent = WidgetActivity.createResultIntent(event.content)
|
||||
vectorBaseActivity.setResult(Activity.RESULT_OK, intent)
|
||||
}
|
||||
|
||||
override fun onPageError(url: String, errorCode: Int, description: String) {
|
||||
viewModel.handle(RoomWidgetAction.OnWebViewLoadingError(url, false, errorCode, description))
|
||||
}
|
||||
|
||||
override fun onHttpError(url: String, errorCode: Int, description: String) {
|
||||
viewModel.handle(RoomWidgetAction.OnWebViewLoadingError(url, true, errorCode, description))
|
||||
vectorBaseActivity.finish()
|
||||
}
|
||||
}
|
|
@ -21,6 +21,7 @@ import com.squareup.inject.assisted.Assisted
|
|||
import com.squareup.inject.assisted.AssistedInject
|
||||
import im.vector.matrix.android.api.query.QueryStringValue
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.events.model.Content
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.toContent
|
||||
|
@ -49,6 +50,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
|
|||
|
||||
interface NavigationCallback {
|
||||
fun close()
|
||||
fun closeWithResult(content: Content)
|
||||
fun openIntegrationManager(integId: String?, integType: String?)
|
||||
}
|
||||
|
||||
|
@ -70,6 +72,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
|
|||
"set_bot_power" -> setBotPower(eventData).run { true }
|
||||
"set_plumbing_state" -> setPlumbingState(eventData).run { true }
|
||||
"set_widget" -> setWidget(eventData).run { true }
|
||||
"m.sticker" -> pickStickerData(eventData).run { true }
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
@ -394,6 +397,23 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
|
|||
widgetPostAPIMediator.sendIntegerResponse(numberOfJoinedMembers, eventData)
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun pickStickerData(eventData: JsonDict) {
|
||||
Timber.d("Received request send sticker")
|
||||
val data = eventData["data"]
|
||||
if (data == null) {
|
||||
widgetPostAPIMediator.sendError(stringProvider.getString(R.string.widget_integration_missing_parameter), eventData)
|
||||
return
|
||||
}
|
||||
val content = (data as? JsonDict)?.get("content") as? Content
|
||||
if (content == null) {
|
||||
widgetPostAPIMediator.sendError(stringProvider.getString(R.string.widget_integration_missing_parameter), eventData)
|
||||
return
|
||||
}
|
||||
widgetPostAPIMediator.sendSuccess(eventData)
|
||||
navigationCallback.closeWithResult(content)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if roomId is present in the event and match
|
||||
* Send response and return true in case of error
|
||||
|
|
|
@ -14,13 +14,14 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotx.features.widgets.room
|
||||
package im.vector.riotx.features.widgets
|
||||
|
||||
import im.vector.matrix.android.api.session.events.model.Content
|
||||
import im.vector.riotx.core.platform.VectorViewEvents
|
||||
|
||||
sealed class RoomWidgetViewEvents : VectorViewEvents {
|
||||
object Close: RoomWidgetViewEvents()
|
||||
data class DisplayIntegrationManager(val integId: String?, val integType: String?): RoomWidgetViewEvents()
|
||||
data class LoadFormattedURL(val formattedURL: String): RoomWidgetViewEvents()
|
||||
data class DisplayTerms(val url: String, val token: String): RoomWidgetViewEvents()
|
||||
sealed class WidgetViewEvents : VectorViewEvents {
|
||||
data class Close(val content: Content?): WidgetViewEvents()
|
||||
data class DisplayIntegrationManager(val integId: String?, val integType: String?): WidgetViewEvents()
|
||||
data class LoadFormattedURL(val formattedURL: String): WidgetViewEvents()
|
||||
data class DisplayTerms(val url: String, val token: String): WidgetViewEvents()
|
||||
}
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotx.features.widgets.room
|
||||
package im.vector.riotx.features.widgets
|
||||
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.airbnb.mvrx.ActivityViewModelContext
|
||||
|
@ -28,27 +28,30 @@ import com.squareup.inject.assisted.Assisted
|
|||
import com.squareup.inject.assisted.AssistedInject
|
||||
import im.vector.matrix.android.api.query.QueryStringValue
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.events.model.Content
|
||||
import im.vector.matrix.android.api.session.integrationmanager.IntegrationManagerService
|
||||
import im.vector.matrix.android.internal.session.widgets.WidgetManagementFailure
|
||||
import im.vector.riotx.core.platform.VectorViewModel
|
||||
import im.vector.riotx.features.widgets.WidgetPostAPIHandler
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
|
||||
class RoomWidgetViewModel @AssistedInject constructor(@Assisted val initialState: WidgetViewState,
|
||||
class WidgetViewModel @AssistedInject constructor(@Assisted val initialState: WidgetViewState,
|
||||
private val widgetPostAPIHandlerFactory: WidgetPostAPIHandler.Factory,
|
||||
private val session: Session)
|
||||
: VectorViewModel<WidgetViewState, RoomWidgetAction, RoomWidgetViewEvents>(initialState), WidgetPostAPIHandler.NavigationCallback {
|
||||
: VectorViewModel<WidgetViewState, WidgetAction, WidgetViewEvents>(initialState),
|
||||
WidgetPostAPIHandler.NavigationCallback,
|
||||
IntegrationManagerService.Listener {
|
||||
|
||||
@AssistedInject.Factory
|
||||
interface Factory {
|
||||
fun create(initialState: WidgetViewState): RoomWidgetViewModel
|
||||
fun create(initialState: WidgetViewState): WidgetViewModel
|
||||
}
|
||||
|
||||
companion object : MvRxViewModelFactory<RoomWidgetViewModel, WidgetViewState> {
|
||||
companion object : MvRxViewModelFactory<WidgetViewModel, WidgetViewState> {
|
||||
|
||||
@JvmStatic
|
||||
override fun create(viewModelContext: ViewModelContext, state: WidgetViewState): RoomWidgetViewModel? {
|
||||
override fun create(viewModelContext: ViewModelContext, state: WidgetViewState): WidgetViewModel? {
|
||||
val factory = when (viewModelContext) {
|
||||
is FragmentViewModelContext -> viewModelContext.fragment as? Factory
|
||||
is ActivityViewModelContext -> viewModelContext.activity as? Factory
|
||||
|
@ -59,10 +62,11 @@ class RoomWidgetViewModel @AssistedInject constructor(@Assisted val initialState
|
|||
|
||||
private val widgetService = session.widgetService()
|
||||
private val integrationManagerService = session.integrationManagerService()
|
||||
private val widgetBuilder = widgetService.getWidgetURLFormatter()
|
||||
private val widgetURLFormatter = widgetService.getWidgetURLFormatter()
|
||||
private val postAPIMediator = widgetService.getWidgetPostAPIMediator()
|
||||
|
||||
init {
|
||||
integrationManagerService.addListener(this)
|
||||
if (initialState.widgetKind.isAdmin()) {
|
||||
val widgetPostAPIHandler = widgetPostAPIHandlerFactory.create(initialState.roomId, this)
|
||||
postAPIMediator.setHandler(widgetPostAPIHandler)
|
||||
|
@ -82,11 +86,11 @@ class RoomWidgetViewModel @AssistedInject constructor(@Assisted val initialState
|
|||
|
||||
fun getPostAPIMediator() = postAPIMediator
|
||||
|
||||
override fun handle(action: RoomWidgetAction) {
|
||||
override fun handle(action: WidgetAction) {
|
||||
when (action) {
|
||||
is RoomWidgetAction.OnWebViewLoadingError -> handleWebViewLoadingError(action.isHttpError, action.errorCode, action.errorDescription)
|
||||
is RoomWidgetAction.OnWebViewLoadingSuccess -> handleWebViewLoadingSuccess(action.url)
|
||||
is RoomWidgetAction.OnWebViewStartedToLoad -> handleWebViewStartLoading()
|
||||
is WidgetAction.OnWebViewLoadingError -> handleWebViewLoadingError(action.isHttpError, action.errorCode, action.errorDescription)
|
||||
is WidgetAction.OnWebViewLoadingSuccess -> handleWebViewLoadingSuccess(action.url)
|
||||
is WidgetAction.OnWebViewStartedToLoad -> handleWebViewStartLoading()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -131,17 +135,17 @@ class RoomWidgetViewModel @AssistedInject constructor(@Assisted val initialState
|
|||
viewModelScope.launch {
|
||||
try {
|
||||
setState { copy(formattedURL = Loading()) }
|
||||
val formattedUrl = widgetBuilder.format(
|
||||
val formattedUrl = widgetURLFormatter.format(
|
||||
baseUrl = initialState.baseUrl,
|
||||
params = initialState.urlParams,
|
||||
forceFetchScalarToken = forceFetchToken,
|
||||
bypassWhitelist = initialState.widgetKind == WidgetKind.INTEGRATION_MANAGER
|
||||
)
|
||||
setState { copy(formattedURL = Success(formattedUrl)) }
|
||||
_viewEvents.post(RoomWidgetViewEvents.LoadFormattedURL(formattedUrl))
|
||||
_viewEvents.post(WidgetViewEvents.LoadFormattedURL(formattedUrl))
|
||||
} catch (failure: Throwable) {
|
||||
if (failure is WidgetManagementFailure.TermsNotSignedException) {
|
||||
_viewEvents.post(RoomWidgetViewEvents.DisplayTerms(failure.baseUrl, failure.token))
|
||||
_viewEvents.post(WidgetViewEvents.DisplayTerms(failure.baseUrl, failure.token))
|
||||
}
|
||||
setState { copy(formattedURL = Fail(failure)) }
|
||||
}
|
||||
|
@ -162,9 +166,11 @@ class RoomWidgetViewModel @AssistedInject constructor(@Assisted val initialState
|
|||
private fun handleWebViewLoadingError(isHttpError: Boolean, reason: Int, errorDescription: String) {
|
||||
if (isHttpError) {
|
||||
// In case of 403, try to refresh the scalar token
|
||||
if (reason == HttpsURLConnection.HTTP_FORBIDDEN) {
|
||||
withState {
|
||||
if (it.formattedURL is Success && reason == HttpsURLConnection.HTTP_FORBIDDEN) {
|
||||
loadFormattedUrl(true)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setState { copy(webviewLoadedUrl = Fail(Throwable(errorDescription))) }
|
||||
}
|
||||
|
@ -172,14 +178,27 @@ class RoomWidgetViewModel @AssistedInject constructor(@Assisted val initialState
|
|||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
integrationManagerService.removeListener(this)
|
||||
postAPIMediator.setHandler(null)
|
||||
}
|
||||
|
||||
// IntegrationManagerService.Listener
|
||||
|
||||
override fun onWidgetPermissionsChanged(widgets: Map<String, Boolean>) {
|
||||
refreshPermissionStatus()
|
||||
}
|
||||
|
||||
// WidgetPostAPIHandler.NavigationCallback
|
||||
|
||||
override fun close() {
|
||||
_viewEvents.post(RoomWidgetViewEvents.Close)
|
||||
_viewEvents.post(WidgetViewEvents.Close(null))
|
||||
}
|
||||
|
||||
override fun closeWithResult(content: Content) {
|
||||
_viewEvents.post(WidgetViewEvents.Close(content))
|
||||
}
|
||||
|
||||
override fun openIntegrationManager(integId: String?, integType: String?) {
|
||||
_viewEvents.post(RoomWidgetViewEvents.DisplayIntegrationManager(integId, integType))
|
||||
_viewEvents.post(WidgetViewEvents.DisplayIntegrationManager(integId, integType))
|
||||
}
|
||||
}
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotx.features.widgets.room
|
||||
package im.vector.riotx.features.widgets
|
||||
|
||||
import com.airbnb.mvrx.Async
|
||||
import com.airbnb.mvrx.MvRxState
|
|
@ -1,49 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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.riotx.features.widgets.admin
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import com.airbnb.mvrx.fragmentViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class AdminWidgetFragment @Inject constructor(
|
||||
private val viewModelFactory: AdminWidgetViewModel.Factory
|
||||
) : VectorBaseFragment(), AdminWidgetViewModel.Factory by viewModelFactory {
|
||||
|
||||
private val viewModel: AdminWidgetViewModel by fragmentViewModel()
|
||||
|
||||
override fun getLayoutResId() = R.layout.fragment_admin_widget
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
// Initialize your view, subscribe to viewModel...
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
// Clear your view, unsubscribe...
|
||||
}
|
||||
|
||||
override fun invalidate() = withState(viewModel) { state ->
|
||||
Timber.v("Invalidate with state: $state")
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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.riotx.features.widgets.admin
|
||||
|
||||
import im.vector.riotx.core.platform.VectorViewEvents
|
||||
|
||||
sealed class AdminWidgetViewEvents : VectorViewEvents
|
|
@ -1,50 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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.riotx.features.widgets.admin
|
||||
|
||||
import com.airbnb.mvrx.ActivityViewModelContext
|
||||
import com.airbnb.mvrx.FragmentViewModelContext
|
||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||
import com.airbnb.mvrx.ViewModelContext
|
||||
import com.squareup.inject.assisted.Assisted
|
||||
import com.squareup.inject.assisted.AssistedInject
|
||||
import im.vector.riotx.core.platform.VectorViewModel
|
||||
|
||||
class AdminWidgetViewModel @AssistedInject constructor(@Assisted initialState: AdminWidgetViewState)
|
||||
: VectorViewModel<AdminWidgetViewState, AdminWidgetAction, AdminWidgetViewEvents>(initialState) {
|
||||
|
||||
@AssistedInject.Factory
|
||||
interface Factory {
|
||||
fun create(initialState: AdminWidgetViewState): AdminWidgetViewModel
|
||||
}
|
||||
|
||||
companion object : MvRxViewModelFactory<AdminWidgetViewModel, AdminWidgetViewState> {
|
||||
|
||||
@JvmStatic
|
||||
override fun create(viewModelContext: ViewModelContext, state: AdminWidgetViewState): AdminWidgetViewModel? {
|
||||
val factory = when (viewModelContext) {
|
||||
is FragmentViewModelContext -> viewModelContext.fragment as? Factory
|
||||
is ActivityViewModelContext -> viewModelContext.activity as? Factory
|
||||
}
|
||||
return factory?.create(state) ?: error("You should let your activity/fragment implements Factory interface")
|
||||
}
|
||||
}
|
||||
|
||||
override fun handle(action: AdminWidgetAction) {
|
||||
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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.riotx.features.widgets.admin
|
||||
|
||||
import com.airbnb.mvrx.MvRxState
|
||||
|
||||
data class AdminWidgetViewState(val boolean: Boolean = false) : MvRxState
|
10
vector/src/main/res/layout/dialog_no_sticker_pack.xml
Normal file
10
vector/src/main/res/layout/dialog_no_sticker_pack.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:drawableBottom="@drawable/stickerpack_rabbit"
|
||||
android:drawablePadding="16dp"
|
||||
android:gravity="center_horizontal"
|
||||
android:maxWidth="300dp"
|
||||
android:paddingTop="16dp"
|
||||
android:text="@string/no_sticker_application_dialog_content" />
|
Loading…
Reference in a new issue