From adbfde94d67116912713fcf6ce4f47b423e1926f Mon Sep 17 00:00:00 2001 From: Valere <valeref@matrix.org> Date: Tue, 4 Jun 2019 17:14:12 +0200 Subject: [PATCH 01/18] Fix / move read receipt on m.replace events --- .../features/home/room/detail/RoomDetailViewModel.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt index b02a71e8da..7a47cff0f6 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt @@ -350,6 +350,12 @@ class RoomDetailViewModel(initialState: RoomDetailViewState, private fun handleEventDisplayed(action: RoomDetailActions.EventDisplayed) { displayedEventsObservable.accept(action) + //We need to update this with the related m.replace also (to move read receipt) + action.event.annotations?.editSummary?.sourceEvents?.forEach { + room.getTimeLineEvent(it)?.let {event -> + displayedEventsObservable.accept(RoomDetailActions.EventDisplayed(event)) + } + } } private fun handleLoadMore(action: RoomDetailActions.LoadMore) { From 53c91dc0c28a0f72623c74e347c014f071254129 Mon Sep 17 00:00:00 2001 From: Valere <valeref@matrix.org> Date: Tue, 4 Jun 2019 17:14:52 +0200 Subject: [PATCH 02/18] Ignore server aggregation until API ready --- .../room/EventRelationsAggregationTask.kt | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationTask.kt index 4ff661e237..a66b65ab71 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationTask.kt @@ -44,6 +44,9 @@ internal interface EventRelationsAggregationTask : Task<EventRelationsAggregatio */ internal class DefaultEventRelationsAggregationTask(private val monarchy: Monarchy) : EventRelationsAggregationTask { + //OPT OUT serer aggregation until API mature enough + private val SHOULD_HANDLE_SERVER_AGREGGATION = false + override fun execute(params: EventRelationsAggregationTask.Params): Try<Unit> { return monarchy.tryTransactionAsync { realm -> update(realm, params.events, params.userId) @@ -155,20 +158,22 @@ internal class DefaultEventRelationsAggregationTask(private val monarchy: Monarc } private fun handleInitialAggregatedRelations(event: Event, roomId: String, aggregation: AggregatedAnnotation, realm: Realm) { - aggregation.chunk?.forEach { - if (it.type == EventType.REACTION) { - val eventId = event.eventId ?: "" - val existing = EventAnnotationsSummaryEntity.where(realm, eventId).findFirst() - if (existing == null) { - val eventSummary = EventAnnotationsSummaryEntity.create(realm, eventId) - eventSummary.roomId = roomId - val sum = realm.createObject(ReactionAggregatedSummaryEntity::class.java) - sum.key = it.key - sum.firstTimestamp = event.originServerTs ?: 0 //TODO how to maintain order? - sum.count = it.count - eventSummary.reactionsSummary.add(sum) - } else { - //TODO how to handle that + if (SHOULD_HANDLE_SERVER_AGREGGATION) { + aggregation.chunk?.forEach { + if (it.type == EventType.REACTION) { + val eventId = event.eventId ?: "" + val existing = EventAnnotationsSummaryEntity.where(realm, eventId).findFirst() + if (existing == null) { + val eventSummary = EventAnnotationsSummaryEntity.create(realm, eventId) + eventSummary.roomId = roomId + val sum = realm.createObject(ReactionAggregatedSummaryEntity::class.java) + sum.key = it.key + sum.firstTimestamp = event.originServerTs ?: 0 //TODO how to maintain order? + sum.count = it.count + eventSummary.reactionsSummary.add(sum) + } else { + //TODO how to handle that + } } } } From d2f648edec84a0ff0424738395f7c42c39b08a19 Mon Sep 17 00:00:00 2001 From: Valere <valeref@matrix.org> Date: Tue, 4 Jun 2019 17:16:02 +0200 Subject: [PATCH 03/18] Use Font emoji compat for quickReactions and pills --- .../riotredesign/EmojiCompatFontProvider.kt | 48 +++++++++++++++++ .../vector/riotredesign/VectorApplication.kt | 29 +++++++++- .../vector/riotredesign/core/di/AppModule.kt | 5 ++ .../riotredesign/features/home/HomeModule.kt | 3 +- .../timeline/action/QuickReactionFragment.kt | 17 ++++-- .../timeline/factory/MessageItemFactory.kt | 33 +++++++----- .../detail/timeline/item/AbsMessageItem.kt | 5 ++ .../reactions/EmojiReactionPickerActivity.kt | 54 ++++++------------- .../reactions/widget/ReactionButton.kt | 14 +++-- 9 files changed, 149 insertions(+), 59 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotredesign/EmojiCompatFontProvider.kt diff --git a/vector/src/main/java/im/vector/riotredesign/EmojiCompatFontProvider.kt b/vector/src/main/java/im/vector/riotredesign/EmojiCompatFontProvider.kt new file mode 100644 index 0000000000..a928d48b5f --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/EmojiCompatFontProvider.kt @@ -0,0 +1,48 @@ +package im.vector.riotredesign + +import android.graphics.Typeface +import androidx.core.provider.FontsContractCompat +import timber.log.Timber + + +class EmojiCompatFontProvider : FontsContractCompat.FontRequestCallback() { + + var typeface: Typeface? = null + set(value) { + if (value != field) { + field = value + listeners.forEach { + try { + it.compatibilityFontUpdate(value) + } catch (t: Throwable) { + Timber.e(t) + } + } + } + } + + private val listeners = ArrayList<FontProviderListener>() + + override fun onTypefaceRetrieved(typeface: Typeface) { + this.typeface = typeface + } + + override fun onTypefaceRequestFailed(reason: Int) { + Timber.e("Failed to load Emoji Compatible font, reason:$reason") + } + + fun addListener(listener: FontProviderListener) { + if (!listeners.contains(listener)) { + listeners.add(listener) + } + } + + fun removeListener(listener: FontProviderListener) { + listeners.remove(listener) + } + + + interface FontProviderListener { + fun compatibilityFontUpdate(typeface: Typeface?) + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/VectorApplication.kt b/vector/src/main/java/im/vector/riotredesign/VectorApplication.kt index 88cee67496..e1d85fe3d6 100644 --- a/vector/src/main/java/im/vector/riotredesign/VectorApplication.kt +++ b/vector/src/main/java/im/vector/riotredesign/VectorApplication.kt @@ -18,6 +18,10 @@ package im.vector.riotredesign import android.app.Application import android.content.Context +import android.os.Handler +import android.os.HandlerThread +import androidx.core.provider.FontRequest +import androidx.core.provider.FontsContractCompat import android.content.res.Configuration import androidx.multidex.MultiDex import com.airbnb.epoxy.EpoxyAsyncUtil @@ -41,6 +45,10 @@ import timber.log.Timber class VectorApplication : Application() { + //font thread handler + private var mFontThreadHandler: Handler? = null + + val vectorConfiguration: VectorConfiguration by inject() override fun onCreate() { @@ -63,10 +71,20 @@ class VectorApplication : Application() { val appModule = AppModule(applicationContext).definition val homeModule = HomeModule().definition val roomDirectoryModule = RoomDirectoryModule().definition - startKoin(listOf(appModule, homeModule, roomDirectoryModule), logger = EmptyLogger()) + val koin = startKoin(listOf(appModule, homeModule, roomDirectoryModule), logger = EmptyLogger()) Matrix.getInstance().setApplicationFlavor(BuildConfig.FLAVOR_DESCRIPTION) + val fontRequest = FontRequest( + "com.google.android.gms.fonts", + "com.google.android.gms", + "Noto Color Emoji Compat", + R.array.com_google_android_gms_fonts_certs + ) + +// val efp = koin.koinContext.get<EmojiCompatFontProvider>() + FontsContractCompat.requestFont(this, fontRequest, koin.koinContext.get<EmojiCompatFontProvider>(), getFontThreadHandler()) + vectorConfiguration.initConfiguration() } @@ -81,4 +99,13 @@ class VectorApplication : Application() { vectorConfiguration.onConfigurationChanged(newConfig) } + private fun getFontThreadHandler(): Handler { + if (mFontThreadHandler == null) { + val handlerThread = HandlerThread("fonts") + handlerThread.start() + mFontThreadHandler = Handler(handlerThread.looper) + } + return mFontThreadHandler!! + } + } \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/core/di/AppModule.kt b/vector/src/main/java/im/vector/riotredesign/core/di/AppModule.kt index dc3068cfa3..542c0953e7 100644 --- a/vector/src/main/java/im/vector/riotredesign/core/di/AppModule.kt +++ b/vector/src/main/java/im/vector/riotredesign/core/di/AppModule.kt @@ -20,6 +20,7 @@ import android.content.Context import android.content.Context.MODE_PRIVATE import androidx.fragment.app.Fragment import im.vector.matrix.android.api.Matrix +import im.vector.riotredesign.EmojiCompatFontProvider import im.vector.riotredesign.core.error.ErrorFormatter import im.vector.riotredesign.core.resources.LocaleProvider import im.vector.riotredesign.core.resources.StringArrayProvider @@ -90,5 +91,9 @@ class AppModule(private val context: Context) { DefaultNavigator(fragment) as Navigator } + single { + EmojiCompatFontProvider() + } + } } \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt b/vector/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt index d6297b6ff6..ce985abbf2 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt @@ -72,7 +72,8 @@ class HomeModule { val timelineMediaSizeProvider = TimelineMediaSizeProvider() val colorProvider = ColorProvider(fragment.requireContext()) val timelineDateFormatter = get<TimelineDateFormatter>() - val messageItemFactory = MessageItemFactory(colorProvider, timelineMediaSizeProvider, timelineDateFormatter, eventHtmlRenderer, get()) + val messageItemFactory = MessageItemFactory(colorProvider, timelineMediaSizeProvider, + timelineDateFormatter, eventHtmlRenderer, get(), get()) val timelineItemFactory = TimelineItemFactory( messageItemFactory = messageItemFactory, diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/QuickReactionFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/QuickReactionFragment.kt index eac10a9044..2d601544c8 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/QuickReactionFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/QuickReactionFragment.kt @@ -15,6 +15,7 @@ */ package im.vector.riotredesign.features.home.room.detail.timeline.action +import android.graphics.Typeface import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -28,7 +29,9 @@ import com.airbnb.mvrx.BaseMvRxFragment import com.airbnb.mvrx.MvRx import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState +import im.vector.riotredesign.EmojiCompatFontProvider import im.vector.riotredesign.R +import org.koin.android.ext.android.inject /** * Quick Reaction Fragment (agree / like reactions) @@ -54,6 +57,8 @@ class QuickReactionFragment : BaseMvRxFragment() { var interactionListener: InteractionListener? = null + val fontProvider by inject<EmojiCompatFontProvider>() + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { val view = inflater.inflate(R.layout.adapter_item_action_quick_reaction, container, false) ButterKnife.bind(this, view) @@ -68,6 +73,10 @@ class QuickReactionFragment : BaseMvRxFragment() { quickReact3Text.text = QuickReactionViewModel.likePositive quickReact4Text.text = QuickReactionViewModel.likeNegative + listOf(quickReact1Text, quickReact2Text, quickReact3Text, quickReact4Text).forEach { + it.typeface = fontProvider.typeface ?: Typeface.DEFAULT + } + //configure click listeners quickReact1Text.setOnClickListener { viewModel.toggleAgree(true) @@ -88,11 +97,11 @@ class QuickReactionFragment : BaseMvRxFragment() { TransitionManager.beginDelayedTransition(rootLayout) when (it.agreeTrigleState) { - TriggleState.NONE -> { + TriggleState.NONE -> { quickReact1Text.alpha = 1f quickReact2Text.alpha = 1f } - TriggleState.FIRST -> { + TriggleState.FIRST -> { quickReact1Text.alpha = 1f quickReact2Text.alpha = 0.2f @@ -103,11 +112,11 @@ class QuickReactionFragment : BaseMvRxFragment() { } } when (it.likeTriggleState) { - TriggleState.NONE -> { + TriggleState.NONE -> { quickReact3Text.alpha = 1f quickReact4Text.alpha = 1f } - TriggleState.FIRST -> { + TriggleState.FIRST -> { quickReact3Text.alpha = 1f quickReact4Text.alpha = 0.2f diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt index d61848af6d..7571fead47 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -33,6 +33,7 @@ import im.vector.matrix.android.api.session.room.model.EditAggregatedSummary import im.vector.matrix.android.api.session.room.model.message.* import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.api.session.room.timeline.TimelineEvent +import im.vector.riotredesign.EmojiCompatFontProvider import im.vector.riotredesign.R import im.vector.riotredesign.core.epoxy.VectorEpoxyModel import im.vector.riotredesign.core.extensions.localDateTime @@ -55,7 +56,8 @@ class MessageItemFactory(private val colorProvider: ColorProvider, private val timelineMediaSizeProvider: TimelineMediaSizeProvider, private val timelineDateFormatter: TimelineDateFormatter, private val htmlRenderer: EventHtmlRenderer, - private val stringProvider: StringProvider) { + private val stringProvider: StringProvider, + private val emojiCompatFontProvider: EmojiCompatFontProvider) { fun create(event: TimelineEvent, nextEvent: TimelineEvent?, @@ -115,24 +117,24 @@ class MessageItemFactory(private val colorProvider: ColorProvider, // val all = event.root.toContent() // val ev = all.toModel<Event>() return when (messageContent) { - is MessageEmoteContent -> buildEmoteMessageItem(messageContent, + is MessageEmoteContent -> buildEmoteMessageItem(messageContent, informationData, hasBeenEdited, event.annotations?.editSummary, callback) - is MessageTextContent -> buildTextMessageItem(event.sendState, + is MessageTextContent -> buildTextMessageItem(event.sendState, messageContent, informationData, hasBeenEdited, event.annotations?.editSummary, callback ) - is MessageImageContent -> buildImageMessageItem(messageContent, informationData, callback) + is MessageImageContent -> buildImageMessageItem(messageContent, informationData, callback) is MessageNoticeContent -> buildNoticeMessageItem(messageContent, informationData, callback) - is MessageVideoContent -> buildVideoMessageItem(messageContent, informationData, callback) - is MessageFileContent -> buildFileMessageItem(messageContent, informationData, callback) - is MessageAudioContent -> buildAudioMessageItem(messageContent, informationData, callback) - else -> buildNotHandledMessageItem(messageContent) + is MessageVideoContent -> buildVideoMessageItem(messageContent, informationData, callback) + is MessageFileContent -> buildFileMessageItem(messageContent, informationData, callback) + is MessageAudioContent -> buildAudioMessageItem(messageContent, informationData, callback) + else -> buildNotHandledMessageItem(messageContent) } } @@ -144,20 +146,21 @@ class MessageItemFactory(private val colorProvider: ColorProvider, .filename(messageContent.body) .iconRes(R.drawable.filetype_audio) .reactionPillCallback(callback) + .emojiTypeFace(emojiCompatFontProvider.typeface) .avatarClickListener( - DebouncedClickListener(View.OnClickListener { view -> + DebouncedClickListener(View.OnClickListener { callback?.onAvatarClicked(informationData) })) .memberClickListener( - DebouncedClickListener(View.OnClickListener { view -> + DebouncedClickListener(View.OnClickListener { callback?.onMemberNameClicked(informationData) })) .cellClickListener( - DebouncedClickListener(View.OnClickListener { view -> + DebouncedClickListener(View.OnClickListener { view: View -> callback?.onEventCellClicked(informationData, messageContent, view) })) .clickListener( - DebouncedClickListener(View.OnClickListener { _ -> + DebouncedClickListener(View.OnClickListener { callback?.onAudioMessageClicked(messageContent) })) .longClickListener { view -> @@ -173,6 +176,7 @@ class MessageItemFactory(private val colorProvider: ColorProvider, .informationData(informationData) .filename(messageContent.body) .reactionPillCallback(callback) + .emojiTypeFace(emojiCompatFontProvider.typeface) .iconRes(R.drawable.filetype_attachment) .avatarClickListener( DebouncedClickListener(View.OnClickListener { view -> @@ -221,6 +225,7 @@ class MessageItemFactory(private val colorProvider: ColorProvider, .informationData(informationData) .mediaData(data) .reactionPillCallback(callback) + .emojiTypeFace(emojiCompatFontProvider.typeface) .avatarClickListener( DebouncedClickListener(View.OnClickListener { view -> callback?.onAvatarClicked(informationData) @@ -268,6 +273,7 @@ class MessageItemFactory(private val colorProvider: ColorProvider, .informationData(informationData) .mediaData(thumbnailData) .reactionPillCallback(callback) + .emojiTypeFace(emojiCompatFontProvider.typeface) .avatarClickListener( DebouncedClickListener(View.OnClickListener { view -> callback?.onAvatarClicked(informationData) @@ -311,6 +317,7 @@ class MessageItemFactory(private val colorProvider: ColorProvider, } .informationData(informationData) .reactionPillCallback(callback) + .emojiTypeFace(emojiCompatFontProvider.typeface) .avatarClickListener( DebouncedClickListener(View.OnClickListener { view -> callback?.onAvatarClicked(informationData) @@ -384,6 +391,7 @@ class MessageItemFactory(private val colorProvider: ColorProvider, .message(message) .informationData(informationData) .reactionPillCallback(callback) + .emojiTypeFace(emojiCompatFontProvider.typeface) .avatarClickListener( DebouncedClickListener(View.OnClickListener { view -> callback?.onAvatarClicked(informationData) @@ -423,6 +431,7 @@ class MessageItemFactory(private val colorProvider: ColorProvider, } .informationData(informationData) .reactionPillCallback(callback) + .emojiTypeFace(emojiCompatFontProvider.typeface) .avatarClickListener( DebouncedClickListener(View.OnClickListener { view -> callback?.onAvatarClicked(informationData) diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/AbsMessageItem.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/AbsMessageItem.kt index 0ce01a88fc..b74f7bcac5 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/AbsMessageItem.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/AbsMessageItem.kt @@ -16,6 +16,7 @@ package im.vector.riotredesign.features.home.room.detail.timeline.item +import android.graphics.Typeface import android.os.Build import android.view.View import android.view.ViewGroup @@ -50,6 +51,9 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() { @EpoxyAttribute var memberClickListener: View.OnClickListener? = null + @EpoxyAttribute + var emojiTypeFace: Typeface? = null + @EpoxyAttribute var reactionPillCallback: TimelineEventController.ReactionPillCallback? = null @@ -116,6 +120,7 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() { idToRefInFlow.add(reactionButton.id) reactionButton.reactionString = reaction.key reactionButton.reactionCount = reaction.count + reactionButton.emojiTypeFace = emojiTypeFace reactionButton.setChecked(reaction.addedByMe) reactionButton.isEnabled = reaction.synced } diff --git a/vector/src/main/java/im/vector/riotredesign/features/reactions/EmojiReactionPickerActivity.kt b/vector/src/main/java/im/vector/riotredesign/features/reactions/EmojiReactionPickerActivity.kt index a75accd9ba..7ad57d5368 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/reactions/EmojiReactionPickerActivity.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/reactions/EmojiReactionPickerActivity.kt @@ -19,23 +19,20 @@ import android.app.Activity import android.content.Context import android.content.Intent import android.graphics.Typeface -import android.os.Handler -import android.os.HandlerThread import android.util.TypedValue import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.widget.SearchView import androidx.appcompat.widget.Toolbar -import androidx.core.provider.FontRequest -import androidx.core.provider.FontsContractCompat import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProviders import com.google.android.material.tabs.TabLayout +import im.vector.riotredesign.EmojiCompatFontProvider import im.vector.riotredesign.R import im.vector.riotredesign.core.platform.VectorBaseActivity import kotlinx.android.synthetic.main.activity_emoji_reaction_picker.* -import timber.log.Timber +import org.koin.android.ext.android.inject /** * @@ -44,20 +41,21 @@ import timber.log.Timber * TODO: Finish Refactor to vector base activity * TODO: Move font request to app */ -class EmojiReactionPickerActivity : VectorBaseActivity() { +class EmojiReactionPickerActivity : VectorBaseActivity(), EmojiCompatFontProvider.FontProviderListener { + private lateinit var tabLayout: TabLayout lateinit var viewModel: EmojiChooserViewModel - private var mHandler: Handler? = null - override fun getMenuRes(): Int = R.menu.menu_emoji_reaction_picker override fun getLayoutRes(): Int = R.layout.activity_emoji_reaction_picker override fun getTitleRes(): Int = R.string.title_activity_emoji_reaction_picker + val emojiCompatFontProvider by inject<EmojiCompatFontProvider>() + private var tabLayoutSelectionListener = object : TabLayout.BaseOnTabSelectedListener<TabLayout.Tab> { override fun onTabReselected(p0: TabLayout.Tab) { } @@ -71,19 +69,13 @@ class EmojiReactionPickerActivity : VectorBaseActivity() { } - private fun getFontThreadHandler(): Handler { - if (mHandler == null) { - val handlerThread = HandlerThread("fonts") - handlerThread.start() - mHandler = Handler(handlerThread.looper) - } - return mHandler!! - } - override fun initUiAndData() { configureToolbar(emojiPickerToolbar) - requestEmojivUnicode10CompatibleFont() + emojiCompatFontProvider.let { + EmojiDrawView.configureTextPaint(this, it.typeface) + it.addListener(this) + } tabLayout = findViewById(R.id.tabs) @@ -124,27 +116,13 @@ class EmojiReactionPickerActivity : VectorBaseActivity() { }) } - private fun requestEmojivUnicode10CompatibleFont() { - val fontRequest = FontRequest( - "com.google.android.gms.fonts", - "com.google.android.gms", - "Noto Color Emoji Compat", - R.array.com_google_android_gms_fonts_certs - ) + override fun compatibilityFontUpdate(typeface: Typeface?) { + EmojiDrawView.configureTextPaint(this, typeface) + } - EmojiDrawView.configureTextPaint(this, null) - val callback = object : FontsContractCompat.FontRequestCallback() { - - override fun onTypefaceRetrieved(typeface: Typeface) { - EmojiDrawView.configureTextPaint(this@EmojiReactionPickerActivity, typeface) - } - - override fun onTypefaceRequestFailed(reason: Int) { - Timber.e("Failed to load Emoji Compatible font, reason:$reason") - } - } - - FontsContractCompat.requestFont(this, fontRequest, callback, getFontThreadHandler()) + override fun onDestroy() { + emojiCompatFontProvider.removeListener(this) + super.onDestroy() } override fun onCreateOptionsMenu(menu: Menu): Boolean { diff --git a/vector/src/main/java/im/vector/riotredesign/features/reactions/widget/ReactionButton.kt b/vector/src/main/java/im/vector/riotredesign/features/reactions/widget/ReactionButton.kt index c4a87d0fe7..b0bf08e383 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/reactions/widget/ReactionButton.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/reactions/widget/ReactionButton.kt @@ -21,6 +21,7 @@ import android.animation.AnimatorSet import android.animation.ObjectAnimator import android.content.Context import android.content.res.TypedArray +import android.graphics.Typeface import android.graphics.drawable.Drawable import android.util.AttributeSet import android.view.LayoutInflater @@ -56,6 +57,11 @@ class ReactionButton @JvmOverloads constructor(context: Context, attrs: Attribut private var reactionSelector: View? = null + var emojiTypeFace: Typeface? = null + set(value) { + field = value + emojiView?.typeface = value ?: Typeface.DEFAULT + } private var dotsView: DotsView private var circleView: CircleView @@ -97,6 +103,8 @@ class ReactionButton @JvmOverloads constructor(context: Context, attrs: Attribut countTextView?.text = reactionCount.toString() + emojiView?.typeface = this.emojiTypeFace ?: Typeface.DEFAULT + val array = context.obtainStyledAttributes(attrs, R.styleable.ReactionButton, defStyleAttr, 0) onDrawable = ContextCompat.getDrawable(context, R.drawable.rounded_rect_shape) @@ -239,7 +247,7 @@ class ReactionButton @JvmOverloads constructor(context: Context, attrs: Attribut return true when (event.action) { - MotionEvent.ACTION_DOWN -> + MotionEvent.ACTION_DOWN -> /* Commented out this line and moved the animation effect to the action up event due to conflicts that were occurring when library is used in sliding type views. @@ -248,7 +256,7 @@ class ReactionButton @JvmOverloads constructor(context: Context, attrs: Attribut */ isPressed = true - MotionEvent.ACTION_MOVE -> { + MotionEvent.ACTION_MOVE -> { val x = event.x val y = event.y val isInside = x > 0 && x < width && y > 0 && y < height @@ -257,7 +265,7 @@ class ReactionButton @JvmOverloads constructor(context: Context, attrs: Attribut } } - MotionEvent.ACTION_UP -> { + MotionEvent.ACTION_UP -> { emojiView!!.animate().scaleX(0.7f).scaleY(0.7f).setDuration(150).interpolator = DECCELERATE_INTERPOLATOR emojiView!!.animate().scaleX(1f).scaleY(1f).interpolator = DECCELERATE_INTERPOLATOR if (isPressed) { From 440442bb99b45cfb248343f82bb221707ac15d4c Mon Sep 17 00:00:00 2001 From: Valere <valeref@matrix.org> Date: Wed, 5 Jun 2019 19:23:57 +0200 Subject: [PATCH 04/18] New View Reactions bottom sheet + visible on reaction long click + Reaction pills size adapt to count, and number format --- .../room/model/relation/RelationService.kt | 3 + .../room/relation/DefaultRelationService.kt | 16 +++ vector/sampledata/reactions.json | 22 ++++ .../riotredesign/core/utils/TextUtils.kt | 29 +++++ .../home/room/detail/RoomDetailFragment.kt | 12 +- .../timeline/TimelineEventController.kt | 1 + .../action/MessageActionsBottomSheet.kt | 15 +-- .../action/MessageActionsViewModel.kt | 3 +- .../timeline/action/MessageMenuFragment.kt | 2 +- .../timeline/action/MessageMenuViewModel.kt | 2 +- .../timeline/action/QuickReactionFragment.kt | 2 +- .../timeline/action/QuickReactionViewModel.kt | 2 +- .../timeline/action/ReactionInfoSimpleItem.kt | 39 +++++++ .../action/TimelineEventFragmentArgs.kt | 12 ++ .../action/ViewReactionBottomSheet.kt | 71 ++++++++++++ .../timeline/action/ViewReactionViewModel.kt | 106 ++++++++++++++++++ .../action/ViewReactionsEpoxyController.kt | 19 ++++ .../detail/timeline/item/AbsMessageItem.kt | 6 +- .../reactions/widget/ReactionButton.kt | 80 +++++++------ .../layout/bottom_sheet_display_reactions.xml | 40 +++++++ .../res/layout/item_simple_reaction_info.xml | 45 ++++++++ .../src/main/res/layout/reaction_button.xml | 32 ++++-- vector/src/main/res/values/strings_riotX.xml | 1 + 23 files changed, 492 insertions(+), 68 deletions(-) create mode 100644 vector/sampledata/reactions.json create mode 100644 vector/src/main/java/im/vector/riotredesign/core/utils/TextUtils.kt create mode 100644 vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ReactionInfoSimpleItem.kt create mode 100644 vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/TimelineEventFragmentArgs.kt create mode 100644 vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ViewReactionBottomSheet.kt create mode 100644 vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ViewReactionViewModel.kt create mode 100644 vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ViewReactionsEpoxyController.kt create mode 100644 vector/src/main/res/layout/bottom_sheet_display_reactions.xml create mode 100644 vector/src/main/res/layout/item_simple_reaction_info.xml diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/relation/RelationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/relation/RelationService.kt index bc92472892..ac08b64f53 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/relation/RelationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/relation/RelationService.kt @@ -15,7 +15,9 @@ */ package im.vector.matrix.android.api.session.room.model.relation +import androidx.lifecycle.LiveData import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary import im.vector.matrix.android.api.util.Cancelable /** @@ -91,4 +93,5 @@ interface RelationService { */ fun replyToMessage(eventReplied: Event, replyText: String): Cancelable? + fun getEventSummaryLive(eventId: String): LiveData<List<EventAnnotationsSummary>> } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/DefaultRelationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/DefaultRelationService.kt index 6d6e4763d2..264909e956 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/DefaultRelationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/DefaultRelationService.kt @@ -15,15 +15,19 @@ */ package im.vector.matrix.android.internal.session.room.relation +import androidx.lifecycle.LiveData import androidx.work.OneTimeWorkRequest import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary import im.vector.matrix.android.api.session.room.model.message.MessageType import im.vector.matrix.android.api.session.room.model.relation.RelationService import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.internal.database.helper.addSendingEvent +import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.model.ChunkEntity +import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom import im.vector.matrix.android.internal.database.query.where @@ -169,6 +173,18 @@ internal class DefaultRelationService(private val roomId: String, return CancelableWork(workRequest.id) } + + override fun getEventSummaryLive(eventId: String): LiveData<List<EventAnnotationsSummary>> { + return monarchy.findAllMappedWithChanges( + { realm -> + EventAnnotationsSummaryEntity.where(realm, eventId) + }, + { + it.asDomain() + } + ) + } + /** * Saves the event in database as a local echo. * SendState is set to UNSENT and it's added to a the sendingTimelineEvents list of the room. diff --git a/vector/sampledata/reactions.json b/vector/sampledata/reactions.json new file mode 100644 index 0000000000..e2c8e4f4cd --- /dev/null +++ b/vector/sampledata/reactions.json @@ -0,0 +1,22 @@ +{ + "data": [ + { + "reaction" : "👍" + }, + { + "reaction" : "😀" + }, + { + "reaction" : "😞" + }, + { + "reaction" : "Not a reaction" + }, + { + "reaction" : "✅" + }, + { + "reaction" : "🎉" + } + ] +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/core/utils/TextUtils.kt b/vector/src/main/java/im/vector/riotredesign/core/utils/TextUtils.kt new file mode 100644 index 0000000000..558275565a --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/core/utils/TextUtils.kt @@ -0,0 +1,29 @@ +package im.vector.riotredesign.core.utils + +import java.util.* + +object TextUtils { + + private val suffixes = TreeMap<Int, String>().also { + it.put(1000, "k") + it.put(1000000, "M") + it.put(1000000000, "G") + } + + fun formatCountToShortDecimal(value: Int): String { + try { + if (value < 0) return "-" + formatCountToShortDecimal(-value) + if (value < 1000) return value.toString() //deal with easy case + + val e = suffixes.floorEntry(value) + val divideBy = e.key + val suffix = e.value + + val truncated = value / (divideBy!! / 10) //the number part of the output times 10 + val hasDecimal = truncated < 100 && truncated / 10.0 != (truncated / 10).toDouble() + return if (hasDecimal) "${truncated / 10.0}$suffix" else "${truncated / 10}$suffix" + } catch (t: Throwable) { + return value.toString() + } + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt index 4653d75166..5ab0e46c94 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt @@ -84,6 +84,7 @@ import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventCo import im.vector.riotredesign.features.home.room.detail.timeline.action.ActionsHandler import im.vector.riotredesign.features.home.room.detail.timeline.action.MessageActionsBottomSheet import im.vector.riotredesign.features.home.room.detail.timeline.action.MessageMenuViewModel +import im.vector.riotredesign.features.home.room.detail.timeline.action.ViewReactionBottomSheet import im.vector.riotredesign.features.home.room.detail.timeline.helper.EndlessRecyclerViewScrollListener import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData import im.vector.riotredesign.features.html.PillImageSpan @@ -235,11 +236,13 @@ class RoomDetailFragment : var formattedBody: CharSequence? = null if (messageContent is MessageTextContent && messageContent.format == MessageType.FORMAT_MATRIX_HTML) { val parser = Parser.builder().build() - val document = parser.parse(messageContent.formattedBody ?: messageContent.body) + val document = parser.parse(messageContent.formattedBody + ?: messageContent.body) formattedBody = Markwon.builder(requireContext()) .usePlugin(HtmlPlugin.create()).build().render(document) } - composerLayout.composerRelatedMessageContent.text = formattedBody ?: nonFormattedBody + composerLayout.composerRelatedMessageContent.text = formattedBody + ?: nonFormattedBody if (mode == SendMode.EDIT) { @@ -593,6 +596,11 @@ class RoomDetailFragment : } } + override fun onLongClickOnReactionPill(informationData: MessageInformationData, reaction: String) { + ViewReactionBottomSheet.newInstance(roomDetailArgs.roomId, informationData) + .show(requireActivity().supportFragmentManager, "DISPLAY_REACTIONS") + } + override fun onEditedDecorationClicked(informationData: MessageInformationData, editAggregatedSummary: EditAggregatedSummary?) { editAggregatedSummary?.also { roomDetailViewModel.process(RoomDetailActions.ShowEditHistoryAction(informationData.eventId, it)) diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt index 84f970471e..175702cfa8 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt @@ -62,6 +62,7 @@ class TimelineEventController(private val dateFormatter: TimelineDateFormatter, interface ReactionPillCallback { fun onClickOnReactionPill(informationData: MessageInformationData, reaction: String, on: Boolean) + fun onLongClickOnReactionPill(informationData: MessageInformationData, reaction: String) } private val collapsedEventIds = linkedSetOf<String>() diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt index 44a753afbf..fa94043ed4 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt @@ -17,7 +17,6 @@ package im.vector.riotredesign.features.home.room.detail.timeline.action import android.app.Dialog import android.os.Bundle -import android.os.Parcelable import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -36,7 +35,6 @@ import im.vector.riotredesign.R import im.vector.riotredesign.core.glide.GlideApp import im.vector.riotredesign.features.home.AvatarRenderer import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData -import kotlinx.android.parcel.Parcelize /** * Bottom sheet fragment that shows a message preview with list of contextual actions @@ -74,7 +72,7 @@ class MessageActionsBottomSheet : BaseMvRxBottomSheetDialog() { val cfm = childFragmentManager var menuActionFragment = cfm.findFragmentByTag("MenuActionFragment") as? MessageMenuFragment if (menuActionFragment == null) { - menuActionFragment = MessageMenuFragment.newInstance(arguments!!.get(MvRx.KEY_ARG) as ParcelableArgs) + menuActionFragment = MessageMenuFragment.newInstance(arguments!!.get(MvRx.KEY_ARG) as TimelineEventFragmentArgs) cfm.beginTransaction() .replace(R.id.bottom_sheet_menu_container, menuActionFragment, "MenuActionFragment") .commit() @@ -89,7 +87,7 @@ class MessageActionsBottomSheet : BaseMvRxBottomSheetDialog() { var quickReactionFragment = cfm.findFragmentByTag("QuickReaction") as? QuickReactionFragment if (quickReactionFragment == null) { - quickReactionFragment = QuickReactionFragment.newInstance(arguments!!.get(MvRx.KEY_ARG) as ParcelableArgs) + quickReactionFragment = QuickReactionFragment.newInstance(arguments!!.get(MvRx.KEY_ARG) as TimelineEventFragmentArgs) cfm.beginTransaction() .replace(R.id.bottom_sheet_quick_reaction_container, quickReactionFragment, "QuickReaction") .commit() @@ -135,18 +133,11 @@ class MessageActionsBottomSheet : BaseMvRxBottomSheetDialog() { } - @Parcelize - data class ParcelableArgs( - val eventId: String, - val roomId: String, - val informationData: MessageInformationData - ) : Parcelable - companion object { fun newInstance(roomId: String, informationData: MessageInformationData): MessageActionsBottomSheet { return MessageActionsBottomSheet().apply { setArguments( - ParcelableArgs( + TimelineEventFragmentArgs( informationData.eventId, roomId, informationData diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index 9198f8ee83..a307afe35a 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -25,7 +25,6 @@ import im.vector.matrix.android.api.session.room.model.message.MessageTextConten import im.vector.matrix.android.api.session.room.model.message.MessageType import im.vector.riotredesign.core.platform.VectorViewModel import org.commonmark.parser.Parser -import org.commonmark.renderer.html.HtmlRenderer import org.koin.android.ext.android.get import ru.noties.markwon.Markwon import ru.noties.markwon.html.HtmlPlugin @@ -51,7 +50,7 @@ class MessageActionsViewModel(initialState: MessageActionState) : VectorViewMode override fun initialState(viewModelContext: ViewModelContext): MessageActionState? { val currentSession = viewModelContext.activity.get<Session>() - val parcel = viewModelContext.args as MessageActionsBottomSheet.ParcelableArgs + val parcel = viewModelContext.args as TimelineEventFragmentArgs val dateFormat = SimpleDateFormat("EEE, d MMM yyyy HH:mm", Locale.getDefault()) diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuFragment.kt index 3009f5fc67..2b47eae327 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuFragment.kt @@ -101,7 +101,7 @@ class MessageMenuFragment : BaseMvRxFragment() { companion object { - fun newInstance(pa: MessageActionsBottomSheet.ParcelableArgs): MessageMenuFragment { + fun newInstance(pa: TimelineEventFragmentArgs): MessageMenuFragment { val args = Bundle() args.putParcelable(MvRx.KEY_ARG, pa) val fragment = MessageMenuFragment() diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt index 86ddf8665d..776be95042 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt @@ -46,7 +46,7 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes override fun initialState(viewModelContext: ViewModelContext): MessageMenuState? { // Args are accessible from the context. val currentSession = viewModelContext.activity.get<Session>() - val parcel = viewModelContext.args as MessageActionsBottomSheet.ParcelableArgs + val parcel = viewModelContext.args as TimelineEventFragmentArgs val event = currentSession.getRoom(parcel.roomId)?.getTimeLineEvent(parcel.eventId) ?: return null diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/QuickReactionFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/QuickReactionFragment.kt index 2d601544c8..b10744809a 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/QuickReactionFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/QuickReactionFragment.kt @@ -139,7 +139,7 @@ class QuickReactionFragment : BaseMvRxFragment() { } companion object { - fun newInstance(pa: MessageActionsBottomSheet.ParcelableArgs): QuickReactionFragment { + fun newInstance(pa: TimelineEventFragmentArgs): QuickReactionFragment { val args = Bundle() args.putParcelable(MvRx.KEY_ARG, pa) val fragment = QuickReactionFragment() diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/QuickReactionViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/QuickReactionViewModel.kt index 36a07bee59..89976248b8 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/QuickReactionViewModel.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/QuickReactionViewModel.kt @@ -124,7 +124,7 @@ class QuickReactionViewModel(initialState: QuickReactionState) : VectorViewModel // Args are accessible from the context. // val foo = vieWModelContext.args<MyArgs>.foo val currentSession = viewModelContext.activity.get<Session>() - val parcel = viewModelContext.args as MessageActionsBottomSheet.ParcelableArgs + val parcel = viewModelContext.args as TimelineEventFragmentArgs val event = currentSession.getRoom(parcel.roomId)?.getTimeLineEvent(parcel.eventId) ?: return null var agreeTriggle: TriggleState = TriggleState.NONE diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ReactionInfoSimpleItem.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ReactionInfoSimpleItem.kt new file mode 100644 index 0000000000..7c7e253167 --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ReactionInfoSimpleItem.kt @@ -0,0 +1,39 @@ +package im.vector.riotredesign.features.home.room.detail.timeline.action + +import android.widget.TextView +import androidx.core.view.isVisible +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import com.airbnb.epoxy.EpoxyModelWithHolder +import im.vector.riotredesign.R +import im.vector.riotredesign.core.epoxy.VectorEpoxyHolder + + +@EpoxyModelClass(layout = R.layout.item_simple_reaction_info) +abstract class ReactionInfoSimpleItem : EpoxyModelWithHolder<ReactionInfoSimpleItem.Holder>() { + + @EpoxyAttribute + lateinit var reactionKey: CharSequence + @EpoxyAttribute + lateinit var authorDisplayName: CharSequence + @EpoxyAttribute + var timeStamp: CharSequence? = null + + override fun bind(holder: Holder) { + holder.titleView.text = reactionKey + holder.displayNameView.text = authorDisplayName + timeStamp?.let { + holder.timeStampView.text = it + holder.timeStampView.isVisible = true + } ?: run { + holder.timeStampView.isVisible = false + } + } + + class Holder : VectorEpoxyHolder() { + val titleView by bind<TextView>(R.id.itemSimpleReactionInfoKey) + val displayNameView by bind<TextView>(R.id.itemSimpleReactionInfoMemberName) + val timeStampView by bind<TextView>(R.id.itemSimpleReactionInfoTime) + } + +} diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/TimelineEventFragmentArgs.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/TimelineEventFragmentArgs.kt new file mode 100644 index 0000000000..5764563812 --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/TimelineEventFragmentArgs.kt @@ -0,0 +1,12 @@ +package im.vector.riotredesign.features.home.room.detail.timeline.action + +import android.os.Parcelable +import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData +import kotlinx.android.parcel.Parcelize + +@Parcelize +data class TimelineEventFragmentArgs( + val eventId: String, + val roomId: String, + val informationData: MessageInformationData +) : Parcelable \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ViewReactionBottomSheet.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ViewReactionBottomSheet.kt new file mode 100644 index 0000000000..a53dc3bd78 --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ViewReactionBottomSheet.kt @@ -0,0 +1,71 @@ +package im.vector.riotredesign.features.home.room.detail.timeline.action + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.LinearLayout +import androidx.core.view.isVisible +import androidx.recyclerview.widget.DividerItemDecoration +import butterknife.BindView +import butterknife.ButterKnife +import com.airbnb.epoxy.EpoxyRecyclerView +import com.airbnb.mvrx.MvRx +import com.airbnb.mvrx.args +import com.airbnb.mvrx.fragmentViewModel +import com.airbnb.mvrx.withState +import im.vector.riotredesign.R +import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData +import kotlinx.android.synthetic.main.bottom_sheet_display_reactions.* + + +class ViewReactionBottomSheet : BaseMvRxBottomSheetDialog() { + + private val viewModel: ViewReactionViewModel by fragmentViewModel(ViewReactionViewModel::class) + + private val eventArgs: TimelineEventFragmentArgs by args() + + @BindView(R.id.bottom_sheet_display_reactions_list) + lateinit var epoxyRecyclerView: EpoxyRecyclerView + + private val epoxyController by lazy { ViewReactionsEpoxyController() } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + val view = inflater.inflate(R.layout.bottom_sheet_display_reactions, container, false) + ButterKnife.bind(this, view) + return view + } + + override fun onActivityCreated(savedInstanceState: Bundle?) { + super.onActivityCreated(savedInstanceState) + epoxyRecyclerView.setController(epoxyController) + val dividerItemDecoration = DividerItemDecoration(epoxyRecyclerView.context, + LinearLayout.VERTICAL) + epoxyRecyclerView.addItemDecoration(dividerItemDecoration) + } + + + override fun invalidate() = withState(viewModel) { + if (it.mapReactionKeyToMemberList() == null) { + bottomSheetViewReactionSpinner.isVisible = true + bottomSheetViewReactionSpinner.animate() + } else { + bottomSheetViewReactionSpinner.isVisible = false + } + epoxyController.setData(it) + } + + companion object { + fun newInstance(roomId: String, informationData: MessageInformationData): ViewReactionBottomSheet { + val args = Bundle() + val parcelableArgs = TimelineEventFragmentArgs( + informationData.eventId, + roomId, + informationData + ) + args.putParcelable(MvRx.KEY_ARG, parcelableArgs) + return ViewReactionBottomSheet().apply { arguments = args } + + } + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ViewReactionViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ViewReactionViewModel.kt new file mode 100644 index 0000000000..a5e7fdd882 --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ViewReactionViewModel.kt @@ -0,0 +1,106 @@ +package im.vector.riotredesign.features.home.room.detail.timeline.action + +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.LiveData +import androidx.lifecycle.Observer +import com.airbnb.mvrx.* +import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary +import im.vector.riotredesign.core.extensions.localDateTime +import im.vector.riotredesign.core.platform.VectorViewModel +import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDateFormatter +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import org.koin.android.ext.android.get + + +data class DisplayReactionsViewState( + val eventId: String = "", + val roomId: String = "", + val mapReactionKeyToMemberList: Async<List<ReactionInfo>> = Uninitialized) + : MvRxState + +data class ReactionInfo( + val eventId: String, + val reactionKey: String, + val authorId: String, + val authorName: String? = null, + val timestamp: String? = null +) + +/** + * Used to display the list of members that reacted to a given event + */ +class ViewReactionViewModel(private val session: Session, + private val timelineDateFormatter: TimelineDateFormatter, + lifecycleOwner: LifecycleOwner?, + liveSummary: LiveData<List<EventAnnotationsSummary>>?, + initialState: DisplayReactionsViewState) : VectorViewModel<DisplayReactionsViewState>(initialState) { + + init { + loadReaction() + if (lifecycleOwner != null) { + liveSummary?.observe(lifecycleOwner, Observer { + it?.firstOrNull()?.let { + loadReaction() + } + }) + } + + } + + private fun loadReaction() = withState { state -> + + GlobalScope.launch { + try { + val room = session.getRoom(state.roomId) + val event = room?.getTimeLineEvent(state.eventId) + if (event == null) { + setState { copy(mapReactionKeyToMemberList = Fail(Throwable())) } + return@launch + } + var results = ArrayList<ReactionInfo>() + event.annotations?.reactionsSummary?.forEach { sum -> + + sum.sourceEvents.mapNotNull { room.getTimeLineEvent(it) }.forEach { + val localDate = it.root.localDateTime() + results.add(ReactionInfo(it.root.eventId!!, sum.key, it.root.sender + ?: "", it.senderName, timelineDateFormatter.formatMessageHour(localDate))) + } + } + setState { + copy( + mapReactionKeyToMemberList = Success(results.sortedBy { it.timestamp }) + ) + } + } catch (t: Throwable) { + setState { + copy( + mapReactionKeyToMemberList = Fail(t) + ) + } + } + } + } + + + companion object : MvRxViewModelFactory<ViewReactionViewModel, DisplayReactionsViewState> { + + override fun initialState(viewModelContext: ViewModelContext): DisplayReactionsViewState? { + + val roomId = (viewModelContext.args as? TimelineEventFragmentArgs)?.roomId + ?: return null + val info = (viewModelContext.args as? TimelineEventFragmentArgs)?.informationData + ?: return null + return DisplayReactionsViewState(info.eventId, roomId) + } + + override fun create(viewModelContext: ViewModelContext, state: DisplayReactionsViewState): ViewReactionViewModel? { + val session = viewModelContext.activity.get<Session>() + val eventId = (viewModelContext.args as TimelineEventFragmentArgs).eventId + return ViewReactionViewModel(session, viewModelContext.activity.get(), viewModelContext.activity, session.getRoom(state.roomId)?.getEventSummaryLive(eventId), state) + } + + + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ViewReactionsEpoxyController.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ViewReactionsEpoxyController.kt new file mode 100644 index 0000000000..a3d146a24f --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ViewReactionsEpoxyController.kt @@ -0,0 +1,19 @@ +package im.vector.riotredesign.features.home.room.detail.timeline.action + +import com.airbnb.epoxy.TypedEpoxyController + + +class ViewReactionsEpoxyController : TypedEpoxyController<DisplayReactionsViewState>() { + + override fun buildModels(state: DisplayReactionsViewState) { + val map = state.mapReactionKeyToMemberList() ?: return + map.forEach { + reactionInfoSimpleItem { + id(it.eventId) + timeStamp(it.timestamp) + reactionKey(it.reactionKey) + authorDisplayName(it.authorName ?: it.authorId) + } + } + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/AbsMessageItem.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/AbsMessageItem.kt index b74f7bcac5..a3f43baf51 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/AbsMessageItem.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/AbsMessageItem.kt @@ -65,6 +65,10 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() { override fun onUnReacted(reactionButton: ReactionButton) { reactionPillCallback?.onClickOnReactionPill(informationData, reactionButton.reactionString, false) } + + override fun onLongClick(reactionButton: ReactionButton) { + reactionPillCallback?.onLongClickOnReactionPill(informationData, reactionButton.reactionString) + } } override fun bind(holder: H) { @@ -112,7 +116,7 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() { //clear all reaction buttons (but not the Flow helper!) holder.reactionWrapper?.children?.forEach { (it as? ReactionButton)?.isGone = true } val idToRefInFlow = ArrayList<Int>() - informationData.orderedReactionList?.chunked(7)?.firstOrNull()?.forEachIndexed { index, reaction -> + informationData.orderedReactionList?.chunked(8)?.firstOrNull()?.forEachIndexed { index, reaction -> (holder.reactionWrapper?.children?.elementAtOrNull(index) as? ReactionButton)?.let { reactionButton -> reactionButton.isVisible = true reactionButton.reactedListener = reactionClickListener diff --git a/vector/src/main/java/im/vector/riotredesign/features/reactions/widget/ReactionButton.kt b/vector/src/main/java/im/vector/riotredesign/features/reactions/widget/ReactionButton.kt index b0bf08e383..c0716cf28e 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/reactions/widget/ReactionButton.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/reactions/widget/ReactionButton.kt @@ -37,13 +37,14 @@ import androidx.annotation.ColorInt import androidx.annotation.ColorRes import androidx.core.content.ContextCompat import im.vector.riotredesign.R +import im.vector.riotredesign.core.utils.TextUtils /** * An animated reaction button. * Displays a String reaction (emoji), with a count, and that can be selected or not (toggle) */ class ReactionButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, - defStyleAttr: Int = 0) : FrameLayout(context, attrs, defStyleAttr), View.OnClickListener { + defStyleAttr: Int = 0) : FrameLayout(context, attrs, defStyleAttr), View.OnClickListener, View.OnLongClickListener { companion object { private val DECCELERATE_INTERPOLATOR = DecelerateInterpolator() @@ -74,7 +75,7 @@ class ReactionButton @JvmOverloads constructor(context: Context, attrs: Attribut var reactionCount = 11 set(value) { field = value - countTextView?.text = value.toString() + countTextView?.text = TextUtils.formatCountToShortDecimal(value) } @@ -101,7 +102,7 @@ class ReactionButton @JvmOverloads constructor(context: Context, attrs: Attribut reactionSelector = findViewById(R.id.reactionSelector) countTextView = findViewById(R.id.reactionCount) - countTextView?.text = reactionCount.toString() + countTextView?.text = TextUtils.formatCountToShortDecimal(reactionCount) emojiView?.typeface = this.emojiTypeFace ?: Typeface.DEFAULT @@ -136,6 +137,7 @@ class ReactionButton @JvmOverloads constructor(context: Context, attrs: Attribut val status = array.getBoolean(R.styleable.ReactionButton_toggled, false) setChecked(status) setOnClickListener(this) + setOnLongClickListener(this) array.recycle() } @@ -242,40 +244,45 @@ class ReactionButton @JvmOverloads constructor(context: Context, attrs: Attribut * @param event * @return */ - override fun onTouchEvent(event: MotionEvent): Boolean { - if (!isEnabled) - return true +// override fun onTouchEvent(event: MotionEvent): Boolean { +// if (!isEnabled) +// return true +// +// when (event.action) { +// MotionEvent.ACTION_DOWN -> +// /* +// Commented out this line and moved the animation effect to the action up event due to +// conflicts that were occurring when library is used in sliding type views. +// +// icon.animate().scaleX(0.7f).scaleY(0.7f).setDuration(150).setInterpolator(DECCELERATE_INTERPOLATOR); +// */ +// isPressed = true +// +// MotionEvent.ACTION_MOVE -> { +// val x = event.x +// val y = event.y +// val isInside = x > 0 && x < width && y > 0 && y < height +// if (isPressed != isInside) { +// isPressed = isInside +// } +// } +// +// MotionEvent.ACTION_UP -> { +// emojiView!!.animate().scaleX(0.7f).scaleY(0.7f).setDuration(150).interpolator = DECCELERATE_INTERPOLATOR +// emojiView!!.animate().scaleX(1f).scaleY(1f).interpolator = DECCELERATE_INTERPOLATOR +// if (isPressed) { +// performClick() +// isPressed = false +// } +// } +// MotionEvent.ACTION_CANCEL -> isPressed = false +// } +// return true +// } - when (event.action) { - MotionEvent.ACTION_DOWN -> - /* - Commented out this line and moved the animation effect to the action up event due to - conflicts that were occurring when library is used in sliding type views. - - icon.animate().scaleX(0.7f).scaleY(0.7f).setDuration(150).setInterpolator(DECCELERATE_INTERPOLATOR); - */ - isPressed = true - - MotionEvent.ACTION_MOVE -> { - val x = event.x - val y = event.y - val isInside = x > 0 && x < width && y > 0 && y < height - if (isPressed != isInside) { - isPressed = isInside - } - } - - MotionEvent.ACTION_UP -> { - emojiView!!.animate().scaleX(0.7f).scaleY(0.7f).setDuration(150).interpolator = DECCELERATE_INTERPOLATOR - emojiView!!.animate().scaleX(1f).scaleY(1f).interpolator = DECCELERATE_INTERPOLATOR - if (isPressed) { - performClick() - isPressed = false - } - } - MotionEvent.ACTION_CANCEL -> isPressed = false - } - return true + override fun onLongClick(v: View?): Boolean { + reactedListener?.onLongClick(this) + return reactedListener != null } /** @@ -335,5 +342,6 @@ class ReactionButton @JvmOverloads constructor(context: Context, attrs: Attribut interface ReactedListener { fun onReacted(reactionButton: ReactionButton) fun onUnReacted(reactionButton: ReactionButton) + fun onLongClick(reactionButton: ReactionButton) } } \ No newline at end of file diff --git a/vector/src/main/res/layout/bottom_sheet_display_reactions.xml b/vector/src/main/res/layout/bottom_sheet_display_reactions.xml new file mode 100644 index 0000000000..0f5b63654d --- /dev/null +++ b/vector/src/main/res/layout/bottom_sheet_display_reactions.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="400dp" + android:orientation="vertical"> + + <TextView + android:layout_width="match_parent" + android:layout_height="44dp" + android:gravity="center_vertical" + android:padding="8dp" + android:text="@string/reactions" + android:textColor="?android:textColorSecondary" + android:textSize="16sp" /> + + <ProgressBar + android:id="@+id/bottomSheetViewReactionSpinner" + style="?android:attr/progressBarStyleSmall" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="8dp" + android:visibility="gone" + tools:visibility="visible" /> + + + <com.airbnb.epoxy.EpoxyRecyclerView + android:id="@+id/bottom_sheet_display_reactions_list" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" + android:fadeScrollbars="false" + android:orientation="vertical" + android:scrollbars="vertical" + tools:itemCount="15" + tools:listitem="@layout/item_simple_reaction_info"> + + </com.airbnb.epoxy.EpoxyRecyclerView> +</LinearLayout> \ No newline at end of file diff --git a/vector/src/main/res/layout/item_simple_reaction_info.xml b/vector/src/main/res/layout/item_simple_reaction_info.xml new file mode 100644 index 0000000000..0b84aedcd1 --- /dev/null +++ b/vector/src/main/res/layout/item_simple_reaction_info.xml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="44dp" + android:gravity="center_vertical" + android:orientation="horizontal" + android:paddingStart="8dp" + android:paddingLeft="8dp" + android:paddingEnd="8dp"> + + <TextView + android:id="@+id/itemSimpleReactionInfoKey" + android:layout_width="44dp" + android:layout_height="wrap_content" + android:gravity="center" + android:lines="1" + android:textColor="?android:textColorPrimary" + android:textSize="18sp" + tools:text="@sample/reactions.json/data/reaction" /> + + <TextView + android:id="@+id/itemSimpleReactionInfoMemberName" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="4dp" + android:layout_marginLeft="4dp" + android:layout_weight="1" + android:ellipsize="end" + android:lines="1" + android:textColor="?android:textColorPrimary" + android:textSize="16sp" + tools:text="@sample/matrix.json/data/displayName" /> + + <TextView + android:id="@+id/itemSimpleReactionInfoTime" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:lines="1" + android:textColor="?android:textColorSecondary" + android:textSize="12sp" + tools:text="10:44" /> + + +</LinearLayout> \ No newline at end of file diff --git a/vector/src/main/res/layout/reaction_button.xml b/vector/src/main/res/layout/reaction_button.xml index aca0d2bf7d..6c16929788 100644 --- a/vector/src/main/res/layout/reaction_button.xml +++ b/vector/src/main/res/layout/reaction_button.xml @@ -2,16 +2,19 @@ <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" - android:layout_width="44dp" + android:id="@+id/reactionSelector" + android:layout_width="wrap_content" + android:minWidth="44dp" android:layout_height="26dp" + android:background="@drawable/rounded_rect_shape" android:clipChildren="false"> - <View - android:id="@+id/reactionSelector" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="@drawable/rounded_rect_shape" /> + <!--<View--> + <!--android:id="@+id/reactionSelector"--> + <!--android:layout_width="match_parent"--> + <!--android:layout_height="match_parent"--> + <!--android:background="@drawable/rounded_rect_shape" />--> <im.vector.riotredesign.features.reactions.widget.DotsView android:id="@+id/dots" @@ -42,17 +45,23 @@ android:gravity="center" android:textColor="@color/black" android:textSize="13sp" + app:layout_constraintHorizontal_chainStyle="packed" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" + app:layout_constraintEnd_toStartOf="@id/reactionCount" tools:text="👍" /> <TextView android:id="@+id/reactionCount" - android:layout_width="0dp" - android:layout_height="match_parent" - android:layout_marginEnd="6dp" - android:layout_marginRight="6dp" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + app:layout_constraintHorizontal_chainStyle="packed" + app:layout_constraintBaseline_toBaselineOf="@id/reactionText" + android:layout_marginStart="-4dp" + android:layout_marginLeft="-4dp" + android:layout_marginEnd="8dp" + android:layout_marginRight="8dp" android:gravity="center" android:maxLines="1" android:textColor="?riotx_text_secondary" @@ -61,7 +70,8 @@ app:autoSizeMaxTextSize="14sp" app:autoSizeMinTextSize="8sp" app:autoSizeTextType="uniform" + app:layout_constraintStart_toEndOf="@id/reactionText" app:layout_constraintEnd_toEndOf="parent" - tools:text="10" /> + tools:text="13450" /> </androidx.constraintlayout.widget.ConstraintLayout> diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index 3554d61948..8109c89d10 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -20,6 +20,7 @@ <string name="reactions_agree">Agree</string> <string name="reactions_like">Like</string> <string name="message_add_reaction">Add Reaction</string> + <string name="reactions">Reactions</string> <string name="event_redacted_by_user_reason">Event deleted by user</string> <string name="event_redacted_by_admin_reason">Event moderated by room admin</string> From 297f202005100d79fd8478e9c8431b6075728350 Mon Sep 17 00:00:00 2001 From: Valere <valeref@matrix.org> Date: Wed, 5 Jun 2019 23:42:08 +0200 Subject: [PATCH 05/18] Fix / Local echo taking too much time --- .../internal/session/room/EventRelationsAggregationTask.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationTask.kt index a66b65ab71..e5fb5493bf 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationTask.kt @@ -28,6 +28,7 @@ import im.vector.matrix.android.internal.database.query.create import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.util.tryTransactionAsync +import im.vector.matrix.android.internal.util.tryTransactionSync import io.realm.Realm import timber.log.Timber @@ -48,7 +49,7 @@ internal class DefaultEventRelationsAggregationTask(private val monarchy: Monarc private val SHOULD_HANDLE_SERVER_AGREGGATION = false override fun execute(params: EventRelationsAggregationTask.Params): Try<Unit> { - return monarchy.tryTransactionAsync { realm -> + return monarchy.tryTransactionSync { realm -> update(realm, params.events, params.userId) } } From e22b555b58f5a79ed5669a9d64a3bf4b346078e4 Mon Sep 17 00:00:00 2001 From: Valere <valeref@matrix.org> Date: Wed, 5 Jun 2019 23:45:46 +0200 Subject: [PATCH 06/18] Refactoring (duplication in Message Item Factory) + cleaning --- .../timeline/TimelineEventController.kt | 9 ++- .../timeline/action/ViewReactionViewModel.kt | 81 +++++++++---------- .../timeline/factory/MessageItemFactory.kt | 68 ++-------------- .../detail/timeline/item/AbsMessageItem.kt | 19 +++-- .../res/layout/item_simple_reaction_info.xml | 4 +- 5 files changed, 69 insertions(+), 112 deletions(-) diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt index 175702cfa8..2861ae6a70 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt @@ -46,7 +46,7 @@ class TimelineEventController(private val dateFormatter: TimelineDateFormatter, private val backgroundHandler: Handler = TimelineAsyncHelper.getBackgroundHandler() ) : EpoxyController(backgroundHandler, backgroundHandler), Timeline.Listener { - interface Callback : ReactionPillCallback { + interface Callback : ReactionPillCallback, AvatarCallback { fun onEventVisible(event: TimelineEvent) fun onUrlClicked(url: String) fun onImageMessageClicked(messageImageContent: MessageImageContent, mediaData: ImageContentRenderer.Data, view: View) @@ -55,8 +55,6 @@ class TimelineEventController(private val dateFormatter: TimelineDateFormatter, fun onAudioMessageClicked(messageAudioContent: MessageAudioContent) fun onEventCellClicked(informationData: MessageInformationData, messageContent: MessageContent, view: View) fun onEventLongClicked(informationData: MessageInformationData, messageContent: MessageContent, view: View): Boolean - fun onAvatarClicked(informationData: MessageInformationData) - fun onMemberNameClicked(informationData: MessageInformationData) fun onEditedDecorationClicked(informationData: MessageInformationData, editAggregatedSummary: EditAggregatedSummary?) } @@ -65,6 +63,11 @@ class TimelineEventController(private val dateFormatter: TimelineDateFormatter, fun onLongClickOnReactionPill(informationData: MessageInformationData, reaction: String) } + interface AvatarCallback { + fun onAvatarClicked(informationData: MessageInformationData) + fun onMemberNameClicked(informationData: MessageInformationData) + } + private val collapsedEventIds = linkedSetOf<String>() private val mergeItemCollapseStates = HashMap<String, Boolean>() private val modelCache = arrayListOf<CacheItemData?>() diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ViewReactionViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ViewReactionViewModel.kt index a5e7fdd882..8854886512 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ViewReactionViewModel.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ViewReactionViewModel.kt @@ -1,16 +1,12 @@ package im.vector.riotredesign.features.home.room.detail.timeline.action -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.LiveData +import androidx.fragment.app.Fragment import androidx.lifecycle.Observer import com.airbnb.mvrx.* import im.vector.matrix.android.api.session.Session -import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary import im.vector.riotredesign.core.extensions.localDateTime import im.vector.riotredesign.core.platform.VectorViewModel import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDateFormatter -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch import org.koin.android.ext.android.get @@ -33,53 +29,41 @@ data class ReactionInfo( */ class ViewReactionViewModel(private val session: Session, private val timelineDateFormatter: TimelineDateFormatter, - lifecycleOwner: LifecycleOwner?, - liveSummary: LiveData<List<EventAnnotationsSummary>>?, initialState: DisplayReactionsViewState) : VectorViewModel<DisplayReactionsViewState>(initialState) { init { loadReaction() - if (lifecycleOwner != null) { - liveSummary?.observe(lifecycleOwner, Observer { - it?.firstOrNull()?.let { - loadReaction() - } - }) - } - } - private fun loadReaction() = withState { state -> + fun loadReaction() = withState { state -> - GlobalScope.launch { - try { - val room = session.getRoom(state.roomId) - val event = room?.getTimeLineEvent(state.eventId) - if (event == null) { - setState { copy(mapReactionKeyToMemberList = Fail(Throwable())) } - return@launch - } - var results = ArrayList<ReactionInfo>() - event.annotations?.reactionsSummary?.forEach { sum -> + try { + val room = session.getRoom(state.roomId) + val event = room?.getTimeLineEvent(state.eventId) + if (event == null) { + setState { copy(mapReactionKeyToMemberList = Fail(Throwable())) } + return@withState + } + var results = ArrayList<ReactionInfo>() + event.annotations?.reactionsSummary?.forEach { sum -> - sum.sourceEvents.mapNotNull { room.getTimeLineEvent(it) }.forEach { - val localDate = it.root.localDateTime() - results.add(ReactionInfo(it.root.eventId!!, sum.key, it.root.sender - ?: "", it.senderName, timelineDateFormatter.formatMessageHour(localDate))) - } - } - setState { - copy( - mapReactionKeyToMemberList = Success(results.sortedBy { it.timestamp }) - ) - } - } catch (t: Throwable) { - setState { - copy( - mapReactionKeyToMemberList = Fail(t) - ) + sum.sourceEvents.mapNotNull { room.getTimeLineEvent(it) }.forEach { + val localDate = it.root.localDateTime() + results.add(ReactionInfo(it.root.eventId!!, sum.key, it.root.sender + ?: "", it.senderName, timelineDateFormatter.formatMessageHour(localDate))) } } + setState { + copy( + mapReactionKeyToMemberList = Success(results.sortedBy { it.timestamp }) + ) + } + } catch (t: Throwable) { + setState { + copy( + mapReactionKeyToMemberList = Fail(t) + ) + } } } @@ -98,7 +82,18 @@ class ViewReactionViewModel(private val session: Session, override fun create(viewModelContext: ViewModelContext, state: DisplayReactionsViewState): ViewReactionViewModel? { val session = viewModelContext.activity.get<Session>() val eventId = (viewModelContext.args as TimelineEventFragmentArgs).eventId - return ViewReactionViewModel(session, viewModelContext.activity.get(), viewModelContext.activity, session.getRoom(state.roomId)?.getEventSummaryLive(eventId), state) + val lifecycleOwner = (viewModelContext as FragmentViewModelContext).fragment<Fragment>() + val liveSummary = session.getRoom(state.roomId)?.getEventSummaryLive(eventId) + val viewReactionViewModel = ViewReactionViewModel(session, viewModelContext.activity.get(), state) + // This states observes the live summary + // When fragment context will be destroyed the observer will automatically removed + liveSummary?.observe(lifecycleOwner, Observer { + it?.firstOrNull()?.let { + viewReactionViewModel.loadReaction() + } + }) + + return viewReactionViewModel } diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 7571fead47..3390f4c23e 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -143,18 +143,11 @@ class MessageItemFactory(private val colorProvider: ColorProvider, callback: TimelineEventController.Callback?): MessageFileItem? { return MessageFileItem_() .informationData(informationData) + .avatarCallback(callback) .filename(messageContent.body) .iconRes(R.drawable.filetype_audio) .reactionPillCallback(callback) .emojiTypeFace(emojiCompatFontProvider.typeface) - .avatarClickListener( - DebouncedClickListener(View.OnClickListener { - callback?.onAvatarClicked(informationData) - })) - .memberClickListener( - DebouncedClickListener(View.OnClickListener { - callback?.onMemberNameClicked(informationData) - })) .cellClickListener( DebouncedClickListener(View.OnClickListener { view: View -> callback?.onEventCellClicked(informationData, messageContent, view) @@ -174,18 +167,11 @@ class MessageItemFactory(private val colorProvider: ColorProvider, callback: TimelineEventController.Callback?): MessageFileItem? { return MessageFileItem_() .informationData(informationData) + .avatarCallback(callback) .filename(messageContent.body) .reactionPillCallback(callback) .emojiTypeFace(emojiCompatFontProvider.typeface) .iconRes(R.drawable.filetype_attachment) - .avatarClickListener( - DebouncedClickListener(View.OnClickListener { view -> - callback?.onAvatarClicked(informationData) - })) - .memberClickListener( - DebouncedClickListener(View.OnClickListener { view -> - callback?.onMemberNameClicked(informationData) - })) .cellClickListener( DebouncedClickListener(View.OnClickListener { view -> callback?.onEventCellClicked(informationData, messageContent, view) @@ -223,17 +209,10 @@ class MessageItemFactory(private val colorProvider: ColorProvider, return MessageImageVideoItem_() .playable(messageContent.info?.mimeType == "image/gif") .informationData(informationData) + .avatarCallback(callback) .mediaData(data) .reactionPillCallback(callback) .emojiTypeFace(emojiCompatFontProvider.typeface) - .avatarClickListener( - DebouncedClickListener(View.OnClickListener { view -> - callback?.onAvatarClicked(informationData) - })) - .memberClickListener( - DebouncedClickListener(View.OnClickListener { view -> - callback?.onMemberNameClicked(informationData) - })) .clickListener( DebouncedClickListener(View.OnClickListener { view -> callback?.onImageMessageClicked(messageContent, data, view) @@ -271,17 +250,10 @@ class MessageItemFactory(private val colorProvider: ColorProvider, return MessageImageVideoItem_() .playable(true) .informationData(informationData) + .avatarCallback(callback) .mediaData(thumbnailData) .reactionPillCallback(callback) .emojiTypeFace(emojiCompatFontProvider.typeface) - .avatarClickListener( - DebouncedClickListener(View.OnClickListener { view -> - callback?.onAvatarClicked(informationData) - })) - .memberClickListener( - DebouncedClickListener(View.OnClickListener { view -> - callback?.onMemberNameClicked(informationData) - })) .cellClickListener( DebouncedClickListener(View.OnClickListener { view -> callback?.onEventCellClicked(informationData, messageContent, view) @@ -316,16 +288,9 @@ class MessageItemFactory(private val colorProvider: ColorProvider, } } .informationData(informationData) + .avatarCallback(callback) .reactionPillCallback(callback) .emojiTypeFace(emojiCompatFontProvider.typeface) - .avatarClickListener( - DebouncedClickListener(View.OnClickListener { view -> - callback?.onAvatarClicked(informationData) - })) - .memberClickListener( - DebouncedClickListener(View.OnClickListener { view -> - callback?.onMemberNameClicked(informationData) - })) //click on the text .clickListener( DebouncedClickListener(View.OnClickListener { view -> @@ -390,12 +355,9 @@ class MessageItemFactory(private val colorProvider: ColorProvider, return MessageTextItem_() .message(message) .informationData(informationData) + .avatarCallback(callback) .reactionPillCallback(callback) .emojiTypeFace(emojiCompatFontProvider.typeface) - .avatarClickListener( - DebouncedClickListener(View.OnClickListener { view -> - callback?.onAvatarClicked(informationData) - })) .memberClickListener( DebouncedClickListener(View.OnClickListener { view -> callback?.onMemberNameClicked(informationData) @@ -430,16 +392,9 @@ class MessageItemFactory(private val colorProvider: ColorProvider, } } .informationData(informationData) + .avatarCallback(callback) .reactionPillCallback(callback) .emojiTypeFace(emojiCompatFontProvider.typeface) - .avatarClickListener( - DebouncedClickListener(View.OnClickListener { view -> - callback?.onAvatarClicked(informationData) - })) - .memberClickListener( - DebouncedClickListener(View.OnClickListener { view -> - callback?.onMemberNameClicked(informationData) - })) .cellClickListener( DebouncedClickListener(View.OnClickListener { view -> callback?.onEventCellClicked(informationData, messageContent, view) @@ -454,14 +409,7 @@ class MessageItemFactory(private val colorProvider: ColorProvider, callback: TimelineEventController.Callback?): RedactedMessageItem? { return RedactedMessageItem_() .informationData(informationData) - .avatarClickListener( - DebouncedClickListener(View.OnClickListener { view -> - callback?.onAvatarClicked(informationData) - })) - .memberClickListener( - DebouncedClickListener(View.OnClickListener { view -> - callback?.onMemberNameClicked(informationData) - })) + .avatarCallback(callback) } private fun linkifyBody(body: CharSequence, callback: TimelineEventController.Callback?): CharSequence { diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/AbsMessageItem.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/AbsMessageItem.kt index a3f43baf51..7749e152b3 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/AbsMessageItem.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/AbsMessageItem.kt @@ -29,6 +29,7 @@ import androidx.core.view.isGone import androidx.core.view.isVisible import com.airbnb.epoxy.EpoxyAttribute import im.vector.riotredesign.R +import im.vector.riotredesign.core.utils.DebouncedClickListener import im.vector.riotredesign.core.utils.DimensionUtils.dpToPx import im.vector.riotredesign.features.home.AvatarRenderer import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController @@ -45,9 +46,6 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() { @EpoxyAttribute var cellClickListener: View.OnClickListener? = null - @EpoxyAttribute - var avatarClickListener: View.OnClickListener? = null - @EpoxyAttribute var memberClickListener: View.OnClickListener? = null @@ -57,6 +55,17 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() { @EpoxyAttribute var reactionPillCallback: TimelineEventController.ReactionPillCallback? = null + @EpoxyAttribute + var avatarCallback: TimelineEventController.AvatarCallback?= null + + private val _avatarClickListener = DebouncedClickListener(View.OnClickListener { + avatarCallback?.onAvatarClicked(informationData) + }) + private val _memberNameClickListener = DebouncedClickListener(View.OnClickListener { + avatarCallback?.onMemberNameClicked(informationData) + }) + + var reactionClickListener: ReactionButton.ReactedListener = object : ReactionButton.ReactedListener { override fun onReacted(reactionButton: ReactionButton) { reactionPillCallback?.onClickOnReactionPill(informationData, reactionButton.reactionString, true) @@ -81,9 +90,9 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() { width = size } holder.avatarImageView.visibility = View.VISIBLE - holder.avatarImageView.setOnClickListener(avatarClickListener) + holder.avatarImageView.setOnClickListener(_avatarClickListener) holder.memberNameView.visibility = View.VISIBLE - holder.memberNameView.setOnClickListener(memberClickListener) + holder.memberNameView.setOnClickListener(_memberNameClickListener) holder.timeView.visibility = View.VISIBLE holder.timeView.text = informationData.time holder.memberNameView.text = informationData.memberName diff --git a/vector/src/main/res/layout/item_simple_reaction_info.xml b/vector/src/main/res/layout/item_simple_reaction_info.xml index 0b84aedcd1..0458b17126 100644 --- a/vector/src/main/res/layout/item_simple_reaction_info.xml +++ b/vector/src/main/res/layout/item_simple_reaction_info.xml @@ -11,8 +11,10 @@ <TextView android:id="@+id/itemSimpleReactionInfoKey" - android:layout_width="44dp" + android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_marginEnd="8dp" + android:layout_marginRight="8dp" android:gravity="center" android:lines="1" android:textColor="?android:textColorPrimary" From 834a865dfa4d5717681978070609e54081b23a5c Mon Sep 17 00:00:00 2001 From: Valere <valeref@matrix.org> Date: Thu, 6 Jun 2019 11:54:08 +0200 Subject: [PATCH 07/18] Show text with only few emojis in bigger --- .../riotredesign/core/utils/DimensionUtils.kt | 8 +++ .../vector/riotredesign/core/utils/Emoji.kt | 69 +++++++++++++++++++ .../detail/timeline/item/MessageTextItem.kt | 18 +++-- 3 files changed, 90 insertions(+), 5 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotredesign/core/utils/Emoji.kt diff --git a/vector/src/main/java/im/vector/riotredesign/core/utils/DimensionUtils.kt b/vector/src/main/java/im/vector/riotredesign/core/utils/DimensionUtils.kt index ab3654a33e..44659da6af 100644 --- a/vector/src/main/java/im/vector/riotredesign/core/utils/DimensionUtils.kt +++ b/vector/src/main/java/im/vector/riotredesign/core/utils/DimensionUtils.kt @@ -28,4 +28,12 @@ object DimensionUtils { context.resources.displayMetrics ).toInt() } + + fun spToPx(sp: Int, context: Context): Int { + return TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_SP, + sp.toFloat(), + context.resources.displayMetrics + ).toInt() + } } \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/core/utils/Emoji.kt b/vector/src/main/java/im/vector/riotredesign/core/utils/Emoji.kt new file mode 100644 index 0000000000..3595ad876e --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/core/utils/Emoji.kt @@ -0,0 +1,69 @@ +package im.vector.riotredesign.core.utils + +import java.util.regex.Pattern + +private val emojisPattern = Pattern.compile("((?:[\uD83C\uDF00-\uD83D\uDDFF]" + + "|[\uD83E\uDD00-\uD83E\uDDFF]" + + "|[\uD83D\uDE00-\uD83D\uDE4F]" + + "|[\uD83D\uDE80-\uD83D\uDEFF]" + + "|[\u2600-\u26FF]\uFE0F?" + + "|[\u2700-\u27BF]\uFE0F?" + + "|\u24C2\uFE0F?" + + "|[\uD83C\uDDE6-\uD83C\uDDFF]{1,2}" + + "|[\uD83C\uDD70\uD83C\uDD71\uD83C\uDD7E\uD83C\uDD7F\uD83C\uDD8E\uD83C\uDD91-\uD83C\uDD9A]\uFE0F?" + + "|[\u0023\u002A\u0030-\u0039]\uFE0F?\u20E3" + + "|[\u2194-\u2199\u21A9-\u21AA]\uFE0F?" + + "|[\u2B05-\u2B07\u2B1B\u2B1C\u2B50\u2B55]\uFE0F?" + + "|[\u2934\u2935]\uFE0F?" + + "|[\u3030\u303D]\uFE0F?" + + "|[\u3297\u3299]\uFE0F?" + + "|[\uD83C\uDE01\uD83C\uDE02\uD83C\uDE1A\uD83C\uDE2F\uD83C\uDE32-\uD83C\uDE3A\uD83C\uDE50\uD83C\uDE51]\uFE0F?" + + "|[\u203C\u2049]\uFE0F?" + + "|[\u25AA\u25AB\u25B6\u25C0\u25FB-\u25FE]\uFE0F?" + + "|[\u00A9\u00AE]\uFE0F?" + + "|[\u2122\u2139]\uFE0F?" + + "|\uD83C\uDC04\uFE0F?" + + "|\uD83C\uDCCF\uFE0F?" + + "|[\u231A\u231B\u2328\u23CF\u23E9-\u23F3\u23F8-\u23FA]\uFE0F?))") + +/** + * Test if a string contains emojis. + * It seems that the regex [emoji_regex]+ does not work. + * Some characters like ?, # or digit are accepted. + * + * @param str the body to test + * @return true if the body contains only emojis + */ +fun containsOnlyEmojis(str: String?): Boolean { + var res = false + + if (str != null && str.isNotEmpty()) { + val matcher = emojisPattern.matcher(str) + + var start = -1 + var end = -1 + + while (matcher.find()) { + val nextStart = matcher.start() + + // first emoji position + if (start < 0) { + if (nextStart > 0) { + return false + } + } else { + // must not have a character between + if (nextStart != end) { + return false + } + } + start = nextStart + end = matcher.end() + } + + res = -1 != start && end == str.length + } + + return res +} + diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/MessageTextItem.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/MessageTextItem.kt index 790243c4d6..b6f779011a 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/MessageTextItem.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/MessageTextItem.kt @@ -16,10 +16,7 @@ package im.vector.riotredesign.features.home.room.detail.timeline.item -import android.text.Spannable import android.view.View -import android.widget.ImageView -import android.widget.TextView import androidx.appcompat.widget.AppCompatTextView import androidx.core.text.PrecomputedTextCompat import androidx.core.text.toSpannable @@ -28,6 +25,8 @@ import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.matrix.android.api.permalinks.MatrixLinkify import im.vector.riotredesign.R +import im.vector.riotredesign.core.utils.DimensionUtils +import im.vector.riotredesign.core.utils.containsOnlyEmojis import im.vector.riotredesign.features.html.PillImageSpan import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope @@ -47,12 +46,21 @@ abstract class MessageTextItem : AbsMessageItem<MessageTextItem.Holder>() { override fun bind(holder: Holder) { super.bind(holder) MatrixLinkify.addLinkMovementMethod(holder.messageView) - val textFuture = PrecomputedTextCompat.getTextFuture(message ?: "", + + val msg = message ?: "" + if (msg.length <= 4 && containsOnlyEmojis(msg.toString())) { + holder.messageView.textSize = 44F + } else { + holder.messageView.textSize = 14F + } + + val textFuture = PrecomputedTextCompat.getTextFuture(msg, TextViewCompat.getTextMetricsParams(holder.messageView), null) + holder.messageView.setTextFuture(textFuture) holder.messageView.renderSendState() - holder.messageView.setOnClickListener (clickListener) + holder.messageView.setOnClickListener(clickListener) holder.messageView.setOnLongClickListener(longClickListener) findPillsAndProcess { it.bind(holder.messageView) } } From 053dc1d8dda281c1adccc0e32070e91f870b3f58 Mon Sep 17 00:00:00 2001 From: Valere <valeref@matrix.org> Date: Thu, 6 Jun 2019 11:55:26 +0200 Subject: [PATCH 08/18] Show 'view reaction' option in context menu --- .../home/room/detail/RoomDetailFragment.kt | 5 +++ .../home/room/detail/RoomDetailViewModel.kt | 6 +-- .../timeline/action/MessageMenuViewModel.kt | 27 ++++++++----- .../main/res/drawable/ic_view_reactions.xml | 38 +++++++++++++++++++ .../main/res/drawable/rounded_rect_shape.xml | 2 +- vector/src/main/res/values/strings_riotX.xml | 1 + 6 files changed, 66 insertions(+), 13 deletions(-) create mode 100644 vector/src/main/res/drawable/ic_view_reactions.xml diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt index 5ab0e46c94..3d3667b3ea 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt @@ -621,6 +621,11 @@ class RoomDetailFragment : val eventId = actionData.data?.toString() ?: return startActivityForResult(EmojiReactionPickerActivity.intent(requireContext(), eventId), REACTION_SELECT_REQUEST_CODE) } + MessageMenuViewModel.ACTION_VIEW_REACTIONS -> { + val messageInformationData = actionData.data as? MessageInformationData ?: return + ViewReactionBottomSheet.newInstance(roomDetailArgs.roomId,messageInformationData) + .show(requireActivity().supportFragmentManager, "DISPLAY_REACTIONS") + } MessageMenuViewModel.ACTION_COPY -> { //I need info about the current selected message :/ copyToClipboard(requireContext(), actionData.data?.toString() ?: "", false) diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt index 7a47cff0f6..351129a2f1 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt @@ -195,7 +195,8 @@ class RoomDetailViewModel(initialState: RoomDetailViewState, } } SendMode.EDIT -> { - room.editTextMessage(state.selectedEvent?.root?.eventId ?: "", action.text, action.autoMarkdown) + room.editTextMessage(state.selectedEvent?.root?.eventId + ?: "", action.text, action.autoMarkdown) setState { copy( sendMode = SendMode.REGULAR, @@ -330,7 +331,6 @@ class RoomDetailViewModel(initialState: RoomDetailViewState, room.updateQuickReaction(action.selectedReaction, action.opposite, action.targetEventId, session.sessionParams.credentials.userId) } - private fun handleSendMedia(action: RoomDetailActions.SendMedia) { val attachments = action.mediaFiles.map { ContentAttachmentData( @@ -352,7 +352,7 @@ class RoomDetailViewModel(initialState: RoomDetailViewState, displayedEventsObservable.accept(action) //We need to update this with the related m.replace also (to move read receipt) action.event.annotations?.editSummary?.sourceEvents?.forEach { - room.getTimeLineEvent(it)?.let {event -> + room.getTimeLineEvent(it)?.let { event -> displayedEventsObservable.accept(RoomDetailActions.EventDisplayed(event)) } } diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt index 776be95042..6a77c97062 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt @@ -50,7 +50,7 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes val event = currentSession.getRoom(parcel.roomId)?.getTimeLineEvent(parcel.eventId) ?: return null - val messageContent: MessageContent = event.annotations?.editSummary?.aggregatedContent?.toModel() + val messageContent: MessageContent = event.annotations?.editSummary?.aggregatedContent?.toModel() ?: event.root.content.toModel() ?: return null val type = messageContent.type @@ -64,9 +64,7 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes ) ) } - - - //TODO determine if can copy, forward, reply, quote, report? + val actions = ArrayList<SimpleAction>().apply { if (event.sendState == SendState.SENDING) { @@ -94,10 +92,13 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes } if (canQuote(event, messageContent)) { - //TODO quote icon this.add(SimpleAction(ACTION_QUOTE, R.string.quote, R.drawable.ic_quote, parcel.eventId)) } + if (canViewReactions(event)) { + this.add(SimpleAction(ACTION_VIEW_REACTIONS, R.string.message_view_reaction, R.drawable.ic_view_reactions, parcel.informationData)) + } + if (canShare(type)) { if (messageContent is MessageImageContent) { this.add( @@ -144,7 +145,7 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes MessageType.MSGTYPE_VIDEO, MessageType.MSGTYPE_AUDIO, MessageType.MSGTYPE_FILE -> true - else -> false + else -> false } } @@ -159,7 +160,7 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes MessageType.MSGTYPE_LOCATION -> { true } - else -> false + else -> false } } @@ -170,6 +171,13 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes return event.root.sender == myUserId } + private fun canViewReactions(event: TimelineEvent): Boolean { + //Only event of type Event.EVENT_TYPE_MESSAGE are supported for the moment + if (event.root.type != EventType.MESSAGE) return false + //TODO if user is admin or moderator + return event.annotations?.reactionsSummary?.isNotEmpty() ?: false + } + private fun canEdit(event: TimelineEvent, myUserId: String): Boolean { //Only event of type Event.EVENT_TYPE_MESSAGE are supported for the moment if (event.root.type != EventType.MESSAGE) return false @@ -191,7 +199,7 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes MessageType.MSGTYPE_LOCATION -> { true } - else -> false + else -> false } } @@ -203,7 +211,7 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes MessageType.MSGTYPE_VIDEO -> { true } - else -> false + else -> false } } @@ -220,6 +228,7 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes const val PERMALINK = "PERMALINK" const val ACTION_FLAG = "ACTION_FLAG" const val ACTION_QUICK_REACT = "ACTION_QUICK_REACT" + const val ACTION_VIEW_REACTIONS = "ACTION_VIEW_REACTIONS" } diff --git a/vector/src/main/res/drawable/ic_view_reactions.xml b/vector/src/main/res/drawable/ic_view_reactions.xml new file mode 100644 index 0000000000..f4106852b8 --- /dev/null +++ b/vector/src/main/res/drawable/ic_view_reactions.xml @@ -0,0 +1,38 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="22dp" + android:height="22dp" + android:viewportWidth="22" + android:viewportHeight="22"> + <path + android:pathData="M11,11m-10,0a10,10 0,1 1,20 0a10,10 0,1 1,-20 0" + android:strokeLineJoin="round" + android:strokeWidth="2" + android:fillColor="#00000000" + android:fillType="evenOdd" + android:strokeColor="#9e9e9e" + android:strokeLineCap="round"/> + <path + android:pathData="m7,13s1.5,2 4,2 4,-2 4,-2" + android:strokeLineJoin="round" + android:strokeWidth="2" + android:fillColor="#00000000" + android:fillType="evenOdd" + android:strokeColor="#9e9e9e" + android:strokeLineCap="round"/> + <path + android:pathData="m8,8h0.01" + android:strokeLineJoin="round" + android:strokeWidth="2" + android:fillColor="#00000000" + android:fillType="evenOdd" + android:strokeColor="#9e9e9e" + android:strokeLineCap="round"/> + <path + android:pathData="m14,8h0.01" + android:strokeLineJoin="round" + android:strokeWidth="2" + android:fillColor="#00000000" + android:fillType="evenOdd" + android:strokeColor="#9e9e9e" + android:strokeLineCap="round"/> +</vector> diff --git a/vector/src/main/res/drawable/rounded_rect_shape.xml b/vector/src/main/res/drawable/rounded_rect_shape.xml index cf083254f3..e9c0a27f59 100644 --- a/vector/src/main/res/drawable/rounded_rect_shape.xml +++ b/vector/src/main/res/drawable/rounded_rect_shape.xml @@ -2,7 +2,7 @@ <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> - <size android:width="40dp" android:height="22dp"/> + <!--<size android:width="40dp" android:height="22dp"/>--> <solid android:color="?vctr_list_header_background_color" /> diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index 8109c89d10..8d39ffe774 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -20,6 +20,7 @@ <string name="reactions_agree">Agree</string> <string name="reactions_like">Like</string> <string name="message_add_reaction">Add Reaction</string> + <string name="message_view_reaction">View Reactions</string> <string name="reactions">Reactions</string> <string name="event_redacted_by_user_reason">Event deleted by user</string> From 04576ba7fdef0fb7425c3512cdb5f5312ce9d203 Mon Sep 17 00:00:00 2001 From: Valere <valeref@matrix.org> Date: Thu, 6 Jun 2019 12:26:38 +0200 Subject: [PATCH 09/18] Permalink message action + Fix crash on injection of navigator --- .../vector/riotredesign/core/di/AppModule.kt | 5 ++-- .../features/home/HomeDrawerFragment.kt | 2 +- .../features/home/HomeNavigator.kt | 2 +- .../home/room/detail/RoomDetailFragment.kt | 18 ++++++++---- .../timeline/action/MessageMenuViewModel.kt | 6 ++-- .../home/room/list/RoomListFragment.kt | 4 +-- .../features/navigation/DefaultNavigator.kt | 28 +++++++++---------- .../features/navigation/Navigator.kt | 9 +++--- .../roomdirectory/PublicRoomsFragment.kt | 4 +-- .../RoomPreviewNoPreviewFragment.kt | 2 +- 10 files changed, 44 insertions(+), 36 deletions(-) diff --git a/vector/src/main/java/im/vector/riotredesign/core/di/AppModule.kt b/vector/src/main/java/im/vector/riotredesign/core/di/AppModule.kt index 542c0953e7..28ae4e1a46 100644 --- a/vector/src/main/java/im/vector/riotredesign/core/di/AppModule.kt +++ b/vector/src/main/java/im/vector/riotredesign/core/di/AppModule.kt @@ -18,7 +18,6 @@ package im.vector.riotredesign.core.di import android.content.Context import android.content.Context.MODE_PRIVATE -import androidx.fragment.app.Fragment import im.vector.matrix.android.api.Matrix import im.vector.riotredesign.EmojiCompatFontProvider import im.vector.riotredesign.core.error.ErrorFormatter @@ -87,8 +86,8 @@ class AppModule(private val context: Context) { Matrix.getInstance().currentSession!! } - factory { (fragment: Fragment) -> - DefaultNavigator(fragment) as Navigator + factory { + DefaultNavigator() as Navigator } single { diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/HomeDrawerFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/home/HomeDrawerFragment.kt index ff08720d44..97baa539b9 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/HomeDrawerFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/HomeDrawerFragment.kt @@ -54,7 +54,7 @@ class HomeDrawerFragment : VectorBaseFragment() { } } homeDrawerHeaderSettingsView.setOnClickListener { - navigator.openSettings() + navigator.openSettings(requireActivity()) } // Debug menu diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/HomeNavigator.kt b/vector/src/main/java/im/vector/riotredesign/features/home/HomeNavigator.kt index 8d29c0ab8e..e46fd7f59e 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/HomeNavigator.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/HomeNavigator.kt @@ -48,7 +48,7 @@ class HomeNavigator { activity?.let { //TODO enable eventId permalink. It doesn't work enough at the moment. it.drawerLayout?.closeDrawer(GravityCompat.START) - navigator.openRoom(roomId) + navigator.openRoom(roomId, it) } } diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt index 3d3667b3ea..1da6ed9d13 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt @@ -53,6 +53,7 @@ import com.jaiselrahman.filepicker.model.MediaFile import com.otaliastudios.autocomplete.Autocomplete import com.otaliastudios.autocomplete.AutocompleteCallback import com.otaliastudios.autocomplete.CharPolicy +import im.vector.matrix.android.api.permalinks.PermalinkFactory import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.EditAggregatedSummary @@ -622,16 +623,16 @@ class RoomDetailFragment : startActivityForResult(EmojiReactionPickerActivity.intent(requireContext(), eventId), REACTION_SELECT_REQUEST_CODE) } MessageMenuViewModel.ACTION_VIEW_REACTIONS -> { - val messageInformationData = actionData.data as? MessageInformationData ?: return - ViewReactionBottomSheet.newInstance(roomDetailArgs.roomId,messageInformationData) + val messageInformationData = actionData.data as? MessageInformationData + ?: return + ViewReactionBottomSheet.newInstance(roomDetailArgs.roomId, messageInformationData) .show(requireActivity().supportFragmentManager, "DISPLAY_REACTIONS") } MessageMenuViewModel.ACTION_COPY -> { //I need info about the current selected message :/ copyToClipboard(requireContext(), actionData.data?.toString() ?: "", false) - val snack = Snackbar.make(view!!, requireContext().getString(R.string.copied_to_clipboard), Snackbar.LENGTH_SHORT) - snack.view.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.notification_accent_color)) - snack.show() + val msg = requireContext().getString(R.string.copied_to_clipboard) + showSnackWithMessage(msg, Snackbar.LENGTH_SHORT) } MessageMenuViewModel.ACTION_DELETE -> { val eventId = actionData.data?.toString() ?: return @@ -698,6 +699,13 @@ class RoomDetailFragment : val eventId = actionData.data.toString() roomDetailViewModel.process(RoomDetailActions.EnterReplyMode(eventId)) } + MessageMenuViewModel.ACTION_COPY_PERMALINK -> { + val eventId = actionData.data.toString() + val permalink = PermalinkFactory.createPermalink(roomDetailArgs.roomId, eventId) + copyToClipboard(requireContext(), permalink, false) + showSnackWithMessage(requireContext().getString(R.string.copied_to_clipboard), Snackbar.LENGTH_SHORT) + + } else -> { Toast.makeText(context, "Action ${actionData.actionId} not implemented", Toast.LENGTH_LONG).show() } diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt index 6a77c97062..ca422f720c 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt @@ -64,7 +64,7 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes ) ) } - + val actions = ArrayList<SimpleAction>().apply { if (event.sendState == SendState.SENDING) { @@ -123,7 +123,7 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes if (event.isEncrypted()) { this.add(SimpleAction(VIEW_DECRYPTED_SOURCE, R.string.view_decrypted_source, R.drawable.ic_view_source, parcel.eventId)) } - this.add(SimpleAction(PERMALINK, R.string.permalink, R.drawable.ic_permalink, parcel.eventId)) + this.add(SimpleAction(ACTION_COPY_PERMALINK, R.string.permalink, R.drawable.ic_permalink, parcel.eventId)) if (currentSession.sessionParams.credentials.userId != event.root.sender) { //not sent by me @@ -225,7 +225,7 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes const val ACTION_DELETE = "delete" const val VIEW_SOURCE = "VIEW_SOURCE" const val VIEW_DECRYPTED_SOURCE = "VIEW_DECRYPTED_SOURCE" - const val PERMALINK = "PERMALINK" + const val ACTION_COPY_PERMALINK = "ACTION_COPY_PERMALINK" const val ACTION_FLAG = "ACTION_FLAG" const val ACTION_QUICK_REACT = "ACTION_QUICK_REACT" const val ACTION_VIEW_REACTIONS = "ACTION_VIEW_REACTIONS" diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt index 247ab070b5..e354be2f74 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt @@ -72,7 +72,7 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback, O setupRecyclerView() roomListViewModel.subscribe { renderState(it) } roomListViewModel.openRoomLiveData.observeEvent(this) { - navigator.openRoom(it) + navigator.openRoom(it, requireActivity()) } createChatFabMenu.listener = this @@ -116,7 +116,7 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback, O override fun openRoomDirectory() { - navigator.openRoomDirectory() + navigator.openRoomDirectory(requireActivity()) } override fun createDirectChat() { diff --git a/vector/src/main/java/im/vector/riotredesign/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/riotredesign/features/navigation/DefaultNavigator.kt index 307d4fe812..00207950ba 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/navigation/DefaultNavigator.kt @@ -17,6 +17,7 @@ package im.vector.riotredesign.features.navigation import android.app.Activity +import android.content.Context import android.content.Intent import androidx.fragment.app.Fragment import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom @@ -27,29 +28,28 @@ import im.vector.riotredesign.features.roomdirectory.RoomDirectoryActivity import im.vector.riotredesign.features.roomdirectory.roompreview.RoomPreviewActivity import im.vector.riotredesign.features.settings.VectorSettingsActivity -class DefaultNavigator(private val fraqment: Fragment) : Navigator { +class DefaultNavigator : Navigator { - val activity: Activity = fraqment.requireActivity() - override fun openRoom(roomId: String) { + override fun openRoom(roomId: String, context: Context) { val args = RoomDetailArgs(roomId) - val intent = RoomDetailActivity.newIntent(activity, args) - activity.startActivity(intent) + val intent = RoomDetailActivity.newIntent(context, args) + context.startActivity(intent) } - override fun openRoomPreview(publicRoom: PublicRoom) { - val intent = RoomPreviewActivity.getIntent(activity, publicRoom) - activity.startActivity(intent) + override fun openRoomPreview(publicRoom: PublicRoom, context: Context) { + val intent = RoomPreviewActivity.getIntent(context, publicRoom) + context.startActivity(intent) } - override fun openRoomDirectory() { - val intent = Intent(activity, RoomDirectoryActivity::class.java) - activity.startActivity(intent) + override fun openRoomDirectory(context: Context) { + val intent = Intent(context, RoomDirectoryActivity::class.java) + context.startActivity(intent) } - override fun openSettings() { - val intent = VectorSettingsActivity.getIntent(activity, "TODO") - activity.startActivity(intent) + override fun openSettings(context: Context) { + val intent = VectorSettingsActivity.getIntent(context, "TODO") + context.startActivity(intent) } override fun openDebug() { diff --git a/vector/src/main/java/im/vector/riotredesign/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/riotredesign/features/navigation/Navigator.kt index 518e8de818..0229596474 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/navigation/Navigator.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/navigation/Navigator.kt @@ -16,17 +16,18 @@ package im.vector.riotredesign.features.navigation +import android.content.Context import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom interface Navigator { - fun openRoom(roomId: String) + fun openRoom(roomId: String, context: Context) - fun openRoomPreview(publicRoom: PublicRoom) + fun openRoomPreview(publicRoom: PublicRoom, context: Context) - fun openRoomDirectory() + fun openRoomDirectory(context: Context) - fun openSettings() + fun openSettings(context: Context) fun openDebug() diff --git a/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/PublicRoomsFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/PublicRoomsFragment.kt index aae438b7d6..5deab028f2 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/PublicRoomsFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/PublicRoomsFragment.kt @@ -124,12 +124,12 @@ class PublicRoomsFragment : VectorBaseFragment(), PublicRoomsController.Callback when (joinState) { JoinState.JOINED -> { - navigator.openRoom(publicRoom.roomId) + navigator.openRoom(publicRoom.roomId, requireActivity()) } JoinState.NOT_JOINED, JoinState.JOINING_ERROR -> { // ROOM PREVIEW - navigator.openRoomPreview(publicRoom) + navigator.openRoomPreview(publicRoom, requireActivity()) } else -> { Snackbar.make(publicRoomsCoordinator, getString(R.string.please_wait), Snackbar.LENGTH_SHORT) diff --git a/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/roompreview/RoomPreviewNoPreviewFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/roompreview/RoomPreviewNoPreviewFragment.kt index 074995557a..3f389a4609 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/roompreview/RoomPreviewNoPreviewFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/roompreview/RoomPreviewNoPreviewFragment.kt @@ -108,7 +108,7 @@ class RoomPreviewNoPreviewFragment : VectorBaseFragment() { // Quit this screen requireActivity().finish() // Open room - navigator.openRoom(roomPreviewData.roomId) + navigator.openRoom(roomPreviewData.roomId, requireActivity()) } } } \ No newline at end of file From 3f1bf00fdd3c5a746088c1983d8cd1e68aa4f60f Mon Sep 17 00:00:00 2001 From: Valere <valeref@matrix.org> Date: Thu, 6 Jun 2019 12:59:26 +0200 Subject: [PATCH 10/18] Fix / use emoji Compat font for view reaction screen --- .../timeline/action/ReactionInfoSimpleItem.kt | 13 ++++++++++--- .../timeline/action/ViewReactionBottomSheet.kt | 11 +++++++---- .../timeline/action/ViewReactionsEpoxyController.kt | 8 ++++++-- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ReactionInfoSimpleItem.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ReactionInfoSimpleItem.kt index 7c7e253167..647af4b0f8 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ReactionInfoSimpleItem.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ReactionInfoSimpleItem.kt @@ -1,5 +1,6 @@ package im.vector.riotredesign.features.home.room.detail.timeline.action +import android.graphics.Typeface import android.widget.TextView import androidx.core.view.isVisible import com.airbnb.epoxy.EpoxyAttribute @@ -8,7 +9,9 @@ import com.airbnb.epoxy.EpoxyModelWithHolder import im.vector.riotredesign.R import im.vector.riotredesign.core.epoxy.VectorEpoxyHolder - +/** + * Item displaying an emoji reaction (single line with emoji, author, time) + */ @EpoxyModelClass(layout = R.layout.item_simple_reaction_info) abstract class ReactionInfoSimpleItem : EpoxyModelWithHolder<ReactionInfoSimpleItem.Holder>() { @@ -19,8 +22,12 @@ abstract class ReactionInfoSimpleItem : EpoxyModelWithHolder<ReactionInfoSimpleI @EpoxyAttribute var timeStamp: CharSequence? = null + @EpoxyAttribute + var emojiTypeFace: Typeface? = null + override fun bind(holder: Holder) { - holder.titleView.text = reactionKey + holder.emojiReactionView.text = reactionKey + holder.emojiReactionView.typeface = emojiTypeFace ?: Typeface.DEFAULT holder.displayNameView.text = authorDisplayName timeStamp?.let { holder.timeStampView.text = it @@ -31,7 +38,7 @@ abstract class ReactionInfoSimpleItem : EpoxyModelWithHolder<ReactionInfoSimpleI } class Holder : VectorEpoxyHolder() { - val titleView by bind<TextView>(R.id.itemSimpleReactionInfoKey) + val emojiReactionView by bind<TextView>(R.id.itemSimpleReactionInfoKey) val displayNameView by bind<TextView>(R.id.itemSimpleReactionInfoMemberName) val timeStampView by bind<TextView>(R.id.itemSimpleReactionInfoTime) } diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ViewReactionBottomSheet.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ViewReactionBottomSheet.kt index a53dc3bd78..308c68bf9d 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ViewReactionBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ViewReactionBottomSheet.kt @@ -11,24 +11,27 @@ import butterknife.BindView import butterknife.ButterKnife import com.airbnb.epoxy.EpoxyRecyclerView import com.airbnb.mvrx.MvRx -import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState +import im.vector.riotredesign.EmojiCompatFontProvider import im.vector.riotredesign.R import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData import kotlinx.android.synthetic.main.bottom_sheet_display_reactions.* +import org.koin.android.ext.android.inject - +/** + * Bottom sheet displaying list of reactions for a given event ordered by timestamp + */ class ViewReactionBottomSheet : BaseMvRxBottomSheetDialog() { private val viewModel: ViewReactionViewModel by fragmentViewModel(ViewReactionViewModel::class) - private val eventArgs: TimelineEventFragmentArgs by args() + private val emojiCompatFontProvider by inject<EmojiCompatFontProvider>() @BindView(R.id.bottom_sheet_display_reactions_list) lateinit var epoxyRecyclerView: EpoxyRecyclerView - private val epoxyController by lazy { ViewReactionsEpoxyController() } + private val epoxyController by lazy { ViewReactionsEpoxyController(emojiCompatFontProvider.typeface) } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { val view = inflater.inflate(R.layout.bottom_sheet_display_reactions, container, false) diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ViewReactionsEpoxyController.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ViewReactionsEpoxyController.kt index a3d146a24f..461fec2b13 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ViewReactionsEpoxyController.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ViewReactionsEpoxyController.kt @@ -1,15 +1,19 @@ package im.vector.riotredesign.features.home.room.detail.timeline.action +import android.graphics.Typeface import com.airbnb.epoxy.TypedEpoxyController - -class ViewReactionsEpoxyController : TypedEpoxyController<DisplayReactionsViewState>() { +/** + * Epoxy controller for reaction event list + */ +class ViewReactionsEpoxyController(private val emojiCompatTypeface: Typeface?) : TypedEpoxyController<DisplayReactionsViewState>() { override fun buildModels(state: DisplayReactionsViewState) { val map = state.mapReactionKeyToMemberList() ?: return map.forEach { reactionInfoSimpleItem { id(it.eventId) + emojiTypeFace(emojiCompatTypeface) timeStamp(it.timestamp) reactionKey(it.reactionKey) authorDisplayName(it.authorName ?: it.authorId) From 740900394916a91ccd112a0366dec6953b102c0c Mon Sep 17 00:00:00 2001 From: Valere <valeref@matrix.org> Date: Fri, 7 Jun 2019 10:01:42 +0200 Subject: [PATCH 11/18] Fix / Bug aggregation on initial sync fix / All messages were not processed due to a test exiting the for loop + started adding context menu for non room messages --- matrix-sdk-android/build.gradle | 4 +- .../internal/database/mapper/EventMapper.kt | 13 +- .../room/EventRelationsAggregationTask.kt | 198 ++++++++++-------- .../room/EventRelationsAggregationUpdater.kt | 6 +- .../android/internal/task/TaskExecutor.kt | 7 +- vector/build.gradle | 6 +- .../home/room/detail/RoomDetailFragment.kt | 4 +- .../home/room/detail/RoomDetailViewModel.kt | 3 +- .../timeline/TimelineEventController.kt | 9 +- .../timeline/action/MessageMenuViewModel.kt | 22 +- .../timeline/factory/NoticeItemFactory.kt | 19 +- .../timeline/factory/TimelineItemFactory.kt | 35 +++- .../helper/TimelineDisplayableEvents.kt | 9 + .../room/detail/timeline/item/NoticeItem.kt | 29 ++- .../item_timeline_event_base_noinfo.xml | 3 +- 15 files changed, 233 insertions(+), 134 deletions(-) diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 1a0c0f9123..3fda5a6372 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -48,7 +48,7 @@ android { buildConfigField "boolean", "LOG_PRIVATE_DATA", "false" // Set to BODY instead of NONE to enable logging - buildConfigField "okhttp3.logging.HttpLoggingInterceptor.Level", "OKHTTP_LOGGING_LEVEL", "okhttp3.logging.HttpLoggingInterceptor.Level.BASIC" + buildConfigField "okhttp3.logging.HttpLoggingInterceptor.Level", "OKHTTP_LOGGING_LEVEL", "okhttp3.logging.HttpLoggingInterceptor.Level.NONE" } release { @@ -91,7 +91,7 @@ dependencies { def moshi_version = '1.8.0' def lifecycle_version = '2.0.0' def coroutines_version = "1.0.1" - def markwon_version = '3.0.0-SNAPSHOT' + def markwon_version = '3.0.0' implementation fileTree(dir: 'libs', include: ['*.aar']) implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/EventMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/EventMapper.kt index 3c21437e78..34b29187d3 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/EventMapper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/EventMapper.kt @@ -16,6 +16,7 @@ package im.vector.matrix.android.internal.database.mapper +import com.squareup.moshi.JsonDataException import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.UnsignedData import im.vector.matrix.android.internal.database.model.EventEntity @@ -46,8 +47,16 @@ internal object EventMapper { fun map(eventEntity: EventEntity): Event { //TODO proxy the event to only parse unsigned data when accessed? - var ud = if (eventEntity.unsignedData.isNullOrBlank()) null - else MoshiProvider.providesMoshi().adapter(UnsignedData::class.java).fromJson(eventEntity.unsignedData) + val ud = if (eventEntity.unsignedData.isNullOrBlank()) { + null + } else { + try { + MoshiProvider.providesMoshi().adapter(UnsignedData::class.java).fromJson(eventEntity.unsignedData) + } catch (t: JsonDataException) { + null + } + + } return Event( type = eventEntity.type, eventId = eventEntity.eventId, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationTask.kt index e5fb5493bf..3b02494237 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationTask.kt @@ -27,7 +27,6 @@ import im.vector.matrix.android.internal.database.model.* import im.vector.matrix.android.internal.database.query.create import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.task.Task -import im.vector.matrix.android.internal.util.tryTransactionAsync import im.vector.matrix.android.internal.util.tryTransactionSync import io.realm.Realm import timber.log.Timber @@ -49,60 +48,75 @@ internal class DefaultEventRelationsAggregationTask(private val monarchy: Monarc private val SHOULD_HANDLE_SERVER_AGREGGATION = false override fun execute(params: EventRelationsAggregationTask.Params): Try<Unit> { + val events = params.events + val userId = params.userId return monarchy.tryTransactionSync { realm -> - update(realm, params.events, params.userId) + Timber.v(">>> DefaultEventRelationsAggregationTask[${params.hashCode()}] called with ${events.size} events") + update(realm, events, userId) + Timber.v("<<< DefaultEventRelationsAggregationTask[${params.hashCode()}] finished") } } private fun update(realm: Realm, events: List<Pair<Event, SendState>>, userId: String) { events.forEach { pair -> - val roomId = pair.first.roomId ?: return@forEach - val event = pair.first - val sendState = pair.second - val isLocalEcho = sendState == SendState.UNSENT - when (event.type) { - EventType.REACTION -> { - //we got a reaction!! - Timber.v("###REACTION in room $roomId") - handleReaction(event, roomId, realm, userId, isLocalEcho) + try { //Temporary catch, should be removed + val roomId = pair.first.roomId + if (roomId == null) { + Timber.w("Event has no room id ${pair.first.eventId}") + return@forEach } - EventType.MESSAGE -> { - if (event.unsignedData?.relations?.annotations != null) { - Timber.v("###REACTION Agreggation in room $roomId for event ${event.eventId}") - handleInitialAggregatedRelations(event, roomId, event.unsignedData.relations.annotations, realm) - } else { - val content: MessageContent? = event.content.toModel() - if (content?.relatesTo?.type == RelationType.REPLACE) { - Timber.v("###REPLACE in room $roomId for event ${event.eventId}") - //A replace! - handleReplace(realm, event, content, roomId, isLocalEcho) - } + val event = pair.first + val sendState = pair.second + val isLocalEcho = sendState == SendState.UNSENT + when (event.type) { + EventType.REACTION -> { + //we got a reaction!! + Timber.v("###REACTION in room $roomId , reaction eventID ${event.eventId}") + handleReaction(event, roomId, realm, userId, isLocalEcho) } - - } - EventType.REDACTION -> { - val eventToPrune = event.redacts?.let { EventEntity.where(realm, eventId = it).findFirst() } - ?: return - when (eventToPrune.type) { - EventType.MESSAGE -> { - Timber.d("REDACTION for message ${eventToPrune.eventId}") - val unsignedData = EventMapper.map(eventToPrune).unsignedData - ?: UnsignedData(null, null) - - //was this event a m.replace - val contentModel = ContentMapper.map(eventToPrune.content)?.toModel<MessageContent>() - if (RelationType.REPLACE == contentModel?.relatesTo?.type && contentModel.relatesTo?.eventId != null) { - handleRedactionOfReplace(eventToPrune, contentModel.relatesTo!!.eventId!!, realm) + EventType.MESSAGE -> { + if (event.unsignedData?.relations?.annotations != null) { + Timber.v("###REACTION Agreggation in room $roomId for event ${event.eventId}") + handleInitialAggregatedRelations(event, roomId, event.unsignedData.relations.annotations, realm) + } else { + val content: MessageContent? = event.content.toModel() + if (content?.relatesTo?.type == RelationType.REPLACE) { + Timber.v("###REPLACE in room $roomId for event ${event.eventId}") + //A replace! + handleReplace(realm, event, content, roomId, isLocalEcho) } - } - EventType.REACTION -> { - handleReactionRedact(eventToPrune, realm, userId) + + } + EventType.REDACTION -> { + val eventToPrune = event.redacts?.let { EventEntity.where(realm, eventId = it).findFirst() } + ?: return@forEach + when (eventToPrune.type) { + EventType.MESSAGE -> { + Timber.d("REDACTION for message ${eventToPrune.eventId}") + val unsignedData = EventMapper.map(eventToPrune).unsignedData + ?: UnsignedData(null, null) + + //was this event a m.replace + val contentModel = ContentMapper.map(eventToPrune.content)?.toModel<MessageContent>() + if (RelationType.REPLACE == contentModel?.relatesTo?.type && contentModel.relatesTo?.eventId != null) { + handleRedactionOfReplace(eventToPrune, contentModel.relatesTo!!.eventId!!, realm) + } + + } + EventType.REACTION -> { + handleReactionRedact(eventToPrune, realm, userId) + } } } + else -> Timber.v("UnHandled event ${event.eventId}") } + + } catch (t: Throwable) { + Timber.e(t, "## Should not happen ") } } + } private fun handleReplace(realm: Realm, event: Event, content: MessageContent, roomId: String, isLocalEcho: Boolean) { @@ -112,7 +126,7 @@ internal class DefaultEventRelationsAggregationTask(private val monarchy: Monarc //ok, this is a replace var existing = EventAnnotationsSummaryEntity.where(realm, targetEventId).findFirst() if (existing == null) { - Timber.v("###REPLACE creating no relation summary for ${targetEventId}") + Timber.v("###REPLACE creating new relation summary for ${targetEventId}") existing = EventAnnotationsSummaryEntity.create(realm, targetEventId) existing.roomId = roomId } @@ -120,7 +134,7 @@ internal class DefaultEventRelationsAggregationTask(private val monarchy: Monarc //we have it val existingSummary = existing.editSummary if (existingSummary == null) { - Timber.v("###REPLACE no edit summary for ${targetEventId}, creating one (localEcho:$isLocalEcho)") + Timber.v("###REPLACE new edit summary for ${targetEventId}, creating one (localEcho:$isLocalEcho)") //create the edit summary val editSummary = realm.createObject(EditAggregatedSummaryEntity::class.java) editSummary.lastEditTs = event.originServerTs ?: System.currentTimeMillis() @@ -181,62 +195,70 @@ internal class DefaultEventRelationsAggregationTask(private val monarchy: Monarc } private fun handleReaction(event: Event, roomId: String, realm: Realm, userId: String, isLocalEcho: Boolean) { - event.content.toModel<ReactionContent>()?.let { content -> - //rel_type must be m.annotation - if (RelationType.ANNOTATION == content.relatesTo?.type) { - val reaction = content.relatesTo.key - val eventId = content.relatesTo.eventId - val eventSummary = EventAnnotationsSummaryEntity.where(realm, eventId).findFirst() - ?: EventAnnotationsSummaryEntity.create(realm, eventId).apply { this.roomId = roomId } + val content = event.content.toModel<ReactionContent>() + if (content == null) { + Timber.e("Malformed reaction content ${event.content}") + return + } + //rel_type must be m.annotation + if (RelationType.ANNOTATION == content.relatesTo?.type) { + val reaction = content.relatesTo.key + val relatedEventID = content.relatesTo.eventId + val reactionEventId = event.eventId + Timber.v("Reaction $reactionEventId relates to $relatedEventID") + val eventSummary = EventAnnotationsSummaryEntity.where(realm, relatedEventID).findFirst() + ?: EventAnnotationsSummaryEntity.create(realm, relatedEventID).apply { this.roomId = roomId } - var sum = eventSummary.reactionsSummary.find { it.key == reaction } - val txId = event.unsignedData?.transactionId - if (isLocalEcho && txId.isNullOrBlank()) { - Timber.w("Received a local echo with no transaction ID") - } - if (sum == null) { - sum = realm.createObject(ReactionAggregatedSummaryEntity::class.java) - sum.key = reaction - sum.firstTimestamp = event.originServerTs ?: 0 - if (isLocalEcho) { - Timber.v("Adding local echo reaction $reaction") - sum.sourceLocalEcho.add(txId) - sum.count = 1 - } else { - Timber.v("Adding synced reaction $reaction") - sum.count = 1 - sum.sourceEvents.add(event.eventId) - } - sum.addedByMe = sum.addedByMe || (userId == event.sender) - eventSummary.reactionsSummary.add(sum) + var sum = eventSummary.reactionsSummary.find { it.key == reaction } + val txId = event.unsignedData?.transactionId + if (isLocalEcho && txId.isNullOrBlank()) { + Timber.w("Received a local echo with no transaction ID") + } + if (sum == null) { + sum = realm.createObject(ReactionAggregatedSummaryEntity::class.java) + sum.key = reaction + sum.firstTimestamp = event.originServerTs ?: 0 + if (isLocalEcho) { + Timber.v("Adding local echo reaction $reaction") + sum.sourceLocalEcho.add(txId) + sum.count = 1 } else { - //is this a known event (is possible? pagination?) - if (!sum.sourceEvents.contains(eventId)) { + Timber.v("Adding synced reaction $reaction") + sum.count = 1 + sum.sourceEvents.add(reactionEventId) + } + sum.addedByMe = sum.addedByMe || (userId == event.sender) + eventSummary.reactionsSummary.add(sum) + } else { + //is this a known event (is possible? pagination?) + if (!sum.sourceEvents.contains(reactionEventId)) { - //check if it's not the sync of a local echo - if (!isLocalEcho && sum.sourceLocalEcho.contains(txId)) { - //ok it has already been counted, just sync the list, do not touch count - Timber.v("Ignoring synced of local echo for reaction $reaction") - sum.sourceLocalEcho.remove(txId) - sum.sourceEvents.add(event.eventId) + //check if it's not the sync of a local echo + if (!isLocalEcho && sum.sourceLocalEcho.contains(txId)) { + //ok it has already been counted, just sync the list, do not touch count + Timber.v("Ignoring synced of local echo for reaction $reaction") + sum.sourceLocalEcho.remove(txId) + sum.sourceEvents.add(reactionEventId) + } else { + sum.count += 1 + if (isLocalEcho) { + Timber.v("Adding local echo reaction $reaction") + sum.sourceLocalEcho.add(txId) } else { - sum.count += 1 - if (isLocalEcho) { - Timber.v("Adding local echo reaction $reaction") - sum.sourceLocalEcho.add(txId) - } else { - Timber.v("Adding synced reaction $reaction") - sum.sourceEvents.add(event.eventId) - } - - sum.addedByMe = sum.addedByMe || (userId == event.sender) + Timber.v("Adding synced reaction $reaction") + sum.sourceEvents.add(reactionEventId) } + sum.addedByMe = sum.addedByMe || (userId == event.sender) } - } + } } + + } else { + Timber.e("Unknwon relation type ${content.relatesTo?.type} for event ${event.eventId}") } + } /** diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationUpdater.kt index 0b29064c87..a04ed2160a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationUpdater.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationUpdater.kt @@ -46,11 +46,11 @@ internal class EventRelationsAggregationUpdater(monarchy: Monarchy, override fun processChanges(inserted: List<EventEntity>, updated: List<EventEntity>, deleted: List<EventEntity>) { Timber.v("EventRelationsAggregationUpdater called with ${inserted.size} insertions") - val inserted = inserted - .mapNotNull { it.asDomain() to it.sendState } + val domainInserted = inserted + .map { it.asDomain() to it.sendState } val params = EventRelationsAggregationTask.Params( - inserted, + domainInserted, credentials.userId ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/TaskExecutor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/TaskExecutor.kt index a685581764..caf75bd7b2 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/TaskExecutor.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/TaskExecutor.kt @@ -38,7 +38,12 @@ internal class TaskExecutor(private val coroutineDispatchers: MatrixCoroutineDis task.execute(task.params) } } - resultOrFailure.fold({ task.callback.onFailure(it) }, { task.callback.onSuccess(it) }) + resultOrFailure.fold({ + Timber.d(it, "Task failed") + task.callback.onFailure(it) + }, { + task.callback.onSuccess(it) + }) } return CancelableCoroutine(job) } diff --git a/vector/build.gradle b/vector/build.gradle index 4cec152fde..263df78e27 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -84,13 +84,15 @@ android { debug { resValue "bool", "debug_mode", "true" buildConfigField "boolean", "LOW_PRIVACY_LOG_ENABLE", "false" - + buildConfigField "boolean", "SHOW_HIDDEN_TIMELINE_EVENTS", "false" + signingConfig signingConfigs.debug } release { resValue "bool", "debug_mode", "false" buildConfigField "boolean", "LOW_PRIVACY_LOG_ENABLE", "false" + buildConfigField "boolean", "SHOW_HIDDEN_TIMELINE_EVENTS", "false" minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' @@ -132,7 +134,7 @@ dependencies { def epoxy_version = "3.3.0" def arrow_version = "0.8.2" def coroutines_version = "1.0.1" - def markwon_version = '3.0.0-SNAPSHOT' + def markwon_version = '3.0.0' def big_image_viewer_version = '1.5.6' def glide_version = '4.9.0' def moshi_version = '1.8.0' diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt index 1da6ed9d13..552843258e 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt @@ -563,11 +563,11 @@ class RoomDetailFragment : vectorBaseActivity.notImplemented() } - override fun onEventCellClicked(informationData: MessageInformationData, messageContent: MessageContent, view: View) { + override fun onEventCellClicked(informationData: MessageInformationData, messageContent: MessageContent?, view: View) { } - override fun onEventLongClicked(informationData: MessageInformationData, messageContent: MessageContent, view: View): Boolean { + override fun onEventLongClicked(informationData: MessageInformationData, messageContent: MessageContent?, view: View): Boolean { view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) val roomId = roomDetailArgs.roomId diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt index 351129a2f1..346d21c35f 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt @@ -55,7 +55,8 @@ class RoomDetailViewModel(initialState: RoomDetailViewState, private val roomId = initialState.roomId private val eventId = initialState.eventId private val displayedEventsObservable = BehaviorRelay.create<RoomDetailActions.EventDisplayed>() - private val timeline = room.createTimeline(eventId, TimelineDisplayableEvents.DISPLAYABLE_TYPES) + private val timeline = room.createTimeline(eventId, + if (TimelineDisplayableEvents.DEBUG_HIDDEN_EVENT) TimelineDisplayableEvents.DEBUG_DISPLAYABLE_TYPES else TimelineDisplayableEvents.DISPLAYABLE_TYPES) companion object : MvRxViewModelFactory<RoomDetailViewModel, RoomDetailViewState> { diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt index 2861ae6a70..2a436a192c 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt @@ -46,15 +46,13 @@ class TimelineEventController(private val dateFormatter: TimelineDateFormatter, private val backgroundHandler: Handler = TimelineAsyncHelper.getBackgroundHandler() ) : EpoxyController(backgroundHandler, backgroundHandler), Timeline.Listener { - interface Callback : ReactionPillCallback, AvatarCallback { + interface Callback : ReactionPillCallback, AvatarCallback, BaseCallback { fun onEventVisible(event: TimelineEvent) fun onUrlClicked(url: String) fun onImageMessageClicked(messageImageContent: MessageImageContent, mediaData: ImageContentRenderer.Data, view: View) fun onVideoMessageClicked(messageVideoContent: MessageVideoContent, mediaData: VideoContentRenderer.Data, view: View) fun onFileMessageClicked(messageFileContent: MessageFileContent) fun onAudioMessageClicked(messageAudioContent: MessageAudioContent) - fun onEventCellClicked(informationData: MessageInformationData, messageContent: MessageContent, view: View) - fun onEventLongClicked(informationData: MessageInformationData, messageContent: MessageContent, view: View): Boolean fun onEditedDecorationClicked(informationData: MessageInformationData, editAggregatedSummary: EditAggregatedSummary?) } @@ -63,6 +61,11 @@ class TimelineEventController(private val dateFormatter: TimelineDateFormatter, fun onLongClickOnReactionPill(informationData: MessageInformationData, reaction: String) } + interface BaseCallback { + fun onEventCellClicked(informationData: MessageInformationData, messageContent: MessageContent?, view: View) + fun onEventLongClicked(informationData: MessageInformationData, messageContent: MessageContent?, view: View): Boolean + } + interface AvatarCallback { fun onAvatarClicked(informationData: MessageInformationData) fun onMemberNameClicked(informationData: MessageInformationData) diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt index ca422f720c..6d99d41a2b 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt @@ -34,7 +34,7 @@ import org.koin.android.ext.android.get data class SimpleAction(val uid: String, val titleRes: Int, val iconResId: Int?, val data: Any? = null) -data class MessageMenuState(val actions: List<SimpleAction>) : MvRxState +data class MessageMenuState(val actions: List<SimpleAction> = emptyList()) : MvRxState /** * Manages list actions for a given message (copy / paste / forward...) @@ -50,9 +50,9 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes val event = currentSession.getRoom(parcel.roomId)?.getTimeLineEvent(parcel.eventId) ?: return null - val messageContent: MessageContent = event.annotations?.editSummary?.aggregatedContent?.toModel() - ?: event.root.content.toModel() ?: return null - val type = messageContent.type + val messageContent: MessageContent? = event.annotations?.editSummary?.aggregatedContent?.toModel() + ?: event.root.content.toModel() + val type = messageContent?.type if (event.sendState == SendState.UNSENT) { //Resend and Delete @@ -76,7 +76,7 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes this.add(SimpleAction(ACTION_ADD_REACTION, R.string.message_add_reaction, R.drawable.ic_add_reaction, event.root.eventId)) if (canCopy(type)) { //TODO copy images? html? see ClipBoard - this.add(SimpleAction(ACTION_COPY, R.string.copy, R.drawable.ic_copy, messageContent.body)) + this.add(SimpleAction(ACTION_COPY, R.string.copy, R.drawable.ic_copy, messageContent!!.body)) } if (canReply(event, messageContent)) { @@ -134,10 +134,10 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes return MessageMenuState(actions) } - private fun canReply(event: TimelineEvent, messageContent: MessageContent): Boolean { + private fun canReply(event: TimelineEvent, messageContent: MessageContent?): Boolean { //Only event of type Event.EVENT_TYPE_MESSAGE are supported for the moment if (event.root.type != EventType.MESSAGE) return false - return when (messageContent.type) { + return when (messageContent?.type) { MessageType.MSGTYPE_TEXT, MessageType.MSGTYPE_NOTICE, MessageType.MSGTYPE_EMOTE, @@ -149,10 +149,10 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes } } - private fun canQuote(event: TimelineEvent, messageContent: MessageContent): Boolean { + private fun canQuote(event: TimelineEvent, messageContent: MessageContent?): Boolean { //Only event of type Event.EVENT_TYPE_MESSAGE are supported for the moment if (event.root.type != EventType.MESSAGE) return false - return when (messageContent.type) { + return when (messageContent?.type) { MessageType.MSGTYPE_TEXT, MessageType.MSGTYPE_NOTICE, MessageType.MSGTYPE_EMOTE, @@ -190,7 +190,7 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes } - private fun canCopy(type: String): Boolean { + private fun canCopy(type: String?): Boolean { return when (type) { MessageType.MSGTYPE_TEXT, MessageType.MSGTYPE_NOTICE, @@ -204,7 +204,7 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes } - private fun canShare(type: String): Boolean { + private fun canShare(type: String?): Boolean { return when (type) { MessageType.MSGTYPE_IMAGE, MessageType.MSGTYPE_AUDIO, diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/NoticeItemFactory.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/NoticeItemFactory.kt index fc756c9c12..dabebd2807 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/NoticeItemFactory.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/NoticeItemFactory.kt @@ -17,23 +17,32 @@ package im.vector.riotredesign.features.home.room.detail.timeline.factory import im.vector.matrix.android.api.session.room.timeline.TimelineEvent +import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController import im.vector.riotredesign.features.home.room.detail.timeline.format.NoticeEventFormatter import im.vector.riotredesign.features.home.room.detail.timeline.helper.senderAvatar import im.vector.riotredesign.features.home.room.detail.timeline.helper.senderName +import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem_ class NoticeItemFactory(private val eventFormatter: NoticeEventFormatter) { - fun create(event: TimelineEvent): NoticeItem? { + fun create(event: TimelineEvent, + callback: TimelineEventController.Callback?): NoticeItem? { val formattedText = eventFormatter.format(event) ?: return null - val senderName = event.senderName() - val senderAvatar = event.senderAvatar() + val informationData = MessageInformationData( + eventId = event.root.eventId ?: "?", + senderId = event.root.sender ?: "", + sendState = event.sendState, + avatarUrl = event.senderAvatar(), + memberName = event.senderName(), + showInformation = false + ) return NoticeItem_() .noticeText(formattedText) - .avatarUrl(senderAvatar) - .memberName(senderName) + .informationData(informationData) + .baseCallback(callback) } diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/TimelineItemFactory.kt index 022b7b0b10..c67ce71873 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/TimelineItemFactory.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/TimelineItemFactory.kt @@ -17,10 +17,16 @@ package im.vector.riotredesign.features.home.room.detail.timeline.factory 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.room.model.message.MessageContent +import im.vector.matrix.android.api.session.room.model.message.MessageDefaultContent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.riotredesign.core.epoxy.EmptyItem_ import im.vector.riotredesign.core.epoxy.VectorEpoxyModel import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController +import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDisplayableEvents +import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData +import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageTextItem_ import timber.log.Timber class TimelineItemFactory(private val messageItemFactory: MessageItemFactory, @@ -43,7 +49,7 @@ class TimelineItemFactory(private val messageItemFactory: MessageItemFactory, EventType.STATE_HISTORY_VISIBILITY, EventType.CALL_INVITE, EventType.CALL_HANGUP, - EventType.CALL_ANSWER -> noticeItemFactory.create(event) + EventType.CALL_ANSWER -> noticeItemFactory.create(event, callback) // Unhandled event types (yet) EventType.ENCRYPTED, @@ -51,9 +57,32 @@ class TimelineItemFactory(private val messageItemFactory: MessageItemFactory, EventType.STATE_ROOM_THIRD_PARTY_INVITE, EventType.STICKER, EventType.STATE_ROOM_CREATE -> defaultItemFactory.create(event) + else -> { - Timber.w("Ignored event (type: ${event.root.type}") - null + //These are just for debug to display hidden event, they should be filtered out in normal mode + if (TimelineDisplayableEvents.DEBUG_HIDDEN_EVENT) { + val informationData = MessageInformationData(eventId = event.root.eventId + ?: "?", + senderId = event.root.sender ?: "", + sendState = event.sendState, + time = "", + avatarUrl = null, + memberName = "", + showInformation = false + ) + val messageContent = event.root.content.toModel<MessageContent>() + ?: MessageDefaultContent("", "", null, null) + MessageTextItem_() + .informationData(informationData) + .message("{ \"type\": ${event.root.type} }") + .longClickListener { view -> + return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view) + ?: false + } + } else { + Timber.w("Ignored event (type: ${event.root.type}") + null + } } } } catch (e: Exception) { diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt index 657d9e0367..711b870ee2 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt @@ -22,10 +22,14 @@ import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.RoomMember import im.vector.matrix.android.api.session.room.model.message.MessageContent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent +import im.vector.riotredesign.BuildConfig import im.vector.riotredesign.core.extensions.localDateTime object TimelineDisplayableEvents { + //Debug helper, to show invisible items in time line (reaction, redacts) + val DEBUG_HIDDEN_EVENT = BuildConfig.SHOW_HIDDEN_TIMELINE_EVENTS + val DISPLAYABLE_TYPES = listOf( EventType.MESSAGE, EventType.STATE_ROOM_NAME, @@ -41,6 +45,11 @@ object TimelineDisplayableEvents { EventType.STICKER, EventType.STATE_ROOM_CREATE ) + + val DEBUG_DISPLAYABLE_TYPES = DISPLAYABLE_TYPES + listOf( + EventType.REDACTION, + EventType.REACTION + ) } fun TimelineEvent.isDisplayable(): Boolean { diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/NoticeItem.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/NoticeItem.kt index dcb0bdf4ad..914cb23252 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/NoticeItem.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/NoticeItem.kt @@ -23,27 +23,37 @@ import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.riotredesign.R import im.vector.riotredesign.features.home.AvatarRenderer +import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController @EpoxyModelClass(layout = R.layout.item_timeline_event_base_noinfo) abstract class NoticeItem : BaseEventItem<NoticeItem.Holder>() { @EpoxyAttribute var noticeText: CharSequence? = null - @EpoxyAttribute - var avatarUrl: String? = null - @EpoxyAttribute - var userId: String = "" - @EpoxyAttribute - var memberName: CharSequence? = null - @EpoxyAttribute - var longClickListener: View.OnLongClickListener? = null + lateinit var informationData: MessageInformationData + + @EpoxyAttribute + var baseCallback: TimelineEventController.BaseCallback? = null + + + private var longClickListener = View.OnLongClickListener { + baseCallback?.onEventLongClicked(informationData, null, it) + baseCallback != null + } + override fun bind(holder: Holder) { super.bind(holder) holder.noticeTextView.text = noticeText - AvatarRenderer.render(avatarUrl, userId, memberName?.toString(), holder.avatarImageView) + AvatarRenderer.render( + informationData.avatarUrl, + informationData.senderId, + informationData.memberName?.toString() + ?: informationData.senderId, + holder.avatarImageView + ) holder.view.setOnLongClickListener(longClickListener) } @@ -51,7 +61,6 @@ abstract class NoticeItem : BaseEventItem<NoticeItem.Holder>() { class Holder : BaseHolder() { override fun getStubId(): Int = STUB_ID - val avatarImageView by bind<ImageView>(R.id.itemNoticeAvatarView) val noticeTextView by bind<TextView>(R.id.itemNoticeTextView) } diff --git a/vector/src/main/res/layout/item_timeline_event_base_noinfo.xml b/vector/src/main/res/layout/item_timeline_event_base_noinfo.xml index 07b43f7bc1..8163b3acdd 100644 --- a/vector/src/main/res/layout/item_timeline_event_base_noinfo.xml +++ b/vector/src/main/res/layout/item_timeline_event_base_noinfo.xml @@ -4,6 +4,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:addStatesFromChildren="true" + android:background="?attr/selectableItemBackground" android:paddingLeft="8dp" android:paddingRight="8dp"> @@ -31,9 +32,9 @@ <ViewStub android:id="@+id/messageContentBlankStub" style="@style/TimelineContentStubNoInfoLayoutParams" - android:layout="@layout/item_timeline_event_blank_stub" android:layout_width="0dp" android:layout_height="0dp" + android:layout="@layout/item_timeline_event_blank_stub" tools:ignore="MissingConstraints" /> <ViewStub From 5cf9deb329bf50c383e13159c7d1ecc84c1512c8 Mon Sep 17 00:00:00 2001 From: Valere <valeref@matrix.org> Date: Fri, 7 Jun 2019 10:45:24 +0200 Subject: [PATCH 12/18] Menu action for non room messages --- .../action/MessageActionsBottomSheet.kt | 31 ++++++++++++------- .../action/MessageActionsViewModel.kt | 27 +++++++++------- .../timeline/action/MessageMenuViewModel.kt | 11 +++++-- .../timeline/action/QuickReactionViewModel.kt | 21 ++++++------- .../room/detail/timeline/item/NoticeItem.kt | 1 - .../layout/bottom_sheet_message_actions.xml | 15 ++++++--- 6 files changed, 64 insertions(+), 42 deletions(-) diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt index fa94043ed4..7ea6a9b1b8 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt @@ -23,6 +23,7 @@ import android.view.ViewGroup import android.widget.FrameLayout import android.widget.ImageView import android.widget.TextView +import androidx.core.view.isVisible import androidx.lifecycle.ViewModelProviders import butterknife.BindView import butterknife.ButterKnife @@ -35,6 +36,7 @@ import im.vector.riotredesign.R import im.vector.riotredesign.core.glide.GlideApp import im.vector.riotredesign.features.home.AvatarRenderer import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData +import kotlinx.android.synthetic.main.bottom_sheet_message_actions.* /** * Bottom sheet fragment that shows a message preview with list of contextual actions @@ -115,20 +117,27 @@ class MessageActionsBottomSheet : BaseMvRxBottomSheetDialog() { } override fun invalidate() = withState(viewModel) { - senderNameTextView.text = it.senderName - messageBodyTextView.text = it.messageBody - messageTimestampText.text = it.ts + if (it.showPreview) { + bottom_sheet_message_preview.isVisible = true + senderNameTextView.text = it.senderName + messageBodyTextView.text = it.messageBody + messageTimestampText.text = it.ts - GlideApp.with(this).clear(senderAvatarImageView) - if (it.senderAvatarPath != null) { - GlideApp.with(this) - .load(it.senderAvatarPath) - .circleCrop() - .placeholder(AvatarRenderer.getPlaceholderDrawable(requireContext(), it.userId, it.senderName)) - .into(senderAvatarImageView) + GlideApp.with(this).clear(senderAvatarImageView) + if (it.senderAvatarPath != null) { + GlideApp.with(this) + .load(it.senderAvatarPath) + .circleCrop() + .placeholder(AvatarRenderer.getPlaceholderDrawable(requireContext(), it.userId, it.senderName)) + .into(senderAvatarImageView) + } else { + senderAvatarImageView.setImageDrawable(AvatarRenderer.getPlaceholderDrawable(requireContext(), it.userId, it.senderName)) + } } else { - senderAvatarImageView.setImageDrawable(AvatarRenderer.getPlaceholderDrawable(requireContext(), it.userId, it.senderName)) + bottom_sheet_message_preview.isVisible = false } + quickReactBottomDivider.isVisible = it.canReact + bottom_sheet_quick_reaction_container.isVisible = it.canReact return@withState } diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index a307afe35a..2a925894b3 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -19,6 +19,7 @@ import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.ViewModelContext 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.toModel import im.vector.matrix.android.api.session.room.model.message.MessageContent import im.vector.matrix.android.api.session.room.model.message.MessageTextContent @@ -34,10 +35,12 @@ import java.util.* data class MessageActionState( - val userId: String, - val senderName: String, - val messageBody: CharSequence, - val ts: String?, + val userId: String = "", + val senderName: String = "", + val messageBody: CharSequence? = null, + val ts: String? = null, + val showPreview: Boolean = false, + val canReact: Boolean = false, val senderAvatarPath: String? = null) : MvRxState @@ -59,21 +62,21 @@ class MessageActionsViewModel(initialState: MessageActionState) : VectorViewMode val messageContent: MessageContent? = event.annotations?.editSummary?.aggregatedContent?.toModel() ?: event.root.content.toModel() val originTs = event.root.originServerTs - var body: CharSequence = messageContent?.body ?: "" + var body: CharSequence? = messageContent?.body if (messageContent is MessageTextContent && messageContent.format == MessageType.FORMAT_MATRIX_HTML) { val parser = Parser.builder().build() val document = parser.parse(messageContent.formattedBody ?: messageContent.body) - // val renderer = HtmlRenderer.builder().build() body = Markwon.builder(viewModelContext.activity) .usePlugin(HtmlPlugin.create()).build().render(document) -// body = renderer.render(document) } MessageActionState( - event.root.sender ?: "", - parcel.informationData.memberName.toString(), - body, - dateFormat.format(Date(originTs ?: 0)), - currentSession.contentUrlResolver().resolveFullSize(parcel.informationData.avatarUrl) + userId = event.root.sender ?: "", + senderName = parcel.informationData.memberName.toString(), + messageBody = body, + ts = dateFormat.format(Date(originTs ?: 0)), + showPreview = event.root.type == EventType.MESSAGE, + canReact = event.root.type == EventType.MESSAGE, + senderAvatarPath = currentSession.contentUrlResolver().resolveFullSize(parcel.informationData.avatarUrl) ) } else { //can this happen? diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt index 6d99d41a2b..d2511eff7e 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt @@ -73,7 +73,9 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes } //TODO is downloading attachement? - this.add(SimpleAction(ACTION_ADD_REACTION, R.string.message_add_reaction, R.drawable.ic_add_reaction, event.root.eventId)) + if (canReact(event, messageContent)) { + this.add(SimpleAction(ACTION_ADD_REACTION, R.string.message_add_reaction, R.drawable.ic_add_reaction, event.root.eventId)) + } if (canCopy(type)) { //TODO copy images? html? see ClipBoard this.add(SimpleAction(ACTION_COPY, R.string.copy, R.drawable.ic_copy, messageContent!!.body)) @@ -125,7 +127,7 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes } this.add(SimpleAction(ACTION_COPY_PERMALINK, R.string.permalink, R.drawable.ic_permalink, parcel.eventId)) - if (currentSession.sessionParams.credentials.userId != event.root.sender) { + if (currentSession.sessionParams.credentials.userId != event.root.sender && event.root.type == EventType.MESSAGE) { //not sent by me this.add(SimpleAction(ACTION_FLAG, R.string.report_content, R.drawable.ic_flag, parcel.eventId)) } @@ -149,6 +151,11 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes } } + private fun canReact(event: TimelineEvent, messageContent: MessageContent?): Boolean { + //Only event of type Event.EVENT_TYPE_MESSAGE are supported for the moment + return event.root.type == EventType.MESSAGE + } + private fun canQuote(event: TimelineEvent, messageContent: MessageContent?): Boolean { //Only event of type Event.EVENT_TYPE_MESSAGE are supported for the moment if (event.root.type != EventType.MESSAGE) return false diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/QuickReactionViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/QuickReactionViewModel.kt index 89976248b8..5252e51bda 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/QuickReactionViewModel.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/QuickReactionViewModel.kt @@ -32,15 +32,14 @@ enum class TriggleState { } data class QuickReactionState( - val agreeTrigleState: TriggleState, - val likeTriggleState: TriggleState, + val agreeTrigleState: TriggleState = TriggleState.NONE, + val likeTriggleState: TriggleState = TriggleState.NONE, /** Pair of 'clickedOn' and current toggles state*/ val selectionResult: Pair<String, List<String>>? = null, - val eventId: String) : MvRxState + val eventId: String = "") : MvRxState /** * Quick reaction view model - * TODO: configure initial state from event */ class QuickReactionViewModel(initialState: QuickReactionState) : VectorViewModel<QuickReactionState>(initialState) { @@ -88,15 +87,15 @@ class QuickReactionViewModel(initialState: QuickReactionState) : VectorViewModel private fun getReactions(state: QuickReactionState, newState1: TriggleState?, newState2: TriggleState?): List<String> { return ArrayList<String>(4).apply { when (newState2 ?: state.likeTriggleState) { - TriggleState.FIRST -> add(likePositive) + TriggleState.FIRST -> add(likePositive) TriggleState.SECOND -> add(likeNegative) - else -> { + else -> { } } when (newState1 ?: state.agreeTrigleState) { - TriggleState.FIRST -> add(agreePositive) + TriggleState.FIRST -> add(agreePositive) TriggleState.SECOND -> add(agreeNegative) - else -> { + else -> { } } } @@ -114,9 +113,9 @@ class QuickReactionViewModel(initialState: QuickReactionState) : VectorViewModel return when (reaction) { agreePositive -> agreeNegative agreeNegative -> agreePositive - likePositive -> likeNegative - likeNegative -> likePositive - else -> null + likePositive -> likeNegative + likeNegative -> likePositive + else -> null } } diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/NoticeItem.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/NoticeItem.kt index 914cb23252..d190875f20 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/NoticeItem.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/NoticeItem.kt @@ -37,7 +37,6 @@ abstract class NoticeItem : BaseEventItem<NoticeItem.Holder>() { @EpoxyAttribute var baseCallback: TimelineEventController.BaseCallback? = null - private var longClickListener = View.OnLongClickListener { baseCallback?.onEventLongClicked(informationData, null, it) baseCallback != null diff --git a/vector/src/main/res/layout/bottom_sheet_message_actions.xml b/vector/src/main/res/layout/bottom_sheet_message_actions.xml index 0de471bdd1..d1cb8c9fb9 100644 --- a/vector/src/main/res/layout/bottom_sheet_message_actions.xml +++ b/vector/src/main/res/layout/bottom_sheet_message_actions.xml @@ -86,7 +86,8 @@ tools:text="Friday 8pm" /> </androidx.constraintlayout.widget.ConstraintLayout> - <LinearLayout + <View + android:id="@+id/quickReactTopDivider" android:layout_width="match_parent" android:layout_height="1dp" android:background="?attr/vctr_list_divider_color" /> @@ -94,18 +95,22 @@ <FrameLayout android:id="@+id/bottom_sheet_quick_reaction_container" android:layout_width="match_parent" - android:layout_height="wrap_content" /> + android:layout_height="wrap_content" + tools:background="@android:color/holo_green_light" + tools:layout_height="180dp" /> - <LinearLayout + <View + android:id="@+id/quickReactBottomDivider" android:layout_width="match_parent" android:layout_height="1dp" android:background="?attr/vctr_list_divider_color" /> - <FrameLayout android:id="@+id/bottom_sheet_menu_container" android:layout_width="match_parent" - android:layout_height="wrap_content" /> + android:layout_height="wrap_content" + tools:background="@android:color/holo_blue_dark" + tools:layout_height="250dp" /> </LinearLayout> </androidx.core.widget.NestedScrollView> From 651d0472cd408d963cca18bd189108d16ced9c32 Mon Sep 17 00:00:00 2001 From: Valere <valeref@matrix.org> Date: Fri, 7 Jun 2019 11:18:14 +0200 Subject: [PATCH 13/18] Show preview for notice events in context menu + fix merge issues --- vector/build.gradle | 2 +- .../features/home/HomeDrawerFragment.kt | 2 +- .../action/MessageActionsViewModel.kt | 42 ++++++++++++++----- .../features/navigation/DefaultNavigator.kt | 4 +- .../features/navigation/Navigator.kt | 2 +- 5 files changed, 36 insertions(+), 16 deletions(-) diff --git a/vector/build.gradle b/vector/build.gradle index 263df78e27..f411f064e9 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -85,7 +85,7 @@ android { resValue "bool", "debug_mode", "true" buildConfigField "boolean", "LOW_PRIVACY_LOG_ENABLE", "false" buildConfigField "boolean", "SHOW_HIDDEN_TIMELINE_EVENTS", "false" - + signingConfig signingConfigs.debug } diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/HomeDrawerFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/home/HomeDrawerFragment.kt index 97baa539b9..6619588157 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/HomeDrawerFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/HomeDrawerFragment.kt @@ -59,7 +59,7 @@ class HomeDrawerFragment : VectorBaseFragment() { // Debug menu homeDrawerHeaderDebugView.setOnClickListener { - navigator.openDebug() + navigator.openDebug(requireActivity()) } } } \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index 2a925894b3..1c29314035 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -15,6 +15,7 @@ */ package im.vector.riotredesign.features.home.room.detail.timeline.action +import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.ViewModelContext @@ -25,8 +26,10 @@ import im.vector.matrix.android.api.session.room.model.message.MessageContent import im.vector.matrix.android.api.session.room.model.message.MessageTextContent import im.vector.matrix.android.api.session.room.model.message.MessageType import im.vector.riotredesign.core.platform.VectorViewModel +import im.vector.riotredesign.features.home.room.detail.timeline.format.NoticeEventFormatter import org.commonmark.parser.Parser import org.koin.android.ext.android.get +import org.koin.core.parameter.parametersOf import ru.noties.markwon.Markwon import ru.noties.markwon.html.HtmlPlugin import timber.log.Timber @@ -53,28 +56,45 @@ class MessageActionsViewModel(initialState: MessageActionState) : VectorViewMode override fun initialState(viewModelContext: ViewModelContext): MessageActionState? { val currentSession = viewModelContext.activity.get<Session>() + val fragment = (viewModelContext as? FragmentViewModelContext)?.fragment + val noticeFormatter = fragment?.get<NoticeEventFormatter>(parameters = { parametersOf(fragment) }) val parcel = viewModelContext.args as TimelineEventFragmentArgs val dateFormat = SimpleDateFormat("EEE, d MMM yyyy HH:mm", Locale.getDefault()) val event = currentSession.getRoom(parcel.roomId)?.getTimeLineEvent(parcel.eventId) + var body: CharSequence? = null + val originTs = event?.root?.originServerTs return if (event != null) { - val messageContent: MessageContent? = event.annotations?.editSummary?.aggregatedContent?.toModel() - ?: event.root.content.toModel() - val originTs = event.root.originServerTs - var body: CharSequence? = messageContent?.body - if (messageContent is MessageTextContent && messageContent.format == MessageType.FORMAT_MATRIX_HTML) { - val parser = Parser.builder().build() - val document = parser.parse(messageContent.formattedBody ?: messageContent.body) - body = Markwon.builder(viewModelContext.activity) - .usePlugin(HtmlPlugin.create()).build().render(document) + when (event.root.type) { + EventType.MESSAGE -> { + val messageContent: MessageContent? = event.annotations?.editSummary?.aggregatedContent?.toModel() + ?: event.root.content.toModel() + body = messageContent?.body + if (messageContent is MessageTextContent && messageContent.format == MessageType.FORMAT_MATRIX_HTML) { + val parser = Parser.builder().build() + val document = parser.parse(messageContent.formattedBody + ?: messageContent.body) + body = Markwon.builder(viewModelContext.activity) + .usePlugin(HtmlPlugin.create()).build().render(document) + } + } + EventType.STATE_ROOM_NAME, + EventType.STATE_ROOM_TOPIC, + EventType.STATE_ROOM_MEMBER, + EventType.STATE_HISTORY_VISIBILITY, + EventType.CALL_INVITE, + EventType.CALL_HANGUP, + EventType.CALL_ANSWER -> { + body = noticeFormatter?.format(event) + } } MessageActionState( userId = event.root.sender ?: "", - senderName = parcel.informationData.memberName.toString(), + senderName = parcel.informationData.memberName?.toString() ?: "", messageBody = body, ts = dateFormat.format(Date(originTs ?: 0)), - showPreview = event.root.type == EventType.MESSAGE, + showPreview = body != null, canReact = event.root.type == EventType.MESSAGE, senderAvatarPath = currentSession.contentUrlResolver().resolveFullSize(parcel.informationData.avatarUrl) ) diff --git a/vector/src/main/java/im/vector/riotredesign/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/riotredesign/features/navigation/DefaultNavigator.kt index 00207950ba..44c1cfe366 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/navigation/DefaultNavigator.kt @@ -52,7 +52,7 @@ class DefaultNavigator : Navigator { context.startActivity(intent) } - override fun openDebug() { - activity.startActivity(Intent(activity, DebugMenuActivity::class.java)) + override fun openDebug(context: Context) { + context.startActivity(Intent(context, DebugMenuActivity::class.java)) } } \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/riotredesign/features/navigation/Navigator.kt index 0229596474..8873227b00 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/navigation/Navigator.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/navigation/Navigator.kt @@ -29,6 +29,6 @@ interface Navigator { fun openSettings(context: Context) - fun openDebug() + fun openDebug(context: Context) } \ No newline at end of file From 438404b5ba7f0ac3a418d10e4536e8560a0be6d0 Mon Sep 17 00:00:00 2001 From: Valere <valeref@matrix.org> Date: Fri, 7 Jun 2019 13:55:32 +0200 Subject: [PATCH 14/18] code review cleaning --- .../home/room/detail/RoomDetailViewModel.kt | 8 ++++++-- .../timeline/action/MessageActionsBottomSheet.kt | 13 +------------ .../timeline/action/MessageActionsViewModel.kt | 2 +- .../features/reactions/widget/ReactionButton.kt | 4 ++-- 4 files changed, 10 insertions(+), 17 deletions(-) diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt index 346d21c35f..476ff99b86 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt @@ -55,8 +55,12 @@ class RoomDetailViewModel(initialState: RoomDetailViewState, private val roomId = initialState.roomId private val eventId = initialState.eventId private val displayedEventsObservable = BehaviorRelay.create<RoomDetailActions.EventDisplayed>() - private val timeline = room.createTimeline(eventId, - if (TimelineDisplayableEvents.DEBUG_HIDDEN_EVENT) TimelineDisplayableEvents.DEBUG_DISPLAYABLE_TYPES else TimelineDisplayableEvents.DISPLAYABLE_TYPES) + private val allowedTypes = if (TimelineDisplayableEvents.DEBUG_HIDDEN_EVENT) { + TimelineDisplayableEvents.DEBUG_DISPLAYABLE_TYPES + } else { + TimelineDisplayableEvents.DISPLAYABLE_TYPES + } + private val timeline = room.createTimeline(eventId, allowedTypes) companion object : MvRxViewModelFactory<RoomDetailViewModel, RoomDetailViewState> { diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt index 7ea6a9b1b8..9c9bbc2db3 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt @@ -33,7 +33,6 @@ import com.airbnb.mvrx.withState import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import im.vector.riotredesign.R -import im.vector.riotredesign.core.glide.GlideApp import im.vector.riotredesign.features.home.AvatarRenderer import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData import kotlinx.android.synthetic.main.bottom_sheet_message_actions.* @@ -122,17 +121,7 @@ class MessageActionsBottomSheet : BaseMvRxBottomSheetDialog() { senderNameTextView.text = it.senderName messageBodyTextView.text = it.messageBody messageTimestampText.text = it.ts - - GlideApp.with(this).clear(senderAvatarImageView) - if (it.senderAvatarPath != null) { - GlideApp.with(this) - .load(it.senderAvatarPath) - .circleCrop() - .placeholder(AvatarRenderer.getPlaceholderDrawable(requireContext(), it.userId, it.senderName)) - .into(senderAvatarImageView) - } else { - senderAvatarImageView.setImageDrawable(AvatarRenderer.getPlaceholderDrawable(requireContext(), it.userId, it.senderName)) - } + AvatarRenderer.render(it.senderAvatarPath, it.userId, it.senderName, senderAvatarImageView) } else { bottom_sheet_message_preview.isVisible = false } diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index 1c29314035..d0ddb854c2 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -96,7 +96,7 @@ class MessageActionsViewModel(initialState: MessageActionState) : VectorViewMode ts = dateFormat.format(Date(originTs ?: 0)), showPreview = body != null, canReact = event.root.type == EventType.MESSAGE, - senderAvatarPath = currentSession.contentUrlResolver().resolveFullSize(parcel.informationData.avatarUrl) + senderAvatarPath = parcel.informationData.avatarUrl ) } else { //can this happen? diff --git a/vector/src/main/java/im/vector/riotredesign/features/reactions/widget/ReactionButton.kt b/vector/src/main/java/im/vector/riotredesign/features/reactions/widget/ReactionButton.kt index c0716cf28e..8250b66854 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/reactions/widget/ReactionButton.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/reactions/widget/ReactionButton.kt @@ -25,7 +25,6 @@ import android.graphics.Typeface import android.graphics.drawable.Drawable import android.util.AttributeSet import android.view.LayoutInflater -import android.view.MotionEvent import android.view.View import android.view.animation.AccelerateDecelerateInterpolator import android.view.animation.DecelerateInterpolator @@ -44,7 +43,8 @@ import im.vector.riotredesign.core.utils.TextUtils * Displays a String reaction (emoji), with a count, and that can be selected or not (toggle) */ class ReactionButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, - defStyleAttr: Int = 0) : FrameLayout(context, attrs, defStyleAttr), View.OnClickListener, View.OnLongClickListener { + defStyleAttr: Int = 0) + : FrameLayout(context, attrs, defStyleAttr), View.OnClickListener, View.OnLongClickListener { companion object { private val DECCELERATE_INTERPOLATOR = DecelerateInterpolator() From 5f34e58bd3c046db66138692a2c18067f99c6b90 Mon Sep 17 00:00:00 2001 From: Valere <valeref@matrix.org> Date: Fri, 7 Jun 2019 14:29:42 +0200 Subject: [PATCH 15/18] Fix / style on emoji picker appbar layout --- .../res/layout/activity_emoji_reaction_picker.xml | 12 +++++------- vector/src/main/res/values/styles_riot.xml | 4 ++++ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/vector/src/main/res/layout/activity_emoji_reaction_picker.xml b/vector/src/main/res/layout/activity_emoji_reaction_picker.xml index 38694933b3..6611f2667e 100644 --- a/vector/src/main/res/layout/activity_emoji_reaction_picker.xml +++ b/vector/src/main/res/layout/activity_emoji_reaction_picker.xml @@ -15,25 +15,23 @@ tools:layout="@layout/emoji_chooser_fragment" /> <com.google.android.material.appbar.AppBarLayout + style="@style/VectorAppBarLayoutStyle" android:layout_width="match_parent" - android:layout_height="wrap_content"> + android:layout_height="wrap_content" + android:elevation="4dp"> <androidx.appcompat.widget.Toolbar android:id="@+id/emojiPickerToolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" - android:background="?attr/colorPrimary" - android:elevation="4dp" android:minHeight="0dp" - android:theme="@style/ThemeOverlay.AppCompat.ActionBar" + tools:title="@string/reactions" app:layout_scrollFlags="scroll|exitUntilCollapsed|snap|enterAlways" /> <com.google.android.material.tabs.TabLayout android:id="@+id/tabs" android:layout_width="match_parent" - android:layout_height="40dp" - android:background="?attr/colorPrimary" - android:elevation="4dp" /> + android:layout_height="40dp" /> </com.google.android.material.appbar.AppBarLayout> diff --git a/vector/src/main/res/values/styles_riot.xml b/vector/src/main/res/values/styles_riot.xml index fb536a604e..1cd18d5ee7 100644 --- a/vector/src/main/res/values/styles_riot.xml +++ b/vector/src/main/res/values/styles_riot.xml @@ -26,6 +26,10 @@ <item name="android:fontFamily">"sans-serif"</item> </style> + <style name="VectorAppBarLayoutStyle" parent="Widget.Design.AppBarLayout"> + <item name="android:background">?riotx_background</item> + </style> + <!-- Alert Dialog: Button color are not colorAccent by default --> <style name="VectorAlertDialogStyleLight" parent="Theme.MaterialComponents.Light.Dialog.Alert"> <item name="buttonBarButtonStyle">@style/VectorAlertDialogButtonStyle</item> From c9240c2dce5c3d7781eea7d4e0e8520ee355675c Mon Sep 17 00:00:00 2001 From: Valere <valeref@matrix.org> Date: Fri, 7 Jun 2019 15:49:41 +0200 Subject: [PATCH 16/18] Fix / disable context menu on not sent messages --- .../detail/timeline/action/MessageActionsViewModel.kt | 2 +- .../room/detail/timeline/action/MessageMenuViewModel.kt | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index d0ddb854c2..998ddf8195 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -95,7 +95,7 @@ class MessageActionsViewModel(initialState: MessageActionState) : VectorViewMode messageBody = body, ts = dateFormat.format(Date(originTs ?: 0)), showPreview = body != null, - canReact = event.root.type == EventType.MESSAGE, + canReact = event.root.type == EventType.MESSAGE && event.sendState.isSent(), senderAvatarPath = parcel.informationData.avatarUrl ) } else { diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt index d2511eff7e..64269ad98c 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt @@ -54,13 +54,14 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes ?: event.root.content.toModel() val type = messageContent?.type - if (event.sendState == SendState.UNSENT) { + if (!event.sendState.isSent()) { //Resend and Delete return MessageMenuState( + //TODO listOf( - SimpleAction(ACTION_RESEND, R.string.resend, R.drawable.ic_send, event.root.eventId), - //TODO delete icon - SimpleAction(ACTION_DELETE, R.string.delete, R.drawable.ic_delete, event.root.eventId) +// SimpleAction(ACTION_RESEND, R.string.resend, R.drawable.ic_send, event.root.eventId), +// //TODO delete icon +// SimpleAction(ACTION_DELETE, R.string.delete, R.drawable.ic_delete, event.root.eventId) ) ) } From 10251b906a24dde4c42902cf83e2e7b36f2e6ce8 Mon Sep 17 00:00:00 2001 From: Valere <valeref@matrix.org> Date: Fri, 7 Jun 2019 15:57:55 +0200 Subject: [PATCH 17/18] clean / format --- vector/src/main/res/layout/activity_emoji_reaction_picker.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/layout/activity_emoji_reaction_picker.xml b/vector/src/main/res/layout/activity_emoji_reaction_picker.xml index 6611f2667e..475ff54c3a 100644 --- a/vector/src/main/res/layout/activity_emoji_reaction_picker.xml +++ b/vector/src/main/res/layout/activity_emoji_reaction_picker.xml @@ -25,8 +25,8 @@ android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:minHeight="0dp" - tools:title="@string/reactions" - app:layout_scrollFlags="scroll|exitUntilCollapsed|snap|enterAlways" /> + app:layout_scrollFlags="scroll|exitUntilCollapsed|snap|enterAlways" + tools:title="@string/reactions" /> <com.google.android.material.tabs.TabLayout android:id="@+id/tabs" From a4a813708cb2cd87d63940ba0cca82c41d3571a4 Mon Sep 17 00:00:00 2001 From: Valere <valeref@matrix.org> Date: Fri, 7 Jun 2019 16:39:45 +0200 Subject: [PATCH 18/18] Fix / send state always returning Unknown --- .../android/internal/database/model/EventEntity.kt | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventEntity.kt index 38f4f4518c..5b3e869058 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventEntity.kt @@ -19,12 +19,10 @@ package im.vector.matrix.android.internal.database.model import im.vector.matrix.android.api.session.room.send.SendState import io.realm.RealmObject import io.realm.RealmResults -import io.realm.annotations.Ignore import io.realm.annotations.Index import io.realm.annotations.LinkingObjects import io.realm.annotations.PrimaryKey import java.util.* -import kotlin.properties.Delegates internal open class EventEntity(@PrimaryKey var localId: String = UUID.randomUUID().toString(), @Index var eventId: String = "", @@ -51,10 +49,14 @@ internal open class EventEntity(@PrimaryKey var localId: String = UUID.randomUUI private var sendStateStr: String = SendState.UNKNOWN.name - @delegate:Ignore - var sendState: SendState by Delegates.observable(SendState.valueOf(sendStateStr)) { _, _, newValue -> - sendStateStr = newValue.name - } + var sendState: SendState + get() { + return SendState.valueOf(sendStateStr) + } + set(value) { + sendStateStr = value.name + } + companion object