Widget: handle sticker

This commit is contained in:
ganfra 2020-05-26 18:16:38 +02:00
parent dbe4c0c8e4
commit 4b37ede8c2
34 changed files with 384 additions and 277 deletions

View file

@ -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.MatrixCallback
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.session.integrationmanager.IntegrationManager
interface IntegrationManagerService { 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 getOrderedConfigs(): List<IntegrationManagerConfig>
fun getPreferredConfig(): IntegrationManagerConfig fun getPreferredConfig(): IntegrationManagerConfig

View file

@ -17,6 +17,7 @@
package im.vector.matrix.android.api.session.room.send 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.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.Event
import im.vector.matrix.android.api.session.room.model.message.MessageType import im.vector.matrix.android.api.session.room.model.message.MessageType
import im.vector.matrix.android.api.session.room.model.message.OptionItem 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 { 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. * Method to send a text message asynchronously.
* The text to send can be a Spannable and contains special spans (MatrixItemSpan) that will be translated * The text to send can be a Spannable and contains special spans (MatrixItemSpan) that will be translated

View file

@ -24,6 +24,14 @@ import javax.inject.Inject
internal class DefaultIntegrationManagerService @Inject constructor(private val integrationManager: IntegrationManager) : IntegrationManagerService { 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> { override fun getOrderedConfigs(): List<IntegrationManagerConfig> {
return integrationManager.getOrderedConfigs() return integrationManager.getOrderedConfigs()
} }

View file

@ -23,6 +23,7 @@ import im.vector.matrix.android.R
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.events.model.toModel 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.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.session.widgets.model.WidgetContent
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.api.util.NoOpCancellable 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 accountDataDataSource: AccountDataDataSource,
private val configExtractor: IntegrationManagerConfigExtractor) { 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 currentConfigs = ArrayList<IntegrationManagerConfig>()
private val lifecycleOwner: LifecycleOwner = LifecycleOwner { lifecycleRegistry } private val lifecycleOwner: LifecycleOwner = LifecycleOwner { lifecycleRegistry }
private val lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(lifecycleOwner) private val lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(lifecycleOwner)
private val listeners = HashSet<Listener>() private val listeners = HashSet<IntegrationManagerService.Listener>()
fun addListener(listener: Listener) = synchronized(listeners) { listeners.add(listener) } fun addListener(listener: IntegrationManagerService.Listener) = synchronized(listeners) { listeners.add(listener) }
fun removeListener(listener: Listener) = synchronized(listeners) { listeners.remove(listener) } fun removeListener(listener: IntegrationManagerService.Listener) = synchronized(listeners) { listeners.remove(listener) }
init { init {
val defaultConfig = IntegrationManagerConfig( val defaultConfig = IntegrationManagerConfig(

View file

@ -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.session.room.timeline.TimelineEvent
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.api.util.CancelableBag 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.SessionId
import im.vector.matrix.android.internal.di.WorkManagerProvider import im.vector.matrix.android.internal.di.WorkManagerProvider
import im.vector.matrix.android.internal.session.content.UploadContentWorker import im.vector.matrix.android.internal.session.content.UploadContentWorker
@ -67,6 +68,12 @@ internal class DefaultSendService @AssistedInject constructor(
private val workerFutureListenerExecutor = Executors.newSingleThreadExecutor() 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 { override fun sendTextMessage(text: CharSequence, msgType: String, autoMarkdown: Boolean): Cancelable {
return localEchoEventFactory.createTextEvent(roomId, msgType, text, autoMarkdown) return localEchoEventFactory.createTextEvent(roomId, msgType, text, autoMarkdown)
.also { createLocalEcho(it) } .also { createLocalEcho(it) }

View file

@ -23,6 +23,7 @@ import androidx.exifinterface.media.ExifInterface
import im.vector.matrix.android.R import im.vector.matrix.android.R
import im.vector.matrix.android.api.permalinks.PermalinkFactory import im.vector.matrix.android.api.permalinks.PermalinkFactory
import im.vector.matrix.android.api.session.content.ContentAttachmentData 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.Event
import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.LocalEcho 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.model.relation.ReplyToContent
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent 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.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.di.UserId
import im.vector.matrix.android.internal.extensions.subStringBetween import im.vector.matrix.android.internal.extensions.subStringBetween
import im.vector.matrix.android.internal.session.content.ThumbnailExtractor 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) return createFormattedTextEvent(roomId, createTextContent(text, autoMarkdown), msgType)
} }
val content = MessageTextContent(msgType = msgType, body = text.toString()) val content = MessageTextContent(msgType = msgType, body = text.toString())
return createEvent(roomId, content) return createMessageEvent(roomId, content)
} }
private fun createTextContent(text: CharSequence, autoMarkdown: Boolean): TextContent { 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" text != htmlText && htmlText != "<p>${text.trim()}</p>\n"
fun createFormattedTextEvent(roomId: String, textContent: TextContent, msgType: String): Event { 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, fun createReplaceTextEvent(roomId: String,
@ -138,7 +140,7 @@ internal class LocalEchoEventFactory @Inject constructor(
newBodyAutoMarkdown: Boolean, newBodyAutoMarkdown: Boolean,
msgType: String, msgType: String,
compatibilityText: String): Event { compatibilityText: String): Event {
return createEvent(roomId, return createMessageEvent(roomId,
MessageTextContent( MessageTextContent(
msgType = msgType, msgType = msgType,
body = compatibilityText, body = compatibilityText,
@ -153,7 +155,7 @@ internal class LocalEchoEventFactory @Inject constructor(
pollEventId: String, pollEventId: String,
optionIndex: Int, optionIndex: Int,
optionLabel: String): Event { optionLabel: String): Event {
return createEvent(roomId, return createMessageEvent(roomId,
MessagePollResponseContent( MessagePollResponseContent(
body = optionLabel, body = optionLabel,
relatesTo = RelationDefaultContent( relatesTo = RelationDefaultContent(
@ -175,7 +177,7 @@ internal class LocalEchoEventFactory @Inject constructor(
append(it.value) append(it.value)
} }
} }
return createEvent( return createMessageEvent(
roomId, roomId,
MessageOptionsContent( MessageOptionsContent(
body = compatLabel, body = compatLabel,
@ -211,7 +213,7 @@ internal class LocalEchoEventFactory @Inject constructor(
// //
val replyFallback = buildReplyFallback(body, originalEvent.root.senderId ?: "", newBodyText) val replyFallback = buildReplyFallback(body, originalEvent.root.senderId ?: "", newBodyText)
return createEvent(roomId, return createMessageEvent(roomId,
MessageTextContent( MessageTextContent(
msgType = msgType, msgType = msgType,
body = compatibilityText, body = compatibilityText,
@ -280,7 +282,7 @@ internal class LocalEchoEventFactory @Inject constructor(
), ),
url = attachment.queryUri.toString() url = attachment.queryUri.toString()
) )
return createEvent(roomId, content) return createMessageEvent(roomId, content)
} }
private fun createVideoEvent(roomId: String, attachment: ContentAttachmentData): Event { private fun createVideoEvent(roomId: String, attachment: ContentAttachmentData): Event {
@ -316,7 +318,7 @@ internal class LocalEchoEventFactory @Inject constructor(
), ),
url = attachment.queryUri.toString() url = attachment.queryUri.toString()
) )
return createEvent(roomId, content) return createMessageEvent(roomId, content)
} }
private fun createAudioEvent(roomId: String, attachment: ContentAttachmentData): Event { private fun createAudioEvent(roomId: String, attachment: ContentAttachmentData): Event {
@ -329,7 +331,7 @@ internal class LocalEchoEventFactory @Inject constructor(
), ),
url = attachment.queryUri.toString() url = attachment.queryUri.toString()
) )
return createEvent(roomId, content) return createMessageEvent(roomId, content)
} }
private fun createFileEvent(roomId: String, attachment: ContentAttachmentData): Event { private fun createFileEvent(roomId: String, attachment: ContentAttachmentData): Event {
@ -342,18 +344,22 @@ internal class LocalEchoEventFactory @Inject constructor(
), ),
url = attachment.queryUri.toString() 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() val localId = LocalEcho.createLocalEchoId()
return Event( return Event(
roomId = roomId, roomId = roomId,
originServerTs = dummyOriginServerTs(), originServerTs = dummyOriginServerTs(),
senderId = userId, senderId = userId,
eventId = localId, eventId = localId,
type = EventType.MESSAGE, type = type,
content = content.toContent(), content = content,
unsignedData = UnsignedData(age = null, transactionId = localId) unsignedData = UnsignedData(age = null, transactionId = localId)
) )
} }
@ -410,7 +416,7 @@ internal class LocalEchoEventFactory @Inject constructor(
formattedBody = replyFormatted, formattedBody = replyFormatted,
relatesTo = RelationDefaultContent(null, null, ReplyToContent(eventId)) relatesTo = RelationDefaultContent(null, null, ReplyToContent(eventId))
) )
return createEvent(roomId, content) return createMessageEvent(roomId, content)
} }
private fun buildReplyFallback(body: TextContent, originalSenderId: String?, newBodyText: String): String { private fun buildReplyFallback(body: TextContent, originalSenderId: String?, newBodyText: String): String {

View file

@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.session.widgets
import im.vector.matrix.android.R import im.vector.matrix.android.R
import im.vector.matrix.android.api.session.integrationmanager.IntegrationManagerConfig 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.api.session.widgets.WidgetURLFormatter
import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.session.integrationmanager.IntegrationManager 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, internal class DefaultWidgetURLFormatter @Inject constructor(private val integrationManager: IntegrationManager,
private val getScalarTokenTask: GetScalarTokenTask, private val getScalarTokenTask: GetScalarTokenTask,
private val stringProvider: StringProvider 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() private var whiteListedUrls: List<String> = emptyList()
fun start() { fun start() {
@ -50,7 +51,7 @@ internal class DefaultWidgetURLFormatter @Inject constructor(private val integra
private fun setupWithConfiguration() { private fun setupWithConfiguration() {
val preferredConfig = integrationManager.getPreferredConfig() val preferredConfig = integrationManager.getPreferredConfig()
if (currentConfig != preferredConfig) { if (!this::currentConfig.isInitialized || preferredConfig != currentConfig) {
currentConfig = preferredConfig currentConfig = preferredConfig
val defaultWhiteList = stringProvider.getStringArray(R.array.integrations_widgets_urls).asList() val defaultWhiteList = stringProvider.getStringArray(R.array.integrations_widgets_urls).asList()
whiteListedUrls = when (preferredConfig.kind) { whiteListedUrls = when (preferredConfig.kind) {

View file

@ -21,6 +21,7 @@ import im.vector.matrix.android.api.session.widgets.model.WidgetContent
data class Widget( data class Widget(
val widgetContent: WidgetContent, val widgetContent: WidgetContent,
val event: Event? = null val event: Event? = null,
val widgetId: String? = null
) )

View file

@ -21,16 +21,16 @@ import javax.inject.Inject
internal class WidgetDependenciesHolder @Inject constructor(private val integrationManager: IntegrationManager, internal class WidgetDependenciesHolder @Inject constructor(private val integrationManager: IntegrationManager,
private val widgetManager: WidgetManager, private val widgetManager: WidgetManager,
private val widgetURLBuilder: DefaultWidgetURLFormatter) { private val widgetURLFormatter: DefaultWidgetURLFormatter) {
fun start() { fun start() {
integrationManager.start() integrationManager.start()
widgetManager.start() widgetManager.start()
widgetURLBuilder.start() widgetURLFormatter.start()
} }
fun stop() { fun stop() {
widgetURLBuilder.stop() widgetURLFormatter.stop()
widgetManager.stop() widgetManager.stop()
integrationManager.stop() integrationManager.stop()
} }

View file

@ -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.Event
import im.vector.matrix.android.api.session.events.model.EventType 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.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.model.PowerLevelsContent
import im.vector.matrix.android.api.session.room.powerlevels.PowerLevelsHelper import im.vector.matrix.android.api.session.room.powerlevels.PowerLevelsHelper
import im.vector.matrix.android.api.session.widgets.WidgetService 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 stateEventDataSource: StateEventDataSource,
private val taskExecutor: TaskExecutor, private val taskExecutor: TaskExecutor,
private val createWidgetTask: CreateWidgetTask, 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 lifecycleOwner: LifecycleOwner = LifecycleOwner { lifecycleRegistry }
private val lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(lifecycleOwner) private val lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(lifecycleOwner)
@ -114,7 +115,7 @@ internal class WidgetManager @Inject constructor(private val integrationManager:
} }
// widgetEvent.stateKey = widget id // widgetEvent.stateKey = widget id
if (widgetEvent.stateKey != null && !widgets.containsKey(widgetEvent.stateKey)) { if (widgetEvent.stateKey != null && !widgets.containsKey(widgetEvent.stateKey)) {
val widget = Widget(widgetContent, widgetEvent) val widget = Widget(widgetContent, widgetEvent, widgetEvent.stateKey)
widgets[widgetEvent.stateKey] = widget widgets[widgetEvent.stateKey] = widget
} }
} }

View file

@ -33,7 +33,7 @@ internal fun UserAccountDataEvent.extractWidgetSequence(): Sequence<Widget> {
if (content == null) { if (content == null) {
null null
} else { } else {
Widget(content, event) Widget(content, event, event.stateKey)
} }
} }
} }

View file

@ -102,8 +102,7 @@ import im.vector.riotx.features.signout.soft.SoftLogoutFragment
import im.vector.riotx.features.terms.ReviewTermsFragment import im.vector.riotx.features.terms.ReviewTermsFragment
import im.vector.riotx.features.userdirectory.KnownUsersFragment import im.vector.riotx.features.userdirectory.KnownUsersFragment
import im.vector.riotx.features.userdirectory.UserDirectoryFragment import im.vector.riotx.features.userdirectory.UserDirectoryFragment
import im.vector.riotx.features.widgets.admin.AdminWidgetFragment import im.vector.riotx.features.widgets.WidgetFragment
import im.vector.riotx.features.widgets.room.RoomWidgetFragment
@Module @Module
interface FragmentModule { interface FragmentModule {
@ -515,11 +514,7 @@ interface FragmentModule {
@Binds @Binds
@IntoMap @IntoMap
@FragmentKey(RoomWidgetFragment::class) @FragmentKey(WidgetFragment::class)
fun bindRoomWidgetFragment(fragment: RoomWidgetFragment): Fragment fun bindWidgetFragment(fragment: WidgetFragment): Fragment
@Binds
@IntoMap
@FragmentKey(AdminWidgetFragment::class)
fun bindAdminWidgetFragment(fragment: AdminWidgetFragment): Fragment
} }

View file

@ -17,6 +17,7 @@
package im.vector.riotx.features.attachments package im.vector.riotx.features.attachments
import im.vector.matrix.android.api.session.content.ContentAttachmentData 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.MultiPickerAudioType
import im.vector.riotx.multipicker.entity.MultiPickerBaseType import im.vector.riotx.multipicker.entity.MultiPickerBaseType
import im.vector.riotx.multipicker.entity.MultiPickerContactType import im.vector.riotx.multipicker.entity.MultiPickerContactType

View file

@ -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.content.ContentAttachmentData
import im.vector.matrix.android.api.session.events.model.Event 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.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.Timeline
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.riotx.core.platform.VectorViewModelAction import im.vector.riotx.core.platform.VectorViewModelAction
@ -26,6 +27,7 @@ import im.vector.riotx.core.platform.VectorViewModelAction
sealed class RoomDetailAction : VectorViewModelAction { sealed class RoomDetailAction : VectorViewModelAction {
data class UserIsTyping(val isTyping: Boolean) : RoomDetailAction() data class UserIsTyping(val isTyping: Boolean) : RoomDetailAction()
data class SaveDraft(val draft: String) : 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 SendMessage(val text: CharSequence, val autoMarkdown: Boolean) : RoomDetailAction()
data class SendMedia(val attachments: List<ContentAttachmentData>, val compressBeforeSending: Boolean) : RoomDetailAction() data class SendMedia(val attachments: List<ContentAttachmentData>, val compressBeforeSending: Boolean) : RoomDetailAction()
data class TimelineEventTurnsVisible(val event: TimelineEvent) : 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 RequestVerification(val userId: String) : RoomDetailAction()
data class ResumeVerification(val transactionId: String, val otherUserId: String?) : RoomDetailAction() data class ResumeVerification(val transactionId: String, val otherUserId: String?) : RoomDetailAction()
data class ReRequestKeys(val eventId: String) : RoomDetailAction() data class ReRequestKeys(val eventId: String) : RoomDetailAction()
object SelectStickerAttachment : RoomDetailAction()
} }

View file

@ -26,6 +26,7 @@ import android.os.Bundle
import android.os.Parcelable import android.os.Parcelable
import android.text.Spannable import android.text.Spannable
import android.view.HapticFeedbackConstants import android.view.HapticFeedbackConstants
import android.view.LayoutInflater
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View 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.Session
import im.vector.matrix.android.api.session.content.ContentAttachmentData 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.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.file.FileService
import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.message.MessageAudioContent 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.MessageFileContent
import im.vector.matrix.android.api.session.room.model.message.MessageFormat 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.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.MessageTextContent
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent
import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent 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.AvatarRenderer
import im.vector.riotx.features.home.room.detail.composer.TextComposerView 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.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.TimelineEventController
import im.vector.riotx.features.home.room.detail.timeline.action.EventSharedAction import im.vector.riotx.features.home.room.detail.timeline.action.EventSharedAction
import im.vector.riotx.features.home.room.detail.timeline.action.MessageActionsBottomSheet 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.settings.VectorPreferences
import im.vector.riotx.features.share.SharedData import im.vector.riotx.features.share.SharedData
import im.vector.riotx.features.themes.ThemeUtils import im.vector.riotx.features.themes.ThemeUtils
import im.vector.riotx.features.widgets.WidgetActivity
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
@ -300,10 +305,37 @@ class RoomDetailFragment @Inject constructor(
is RoomDetailViewEvents.DownloadFileState -> handleDownloadFileState(it) is RoomDetailViewEvents.DownloadFileState -> handleDownloadFileState(it)
is RoomDetailViewEvents.JoinRoomCommandSuccess -> handleJoinedToAnotherRoom(it) is RoomDetailViewEvents.JoinRoomCommandSuccess -> handleJoinedToAnotherRoom(it)
is RoomDetailViewEvents.SendMessageResult -> renderSendMessageResult(it) is RoomDetailViewEvents.SendMessageResult -> renderSendMessageResult(it)
RoomDetailViewEvents.DisplayPromptForIntegrationManager -> displayPromptForIntegrationManager()
is RoomDetailViewEvents.OpenStickerPicker -> openStickerPicker(it)
}.exhaustive }.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) { private fun handleJoinedToAnotherRoom(action: RoomDetailViewEvents.JoinRoomCommandSuccess) {
updateComposerText("") updateComposerText("")
lockSendButton = false lockSendButton = false
@ -530,6 +562,10 @@ class RoomDetailFragment @Inject constructor(
val (eventId, reaction) = EmojiReactionPickerActivity.getOutput(data) ?: return val (eventId, reaction) = EmojiReactionPickerActivity.getOutput(data) ?: return
roomDetailViewModel.handle(RoomDetailAction.SendReaction(eventId, reaction)) 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.GALLERY -> attachmentsHelper.selectGallery(this)
AttachmentTypeSelectorView.Type.AUDIO -> attachmentsHelper.selectAudio(this) AttachmentTypeSelectorView.Type.AUDIO -> attachmentsHelper.selectAudio(this)
AttachmentTypeSelectorView.Type.CONTACT -> attachmentsHelper.selectContact(this) AttachmentTypeSelectorView.Type.CONTACT -> attachmentsHelper.selectContact(this)
AttachmentTypeSelectorView.Type.STICKER -> vectorBaseActivity.notImplemented("Adding stickers") AttachmentTypeSelectorView.Type.STICKER -> roomDetailViewModel.handle(RoomDetailAction.SelectStickerAttachment)
}.exhaustive }.exhaustive
} }

View file

@ -17,6 +17,7 @@
package im.vector.riotx.features.home.room.detail package im.vector.riotx.features.home.room.detail
import androidx.annotation.StringRes import androidx.annotation.StringRes
import im.vector.matrix.android.internal.session.widgets.Widget
import im.vector.riotx.core.platform.VectorViewEvents import im.vector.riotx.core.platform.VectorViewEvents
import im.vector.riotx.features.command.Command import im.vector.riotx.features.command.Command
import java.io.File import java.io.File
@ -49,6 +50,10 @@ sealed class RoomDetailViewEvents : VectorViewEvents {
abstract class SendMessageResult : RoomDetailViewEvents() abstract class SendMessageResult : RoomDetailViewEvents()
object DisplayPromptForIntegrationManager: RoomDetailViewEvents()
data class OpenStickerPicker(val widget: Widget): RoomDetailViewEvents()
object MessageSent : SendMessageResult() object MessageSent : SendMessageResult()
data class JoinRoomCommandSuccess(val roomId: String) : SendMessageResult() data class JoinRoomCommandSuccess(val roomId: String) : SendMessageResult()
class SlashCommandError(val command: Command) : SendMessageResult() class SlashCommandError(val command: Command) : SendMessageResult()

View file

@ -18,6 +18,7 @@ package im.vector.riotx.features.home.room.detail
import android.net.Uri import android.net.Uri
import androidx.annotation.IdRes import androidx.annotation.IdRes
import androidx.lifecycle.viewModelScope
import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.Success 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.EventType
import im.vector.matrix.android.api.session.events.model.isImageMessage 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.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.events.model.toModel
import im.vector.matrix.android.api.session.file.FileService import im.vector.matrix.android.api.session.file.FileService
import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilities 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.command.ParsedCommand
import im.vector.riotx.features.crypto.verification.SupportedVerificationMethodsProvider 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.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.detail.timeline.helper.TimelineDisplayableEvents
import im.vector.riotx.features.home.room.typing.TypingHelper import im.vector.riotx.features.home.room.typing.TypingHelper
import im.vector.riotx.features.settings.VectorPreferences import im.vector.riotx.features.settings.VectorPreferences
@ -74,6 +77,7 @@ import io.reactivex.Observable
import io.reactivex.functions.BiFunction import io.reactivex.functions.BiFunction
import io.reactivex.rxkotlin.subscribeBy import io.reactivex.rxkotlin.subscribeBy
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import kotlinx.coroutines.launch
import org.commonmark.parser.Parser import org.commonmark.parser.Parser
import org.commonmark.renderer.html.HtmlRenderer import org.commonmark.renderer.html.HtmlRenderer
import timber.log.Timber import timber.log.Timber
@ -89,7 +93,8 @@ class RoomDetailViewModel @AssistedInject constructor(
private val typingHelper: TypingHelper, private val typingHelper: TypingHelper,
private val rainbowGenerator: RainbowGenerator, private val rainbowGenerator: RainbowGenerator,
private val session: Session, private val session: Session,
private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider,
private val stickerPickerActionHandler: StickerPickerActionHandler
) : VectorViewModel<RoomDetailViewState, RoomDetailAction, RoomDetailViewEvents>(initialState), Timeline.Listener { ) : VectorViewModel<RoomDetailViewState, RoomDetailAction, RoomDetailViewEvents>(initialState), Timeline.Listener {
private val room = session.getRoom(initialState.roomId)!! private val room = session.getRoom(initialState.roomId)!!
@ -183,6 +188,7 @@ class RoomDetailViewModel @AssistedInject constructor(
is RoomDetailAction.SaveDraft -> handleSaveDraft(action) is RoomDetailAction.SaveDraft -> handleSaveDraft(action)
is RoomDetailAction.SendMessage -> handleSendMessage(action) is RoomDetailAction.SendMessage -> handleSendMessage(action)
is RoomDetailAction.SendMedia -> handleSendMedia(action) is RoomDetailAction.SendMedia -> handleSendMedia(action)
is RoomDetailAction.SendSticker -> handleSendSticker(action)
is RoomDetailAction.TimelineEventTurnsVisible -> handleEventVisible(action) is RoomDetailAction.TimelineEventTurnsVisible -> handleEventVisible(action)
is RoomDetailAction.TimelineEventTurnsInvisible -> handleEventInvisible(action) is RoomDetailAction.TimelineEventTurnsInvisible -> handleEventInvisible(action)
is RoomDetailAction.LoadMoreTimelineEvents -> handleLoadMore(action) is RoomDetailAction.LoadMoreTimelineEvents -> handleLoadMore(action)
@ -214,6 +220,18 @@ class RoomDetailViewModel @AssistedInject constructor(
is RoomDetailAction.RequestVerification -> handleRequestVerification(action) is RoomDetailAction.RequestVerification -> handleRequestVerification(action)
is RoomDetailAction.ResumeVerification -> handleResumeRequestVerification(action) is RoomDetailAction.ResumeVerification -> handleResumeRequestVerification(action)
is RoomDetailAction.ReRequestKeys -> handleReRequestKeys(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)
} }
} }

View file

@ -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
)
}
}
}

View file

@ -14,8 +14,9 @@
* limitations under the License. * 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 object StickerPickerConstants {
const val WIDGET_NAME = "m.stickerpicker"
sealed class AdminWidgetAction : VectorViewModelAction const val STICKER_PICKER_REQUEST_CODE = 16000
}

View file

@ -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.room.model.roomdirectory.PublicRoom
import im.vector.matrix.android.api.session.terms.TermsService import im.vector.matrix.android.api.session.terms.TermsService
import im.vector.matrix.android.api.util.MatrixItem 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.R
import im.vector.riotx.core.di.ActiveSessionHolder import im.vector.riotx.core.di.ActiveSessionHolder
import im.vector.riotx.core.error.fatalError 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.debug.DebugMenuActivity
import im.vector.riotx.features.home.room.detail.RoomDetailActivity 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.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.home.room.filtered.FilteredRoomsActivity
import im.vector.riotx.features.invite.InviteUsersToRoomActivity import im.vector.riotx.features.invite.InviteUsersToRoomActivity
import im.vector.riotx.features.media.BigImageViewerActivity 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 im.vector.riotx.features.widgets.WidgetArgsBuilder
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@Singleton @Singleton
class DefaultNavigator @Inject constructor( class DefaultNavigator @Inject constructor(
private val sessionHolder: ActiveSessionHolder, private val sessionHolder: ActiveSessionHolder,
@ -224,6 +227,12 @@ class DefaultNavigator @Inject constructor(
fragment.startActivityForResult(intent, requestCode) 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?) { override fun openIntegrationManager(context: Context, roomId: String, integId: String?, screenId: String?) {
val widgetArgs = widgetArgsBuilder.buildIntegrationManagerArgs(roomId, integId, screenId) val widgetArgs = widgetArgsBuilder.buildIntegrationManagerArgs(roomId, integId, screenId)
context.startActivity(WidgetActivity.newIntent(context, widgetArgs)) context.startActivity(WidgetActivity.newIntent(context, widgetArgs))

View file

@ -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.room.model.roomdirectory.PublicRoom
import im.vector.matrix.android.api.session.terms.TermsService import im.vector.matrix.android.api.session.terms.TermsService
import im.vector.matrix.android.api.util.MatrixItem 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.ImageContentRenderer
import im.vector.riotx.features.media.VideoContentRenderer import im.vector.riotx.features.media.VideoContentRenderer
import im.vector.riotx.features.settings.VectorSettingsActivity import im.vector.riotx.features.settings.VectorSettingsActivity
@ -80,6 +82,11 @@ interface Navigator {
token: String?, token: String?,
requestCode: Int = ReviewTermsActivity.TERMS_REQUEST_CODE) 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 openIntegrationManager(context: Context, roomId: String, integId: String?, screenId: String?)
fun openImageViewer(activity: Activity, mediaData: ImageContentRenderer.Data, view: View, options: ((MutableList<Pair<View, String>>) -> Unit)?) fun openImageViewer(activity: Activity, mediaData: ImageContentRenderer.Data, view: View, options: ((MutableList<Pair<View, String>>) -> Unit)?)

View file

@ -14,13 +14,13 @@
* limitations under the License. * limitations under the License.
*/ */
package im.vector.riotx.features.widgets.room package im.vector.riotx.features.widgets
import im.vector.riotx.core.platform.VectorViewModelAction import im.vector.riotx.core.platform.VectorViewModelAction
sealed class RoomWidgetAction : VectorViewModelAction { sealed class WidgetAction : VectorViewModelAction {
data class OnWebViewStartedToLoad(val url: String) : RoomWidgetAction() data class OnWebViewStartedToLoad(val url: String) : WidgetAction()
data class OnWebViewLoadingError(val url: String, val isHttpError: Boolean, val errorCode: Int, val errorDescription: String) : RoomWidgetAction() data class OnWebViewLoadingError(val url: String, val isHttpError: Boolean, val errorCode: Int, val errorDescription: String) : WidgetAction()
data class OnWebViewLoadingSuccess(val url: String) : RoomWidgetAction() data class OnWebViewLoadingSuccess(val url: String) : WidgetAction()
object OnTermsReviewed: RoomWidgetAction() object OnTermsReviewed: WidgetAction()
} }

View file

@ -19,17 +19,18 @@ package im.vector.riotx.features.widgets
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import im.vector.matrix.android.api.session.events.model.Content
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.extensions.addFragment import im.vector.riotx.core.extensions.addFragment
import im.vector.riotx.core.platform.ToolbarConfigurable import im.vector.riotx.core.platform.ToolbarConfigurable
import im.vector.riotx.core.platform.VectorBaseActivity import im.vector.riotx.core.platform.VectorBaseActivity
import im.vector.riotx.features.widgets.room.RoomWidgetFragment import java.io.Serializable
import im.vector.riotx.features.widgets.room.WidgetArgs
class WidgetActivity : VectorBaseActivity(), ToolbarConfigurable { class WidgetActivity : VectorBaseActivity(), ToolbarConfigurable {
companion object { companion object {
private const val EXTRA_RESULT = "EXTRA_RESULT"
private const val EXTRA_FRAGMENT_ARGS = "EXTRA_FRAGMENT_ARGS" private const val EXTRA_FRAGMENT_ARGS = "EXTRA_FRAGMENT_ARGS"
fun newIntent(context: Context, args: WidgetArgs): Intent { fun newIntent(context: Context, args: WidgetArgs): Intent {
@ -37,6 +38,17 @@ class WidgetActivity : VectorBaseActivity(), ToolbarConfigurable {
putExtra(EXTRA_FRAGMENT_ARGS, args) 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 override fun getLayoutRes() = R.layout.activity_simple
@ -45,7 +57,7 @@ class WidgetActivity : VectorBaseActivity(), ToolbarConfigurable {
if (isFirstCreation()) { if (isFirstCreation()) {
val fragmentArgs: WidgetArgs = intent?.extras?.getParcelable(EXTRA_FRAGMENT_ARGS) val fragmentArgs: WidgetArgs = intent?.extras?.getParcelable(EXTRA_FRAGMENT_ARGS)
?: return ?: return
addFragment(R.id.simpleFragmentContainer, RoomWidgetFragment::class.java, fragmentArgs) addFragment(R.id.simpleFragmentContainer, WidgetFragment::class.java, fragmentArgs)
} }
} }

View file

@ -16,9 +16,8 @@
package im.vector.riotx.features.widgets 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.core.di.ActiveSessionHolder
import im.vector.riotx.features.widgets.room.WidgetArgs
import im.vector.riotx.features.widgets.room.WidgetKind
import javax.inject.Inject import javax.inject.Inject
class WidgetArgsBuilder @Inject constructor(private val sessionHolder: ActiveSessionHolder) { class WidgetArgsBuilder @Inject constructor(private val sessionHolder: ActiveSessionHolder) {
@ -35,7 +34,28 @@ class WidgetArgsBuilder @Inject constructor(private val sessionHolder: ActiveSes
"screen" to screenId, "screen" to screenId,
"integ_id" to integId, "integ_id" to integId,
"room_id" to roomId "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>
}
} }

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package im.vector.riotx.features.widgets.room package im.vector.riotx.features.widgets
import android.app.Activity import android.app.Activity
import android.content.Intent import android.content.Intent
@ -51,12 +51,12 @@ data class WidgetArgs(
val urlParams: Map<String, String> = emptyMap() val urlParams: Map<String, String> = emptyMap()
) : Parcelable ) : Parcelable
class RoomWidgetFragment @Inject constructor( class WidgetFragment @Inject constructor(
private val viewModelFactory: RoomWidgetViewModel.Factory private val viewModelFactory: WidgetViewModel.Factory
) : VectorBaseFragment(), RoomWidgetViewModel.Factory by viewModelFactory, WebViewEventListener { ) : VectorBaseFragment(), WidgetViewModel.Factory by viewModelFactory, WebViewEventListener {
private val fragmentArgs: WidgetArgs by args() 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 override fun getLayoutResId() = R.layout.fragment_room_widget
@ -68,15 +68,10 @@ class RoomWidgetFragment @Inject constructor(
} }
viewModel.observeViewEvents { viewModel.observeViewEvents {
when (it) { when (it) {
is RoomWidgetViewEvents.DisplayTerms -> displayTerms(it) is WidgetViewEvents.DisplayTerms -> displayTerms(it)
is RoomWidgetViewEvents.LoadFormattedURL -> loadFormattedUrl(it) is WidgetViewEvents.LoadFormattedURL -> loadFormattedUrl(it)
is RoomWidgetViewEvents.Close -> vectorBaseActivity.finish() is WidgetViewEvents.Close -> handleClose(it)
is RoomWidgetViewEvents.DisplayIntegrationManager -> navigator.openIntegrationManager( is WidgetViewEvents.DisplayIntegrationManager -> displayIntegrationManager(it)
context = vectorBaseActivity,
roomId = fragmentArgs.roomId,
integId = it.integId,
screenId = it.integType
)
} }
} }
} }
@ -84,7 +79,7 @@ class RoomWidgetFragment @Inject constructor(
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == ReviewTermsActivity.TERMS_REQUEST_CODE) { if (requestCode == ReviewTermsActivity.TERMS_REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK) { if (resultCode == Activity.RESULT_OK) {
viewModel.handle(RoomWidgetAction.OnTermsReviewed) viewModel.handle(WidgetAction.OnTermsReviewed)
} else { } else {
vectorBaseActivity.finish() 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( navigator.openTerms(
fragment = this, fragment = this,
serviceType = TermsService.ServiceType.IntegrationManager, 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.clearHistory()
widgetWebView.loadUrl(loadFormattedUrl.formattedURL) widgetWebView.loadUrl(loadFormattedUrl.formattedURL)
} }
@ -195,19 +206,20 @@ class RoomWidgetFragment @Inject constructor(
} }
} }
override fun onPageStarted(url: String) { private fun displayIntegrationManager(event: WidgetViewEvents.DisplayIntegrationManager) {
viewModel.handle(RoomWidgetAction.OnWebViewStartedToLoad(url)) navigator.openIntegrationManager(
context = vectorBaseActivity,
roomId = fragmentArgs.roomId,
integId = event.integId,
screenId = event.integType
)
} }
override fun onPageFinished(url: String) { private fun handleClose(event: WidgetViewEvents.Close) {
viewModel.handle(RoomWidgetAction.OnWebViewLoadingSuccess(url)) if (event.content != null) {
val intent = WidgetActivity.createResultIntent(event.content)
vectorBaseActivity.setResult(Activity.RESULT_OK, intent)
} }
vectorBaseActivity.finish()
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))
} }
} }

View file

@ -21,6 +21,7 @@ import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.query.QueryStringValue import im.vector.matrix.android.api.query.QueryStringValue
import im.vector.matrix.android.api.session.Session 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.Event
import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.toContent import im.vector.matrix.android.api.session.events.model.toContent
@ -49,6 +50,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
interface NavigationCallback { interface NavigationCallback {
fun close() fun close()
fun closeWithResult(content: Content)
fun openIntegrationManager(integId: String?, integType: String?) 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_bot_power" -> setBotPower(eventData).run { true }
"set_plumbing_state" -> setPlumbingState(eventData).run { true } "set_plumbing_state" -> setPlumbingState(eventData).run { true }
"set_widget" -> setWidget(eventData).run { true } "set_widget" -> setWidget(eventData).run { true }
"m.sticker" -> pickStickerData(eventData).run { true }
else -> false else -> false
} }
} }
@ -394,6 +397,23 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
widgetPostAPIMediator.sendIntegerResponse(numberOfJoinedMembers, eventData) 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 * Check if roomId is present in the event and match
* Send response and return true in case of error * Send response and return true in case of error

View file

@ -14,13 +14,14 @@
* limitations under the License. * 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 import im.vector.riotx.core.platform.VectorViewEvents
sealed class RoomWidgetViewEvents : VectorViewEvents { sealed class WidgetViewEvents : VectorViewEvents {
object Close: RoomWidgetViewEvents() data class Close(val content: Content?): WidgetViewEvents()
data class DisplayIntegrationManager(val integId: String?, val integType: String?): RoomWidgetViewEvents() data class DisplayIntegrationManager(val integId: String?, val integType: String?): WidgetViewEvents()
data class LoadFormattedURL(val formattedURL: String): RoomWidgetViewEvents() data class LoadFormattedURL(val formattedURL: String): WidgetViewEvents()
data class DisplayTerms(val url: String, val token: String): RoomWidgetViewEvents() data class DisplayTerms(val url: String, val token: String): WidgetViewEvents()
} }

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package im.vector.riotx.features.widgets.room package im.vector.riotx.features.widgets
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.ActivityViewModelContext
@ -28,27 +28,30 @@ import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.query.QueryStringValue import im.vector.matrix.android.api.query.QueryStringValue
import im.vector.matrix.android.api.session.Session 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.matrix.android.internal.session.widgets.WidgetManagementFailure
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.features.widgets.WidgetPostAPIHandler
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
import javax.net.ssl.HttpsURLConnection 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 widgetPostAPIHandlerFactory: WidgetPostAPIHandler.Factory,
private val session: Session) private val session: Session)
: VectorViewModel<WidgetViewState, RoomWidgetAction, RoomWidgetViewEvents>(initialState), WidgetPostAPIHandler.NavigationCallback { : VectorViewModel<WidgetViewState, WidgetAction, WidgetViewEvents>(initialState),
WidgetPostAPIHandler.NavigationCallback,
IntegrationManagerService.Listener {
@AssistedInject.Factory @AssistedInject.Factory
interface Factory { interface Factory {
fun create(initialState: WidgetViewState): RoomWidgetViewModel fun create(initialState: WidgetViewState): WidgetViewModel
} }
companion object : MvRxViewModelFactory<RoomWidgetViewModel, WidgetViewState> { companion object : MvRxViewModelFactory<WidgetViewModel, WidgetViewState> {
@JvmStatic @JvmStatic
override fun create(viewModelContext: ViewModelContext, state: WidgetViewState): RoomWidgetViewModel? { override fun create(viewModelContext: ViewModelContext, state: WidgetViewState): WidgetViewModel? {
val factory = when (viewModelContext) { val factory = when (viewModelContext) {
is FragmentViewModelContext -> viewModelContext.fragment as? Factory is FragmentViewModelContext -> viewModelContext.fragment as? Factory
is ActivityViewModelContext -> viewModelContext.activity 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 widgetService = session.widgetService()
private val integrationManagerService = session.integrationManagerService() private val integrationManagerService = session.integrationManagerService()
private val widgetBuilder = widgetService.getWidgetURLFormatter() private val widgetURLFormatter = widgetService.getWidgetURLFormatter()
private val postAPIMediator = widgetService.getWidgetPostAPIMediator() private val postAPIMediator = widgetService.getWidgetPostAPIMediator()
init { init {
integrationManagerService.addListener(this)
if (initialState.widgetKind.isAdmin()) { if (initialState.widgetKind.isAdmin()) {
val widgetPostAPIHandler = widgetPostAPIHandlerFactory.create(initialState.roomId, this) val widgetPostAPIHandler = widgetPostAPIHandlerFactory.create(initialState.roomId, this)
postAPIMediator.setHandler(widgetPostAPIHandler) postAPIMediator.setHandler(widgetPostAPIHandler)
@ -82,11 +86,11 @@ class RoomWidgetViewModel @AssistedInject constructor(@Assisted val initialState
fun getPostAPIMediator() = postAPIMediator fun getPostAPIMediator() = postAPIMediator
override fun handle(action: RoomWidgetAction) { override fun handle(action: WidgetAction) {
when (action) { when (action) {
is RoomWidgetAction.OnWebViewLoadingError -> handleWebViewLoadingError(action.isHttpError, action.errorCode, action.errorDescription) is WidgetAction.OnWebViewLoadingError -> handleWebViewLoadingError(action.isHttpError, action.errorCode, action.errorDescription)
is RoomWidgetAction.OnWebViewLoadingSuccess -> handleWebViewLoadingSuccess(action.url) is WidgetAction.OnWebViewLoadingSuccess -> handleWebViewLoadingSuccess(action.url)
is RoomWidgetAction.OnWebViewStartedToLoad -> handleWebViewStartLoading() is WidgetAction.OnWebViewStartedToLoad -> handleWebViewStartLoading()
} }
} }
@ -131,17 +135,17 @@ class RoomWidgetViewModel @AssistedInject constructor(@Assisted val initialState
viewModelScope.launch { viewModelScope.launch {
try { try {
setState { copy(formattedURL = Loading()) } setState { copy(formattedURL = Loading()) }
val formattedUrl = widgetBuilder.format( val formattedUrl = widgetURLFormatter.format(
baseUrl = initialState.baseUrl, baseUrl = initialState.baseUrl,
params = initialState.urlParams, params = initialState.urlParams,
forceFetchScalarToken = forceFetchToken, forceFetchScalarToken = forceFetchToken,
bypassWhitelist = initialState.widgetKind == WidgetKind.INTEGRATION_MANAGER bypassWhitelist = initialState.widgetKind == WidgetKind.INTEGRATION_MANAGER
) )
setState { copy(formattedURL = Success(formattedUrl)) } setState { copy(formattedURL = Success(formattedUrl)) }
_viewEvents.post(RoomWidgetViewEvents.LoadFormattedURL(formattedUrl)) _viewEvents.post(WidgetViewEvents.LoadFormattedURL(formattedUrl))
} catch (failure: Throwable) { } catch (failure: Throwable) {
if (failure is WidgetManagementFailure.TermsNotSignedException) { 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)) } 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) { private fun handleWebViewLoadingError(isHttpError: Boolean, reason: Int, errorDescription: String) {
if (isHttpError) { if (isHttpError) {
// In case of 403, try to refresh the scalar token // 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) loadFormattedUrl(true)
} }
}
} else { } else {
setState { copy(webviewLoadedUrl = Fail(Throwable(errorDescription))) } setState { copy(webviewLoadedUrl = Fail(Throwable(errorDescription))) }
} }
@ -172,14 +178,27 @@ class RoomWidgetViewModel @AssistedInject constructor(@Assisted val initialState
override fun onCleared() { override fun onCleared() {
super.onCleared() super.onCleared()
integrationManagerService.removeListener(this)
postAPIMediator.setHandler(null) postAPIMediator.setHandler(null)
} }
// IntegrationManagerService.Listener
override fun onWidgetPermissionsChanged(widgets: Map<String, Boolean>) {
refreshPermissionStatus()
}
// WidgetPostAPIHandler.NavigationCallback
override fun close() { 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?) { override fun openIntegrationManager(integId: String?, integType: String?) {
_viewEvents.post(RoomWidgetViewEvents.DisplayIntegrationManager(integId, integType)) _viewEvents.post(WidgetViewEvents.DisplayIntegrationManager(integId, integType))
} }
} }

View file

@ -14,7 +14,7 @@
* limitations under the License. * 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.Async
import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.MvRxState

View file

@ -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")
}
}

View file

@ -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

View file

@ -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) {
}
}

View file

@ -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

View 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" />