diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt index 7c6ca39b..e173c0f2 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt @@ -20,6 +20,7 @@ import com.squareup.moshi.Json import com.squareup.moshi.JsonClass import com.squareup.moshi.Types import im.vector.matrix.android.internal.di.MoshiProvider +import timber.log.Timber import java.lang.reflect.ParameterizedType typealias Content = Map @@ -27,11 +28,20 @@ typealias Content = Map /** * This methods is a facility method to map a json content to a model. */ -inline fun Content?.toModel(): T? { +inline fun Content?.toModel(catchError: Boolean = true): T? { return this?.let { val moshi = MoshiProvider.providesMoshi() val moshiAdapter = moshi.adapter(T::class.java) - return moshiAdapter.fromJsonValue(it) + return try { + moshiAdapter.fromJsonValue(it) + } catch (e: Exception) { + if (catchError) { + Timber.e(e, "To model failed : $e") + null + } else { + throw e + } + } } } 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 ca17d855..c5779816 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 @@ -25,11 +25,16 @@ import im.vector.riotredesign.features.autocomplete.user.AutocompleteUserControl import im.vector.riotredesign.features.autocomplete.user.AutocompleteUserPresenter import im.vector.riotredesign.features.home.group.GroupSummaryController import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController -import im.vector.riotredesign.features.home.room.detail.timeline.factory.* +import im.vector.riotredesign.features.home.room.detail.timeline.factory.DefaultItemFactory +import im.vector.riotredesign.features.home.room.detail.timeline.factory.MessageItemFactory +import im.vector.riotredesign.features.home.room.detail.timeline.factory.NoticeItemFactory +import im.vector.riotredesign.features.home.room.detail.timeline.factory.TimelineItemFactory +import im.vector.riotredesign.features.home.room.detail.timeline.format.NoticeEventFormatter import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDateFormatter import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider import im.vector.riotredesign.features.home.room.list.RoomSummaryController import im.vector.riotredesign.features.html.EventHtmlRenderer +import org.koin.core.parameter.parametersOf import org.koin.dsl.module.module class HomeModule { @@ -37,8 +42,6 @@ class HomeModule { companion object { const val HOME_SCOPE = "HOME_SCOPE" const val ROOM_DETAIL_SCOPE = "ROOM_DETAIL_SCOPE" - const val ROOM_LIST_SCOPE = "ROOM_LIST_SCOPE" - const val GROUP_LIST_SCOPE = "GROUP_LIST_SCOPE" } val definition = module { @@ -59,26 +62,26 @@ class HomeModule { TimelineDateFormatter(get()) } + factory { + NoticeEventFormatter(get()) + } + factory { (fragment: Fragment) -> val eventHtmlRenderer = EventHtmlRenderer(GlideApp.with(fragment), fragment.requireContext(), get()) + val noticeEventFormatter = get(parameters = { parametersOf(fragment) }) val timelineMediaSizeProvider = TimelineMediaSizeProvider() - val colorProvider = ColorProvider(fragment.requireContext()) + val colorProvider = get() val timelineDateFormatter = get() - val messageItemFactory = MessageItemFactory(colorProvider, timelineMediaSizeProvider, timelineDateFormatter, eventHtmlRenderer) - val timelineItemFactory = TimelineItemFactory(messageItemFactory = messageItemFactory, - roomNameItemFactory = RoomNameItemFactory(get()), - roomTopicItemFactory = RoomTopicItemFactory(get()), - roomMemberItemFactory = RoomMemberItemFactory(get()), - roomHistoryVisibilityItemFactory = RoomHistoryVisibilityItemFactory(get()), - callItemFactory = CallItemFactory(get()), - defaultItemFactory = DefaultItemFactory() + val timelineItemFactory = TimelineItemFactory(messageItemFactory = MessageItemFactory(colorProvider, timelineMediaSizeProvider, timelineDateFormatter, eventHtmlRenderer), + noticeItemFactory = NoticeItemFactory(noticeEventFormatter), + defaultItemFactory = DefaultItemFactory() ) TimelineEventController(timelineDateFormatter, timelineItemFactory, timelineMediaSizeProvider) } factory { - RoomSummaryController(get(), get()) + RoomSummaryController(get(), get(), get()) } factory { diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/group/GroupListFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/home/group/GroupListFragment.kt index 884b7a61..8970935e 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/group/GroupListFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/group/GroupListFragment.kt @@ -48,7 +48,6 @@ class GroupListFragment : VectorBaseFragment(), GroupSummaryController.Callback override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) - bindScope(getOrCreateScope(HomeModule.GROUP_LIST_SCOPE)) groupController.callback = this stateView.contentView = epoxyRecyclerView epoxyRecyclerView.setController(groupController) diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/CallItemFactory.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/CallItemFactory.kt deleted file mode 100644 index 42f6ba0e..00000000 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/CallItemFactory.kt +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.riotredesign.features.home.room.detail.timeline.factory - -import im.vector.matrix.android.api.session.events.model.Event -import im.vector.matrix.android.api.session.events.model.EventType -import im.vector.matrix.android.api.session.events.model.toModel -import im.vector.matrix.android.api.session.room.model.RoomMember -import im.vector.matrix.android.api.session.room.model.call.CallInviteContent -import im.vector.matrix.android.api.session.room.timeline.TimelineEvent -import im.vector.riotredesign.R -import im.vector.riotredesign.core.resources.StringProvider -import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem -import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem_ - -class CallItemFactory(private val stringProvider: StringProvider) { - - fun create(event: TimelineEvent): NoticeItem? { - val text = buildNoticeText(event.root, event.senderName) ?: return null - return NoticeItem_() - .noticeText(text) - .avatarUrl(event.senderAvatar) - .memberName(event.senderName) - } - - private fun buildNoticeText(event: Event, senderName: String?): CharSequence? { - return when { - EventType.CALL_INVITE == event.type -> { - val content = event.content.toModel() ?: return null - val isVideoCall = content.offer.sdp == CallInviteContent.Offer.SDP_VIDEO - return if (isVideoCall) { - stringProvider.getString(R.string.notice_placed_video_call, senderName) - } else { - stringProvider.getString(R.string.notice_placed_voice_call, senderName) - } - } - EventType.CALL_ANSWER == event.type -> stringProvider.getString(R.string.notice_answered_call, senderName) - EventType.CALL_HANGUP == event.type -> stringProvider.getString(R.string.notice_ended_call, senderName) - else -> null - } - - } - - -} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/RoomTopicItemFactory.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/NoticeItemFactory.kt similarity index 55% rename from vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/RoomTopicItemFactory.kt rename to vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/NoticeItemFactory.kt index 34e55897..fc756c9c 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/RoomTopicItemFactory.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/NoticeItemFactory.kt @@ -16,28 +16,24 @@ package im.vector.riotredesign.features.home.room.detail.timeline.factory -import im.vector.matrix.android.api.session.events.model.toModel -import im.vector.matrix.android.api.session.room.model.RoomTopicContent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent -import im.vector.riotredesign.R -import im.vector.riotredesign.core.resources.StringProvider +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.NoticeItem import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem_ -class RoomTopicItemFactory(private val stringProvider: StringProvider) { +class NoticeItemFactory(private val eventFormatter: NoticeEventFormatter) { fun create(event: TimelineEvent): NoticeItem? { + val formattedText = eventFormatter.format(event) ?: return null + val senderName = event.senderName() + val senderAvatar = event.senderAvatar() - val content: RoomTopicContent = event.root.content.toModel() ?: return null - val text = if (content.topic.isNullOrEmpty()) { - stringProvider.getString(R.string.notice_room_topic_removed, event.senderName) - } else { - stringProvider.getString(R.string.notice_room_topic_changed, event.senderName, content.topic) - } return NoticeItem_() - .noticeText(text) - .avatarUrl(event.senderAvatar) - .memberName(event.senderName) + .noticeText(formattedText) + .avatarUrl(senderAvatar) + .memberName(senderName) } diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/RoomHistoryVisibilityItemFactory.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/RoomHistoryVisibilityItemFactory.kt deleted file mode 100644 index 3a3a91f1..00000000 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/RoomHistoryVisibilityItemFactory.kt +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.riotredesign.features.home.room.detail.timeline.factory - -import im.vector.matrix.android.api.session.events.model.Event -import im.vector.matrix.android.api.session.events.model.toModel -import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibility -import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibilityContent -import im.vector.matrix.android.api.session.room.model.RoomMember -import im.vector.matrix.android.api.session.room.timeline.TimelineEvent -import im.vector.riotredesign.R -import im.vector.riotredesign.core.resources.StringProvider -import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem -import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem_ - - -class RoomHistoryVisibilityItemFactory(private val stringProvider: StringProvider) { - - fun create(event: TimelineEvent): NoticeItem? { - val noticeText = buildNoticeText(event.root, event.senderName) ?: return null - return NoticeItem_() - .noticeText(noticeText) - .avatarUrl(event.senderAvatar) - .memberName(event.senderName) - } - - private fun buildNoticeText(event: Event, senderName: String?): CharSequence? { - val content = event.content.toModel() ?: return null - val formattedVisibility = when (content.historyVisibility) { - RoomHistoryVisibility.SHARED -> stringProvider.getString(R.string.notice_room_visibility_shared) - RoomHistoryVisibility.INVITED -> stringProvider.getString(R.string.notice_room_visibility_invited) - RoomHistoryVisibility.JOINED -> stringProvider.getString(R.string.notice_room_visibility_joined) - RoomHistoryVisibility.WORLD_READABLE -> stringProvider.getString(R.string.notice_room_visibility_world_readable) - } - return stringProvider.getString(R.string.notice_made_future_room_visibility, senderName, formattedVisibility) - } - - -} - - diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/RoomMemberItemFactory.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/RoomMemberItemFactory.kt deleted file mode 100644 index 77e5920c..00000000 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/RoomMemberItemFactory.kt +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.riotredesign.features.home.room.detail.timeline.factory - -import android.text.TextUtils -import im.vector.matrix.android.api.session.events.model.toModel -import im.vector.matrix.android.api.session.room.model.Membership -import im.vector.matrix.android.api.session.room.model.RoomMember -import im.vector.matrix.android.api.session.room.timeline.TimelineEvent -import im.vector.riotredesign.R -import im.vector.riotredesign.core.resources.StringProvider -import im.vector.riotredesign.features.home.room.detail.timeline.helper.RoomMemberEventHelper -import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem -import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem_ - - -//TODO : complete with call membership eventsĀ¬ -class RoomMemberItemFactory(private val stringProvider: StringProvider) { - - fun create(event: TimelineEvent): NoticeItem? { - val eventContent: RoomMember? = event.root.content.toModel() - val prevEventContent: RoomMember? = event.root.prevContent.toModel() - val noticeText = buildRoomMemberNotice(event, eventContent, prevEventContent) ?: return null - val senderAvatar = RoomMemberEventHelper.senderAvatar(eventContent, prevEventContent, event) - val senderName = RoomMemberEventHelper.senderName(eventContent, prevEventContent, event) - - return NoticeItem_() - .userId(event.root.sender ?: "") - .noticeText(noticeText) - .avatarUrl(senderAvatar) - .memberName(senderName) - } - - private fun buildRoomMemberNotice(event: TimelineEvent, eventContent: RoomMember?, prevEventContent: RoomMember?): String? { - val isMembershipEvent = prevEventContent?.membership != eventContent?.membership - return if (isMembershipEvent) { - buildMembershipNotice(event, eventContent, prevEventContent) - } else { - buildProfileNotice(event, eventContent, prevEventContent) - } - } - - private fun buildProfileNotice(event: TimelineEvent, eventContent: RoomMember?, prevEventContent: RoomMember?): String? { - val displayText = StringBuilder() - // Check display name has been changed - if (!TextUtils.equals(eventContent?.displayName, prevEventContent?.displayName)) { - val displayNameText = when { - prevEventContent?.displayName.isNullOrEmpty() -> - stringProvider.getString(R.string.notice_display_name_set, event.root.sender, eventContent?.displayName) - eventContent?.displayName.isNullOrEmpty() -> - stringProvider.getString(R.string.notice_display_name_removed, event.root.sender, prevEventContent?.displayName) - else -> - stringProvider.getString(R.string.notice_display_name_changed_from, - event.root.sender, prevEventContent?.displayName, eventContent?.displayName) - } - displayText.append(displayNameText) - } - // Check whether the avatar has been changed - if (!TextUtils.equals(eventContent?.avatarUrl, prevEventContent?.avatarUrl)) { - val displayAvatarText = if (displayText.isNotEmpty()) { - displayText.append(" ") - stringProvider.getString(R.string.notice_avatar_changed_too) - } else { - stringProvider.getString(R.string.notice_avatar_url_changed, event.senderName) - } - displayText.append(displayAvatarText) - } - return displayText.toString() - } - - private fun buildMembershipNotice(event: TimelineEvent, eventContent: RoomMember?, prevEventContent: RoomMember?): String? { - val senderDisplayName = event.senderName ?: event.root.sender - val targetDisplayName = eventContent?.displayName ?: event.root.sender - return when { - Membership.INVITE == eventContent?.membership -> { - // TODO get userId - val selfUserId = "" - when { - eventContent.thirdPartyInvite != null -> - stringProvider.getString(R.string.notice_room_third_party_registered_invite, - targetDisplayName, eventContent.thirdPartyInvite?.displayName) - TextUtils.equals(event.root.stateKey, selfUserId) -> - stringProvider.getString(R.string.notice_room_invite_you, senderDisplayName) - event.root.stateKey.isNullOrEmpty() -> - stringProvider.getString(R.string.notice_room_invite_no_invitee, senderDisplayName) - else -> - stringProvider.getString(R.string.notice_room_invite, senderDisplayName, targetDisplayName) - } - } - Membership.JOIN == eventContent?.membership -> - stringProvider.getString(R.string.notice_room_join, senderDisplayName) - Membership.LEAVE == eventContent?.membership -> - // 2 cases here: this member may have left voluntarily or they may have been "left" by someone else ie. kicked - return if (TextUtils.equals(event.root.sender, event.root.stateKey)) { - if (prevEventContent?.membership == Membership.INVITE) { - stringProvider.getString(R.string.notice_room_reject, senderDisplayName) - } else { - val leftDisplayName = RoomMemberEventHelper.senderName(eventContent, prevEventContent, event) - stringProvider.getString(R.string.notice_room_leave, leftDisplayName) - } - } else if (prevEventContent?.membership == Membership.INVITE) { - stringProvider.getString(R.string.notice_room_withdraw, senderDisplayName, targetDisplayName) - } else if (prevEventContent?.membership == Membership.JOIN) { - stringProvider.getString(R.string.notice_room_kick, senderDisplayName, targetDisplayName) - } else if (prevEventContent?.membership == Membership.BAN) { - stringProvider.getString(R.string.notice_room_unban, senderDisplayName, targetDisplayName) - } else { - null - } - Membership.BAN == eventContent?.membership -> - stringProvider.getString(R.string.notice_room_ban, senderDisplayName, targetDisplayName) - Membership.KNOCK == eventContent?.membership -> - stringProvider.getString(R.string.notice_room_kick, senderDisplayName, targetDisplayName) - else -> null - } - } - - -} - - diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/RoomNameItemFactory.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/RoomNameItemFactory.kt deleted file mode 100644 index be33c44e..00000000 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/RoomNameItemFactory.kt +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.riotredesign.features.home.room.detail.timeline.factory - -import android.text.TextUtils -import im.vector.matrix.android.api.session.events.model.toModel -import im.vector.matrix.android.api.session.room.model.RoomNameContent -import im.vector.matrix.android.api.session.room.timeline.TimelineEvent -import im.vector.riotredesign.R -import im.vector.riotredesign.core.resources.StringProvider -import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem -import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem_ - -class RoomNameItemFactory(private val stringProvider: StringProvider) { - - fun create(event: TimelineEvent): NoticeItem? { - - val content: RoomNameContent = event.root.content.toModel() ?: return null - val text = if (!TextUtils.isEmpty(content.name)) { - stringProvider.getString(R.string.notice_room_name_changed, event.senderName, content.name) - } else { - stringProvider.getString(R.string.notice_room_name_removed, event.senderName) - } - return NoticeItem_() - .noticeText(text) - .avatarUrl(event.senderAvatar) - .memberName(event.senderName) - } - - -} \ No newline at end of file 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 d5354434..db5e3e54 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 @@ -23,11 +23,7 @@ import im.vector.riotredesign.core.epoxy.VectorEpoxyModel import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController class TimelineItemFactory(private val messageItemFactory: MessageItemFactory, - private val roomNameItemFactory: RoomNameItemFactory, - private val roomTopicItemFactory: RoomTopicItemFactory, - private val roomMemberItemFactory: RoomMemberItemFactory, - private val roomHistoryVisibilityItemFactory: RoomHistoryVisibilityItemFactory, - private val callItemFactory: CallItemFactory, + private val noticeItemFactory: NoticeItemFactory, private val defaultItemFactory: DefaultItemFactory) { fun create(event: TimelineEvent, @@ -36,23 +32,22 @@ class TimelineItemFactory(private val messageItemFactory: MessageItemFactory, val computedModel = try { when (event.root.type) { - EventType.MESSAGE -> messageItemFactory.create(event, nextEvent, callback) - EventType.STATE_ROOM_NAME -> roomNameItemFactory.create(event) - EventType.STATE_ROOM_TOPIC -> roomTopicItemFactory.create(event) - EventType.STATE_ROOM_MEMBER -> roomMemberItemFactory.create(event) - EventType.STATE_HISTORY_VISIBILITY -> roomHistoryVisibilityItemFactory.create(event) + EventType.MESSAGE -> messageItemFactory.create(event, nextEvent, callback) + 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 -> callItemFactory.create(event) + EventType.CALL_ANSWER -> noticeItemFactory.create(event) EventType.ENCRYPTED, EventType.ENCRYPTION, EventType.STATE_ROOM_THIRD_PARTY_INVITE, EventType.STICKER, - EventType.STATE_ROOM_CREATE -> defaultItemFactory.create(event) - - else -> null + EventType.STATE_ROOM_CREATE -> defaultItemFactory.create(event) + else -> null } } catch (e: Exception) { defaultItemFactory.create(event, e) diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/format/NoticeEventFormatter.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/format/NoticeEventFormatter.kt new file mode 100644 index 00000000..ce4598f6 --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/format/NoticeEventFormatter.kt @@ -0,0 +1,185 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotredesign.features.home.room.detail.timeline.format + +import android.text.TextUtils +import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.android.api.session.events.model.EventType +import im.vector.matrix.android.api.session.events.model.toModel +import im.vector.matrix.android.api.session.room.model.Membership +import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibility +import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibilityContent +import im.vector.matrix.android.api.session.room.model.RoomMember +import im.vector.matrix.android.api.session.room.model.RoomNameContent +import im.vector.matrix.android.api.session.room.model.RoomTopicContent +import im.vector.matrix.android.api.session.room.model.call.CallInviteContent +import im.vector.matrix.android.api.session.room.timeline.TimelineEvent +import im.vector.riotredesign.R +import im.vector.riotredesign.core.resources.StringProvider +import im.vector.riotredesign.features.home.room.detail.timeline.helper.senderName +import timber.log.Timber + +class NoticeEventFormatter(private val stringProvider: StringProvider) { + + fun format(timelineEvent: TimelineEvent): CharSequence? { + return when (timelineEvent.root.type) { + EventType.STATE_ROOM_NAME -> formatRoomNameEvent(timelineEvent.root, timelineEvent.senderName) + EventType.STATE_ROOM_TOPIC -> formatRoomTopicEvent(timelineEvent.root, timelineEvent.senderName) + EventType.STATE_ROOM_MEMBER -> formatRoomMemberEvent(timelineEvent.root, timelineEvent.senderName()) + EventType.STATE_HISTORY_VISIBILITY -> formatRoomHistoryVisibilityEvent(timelineEvent.root, timelineEvent.senderName) + EventType.CALL_INVITE, + EventType.CALL_HANGUP, + EventType.CALL_ANSWER -> formatCallEvent(timelineEvent.root, timelineEvent.senderName) + else -> { + Timber.v("Type ${timelineEvent.root.type} not handled by this formatter") + null + } + } + } + + private fun formatRoomNameEvent(event: Event, senderName: String?): CharSequence? { + val content = event.content.toModel() ?: return null + return if (!TextUtils.isEmpty(content.name)) { + stringProvider.getString(R.string.notice_room_name_changed, senderName, content.name) + } else { + stringProvider.getString(R.string.notice_room_name_removed, senderName) + } + } + + private fun formatRoomTopicEvent(event: Event, senderName: String?): CharSequence? { + val content = event.content.toModel() ?: return null + return if (content.topic.isNullOrEmpty()) { + stringProvider.getString(R.string.notice_room_topic_removed, senderName) + } else { + stringProvider.getString(R.string.notice_room_topic_changed, senderName, content.topic) + } + } + + private fun formatRoomHistoryVisibilityEvent(event: Event, senderName: String?): CharSequence? { + val content = event.content.toModel() ?: return null + val formattedVisibility = when (content.historyVisibility) { + RoomHistoryVisibility.SHARED -> stringProvider.getString(R.string.notice_room_visibility_shared) + RoomHistoryVisibility.INVITED -> stringProvider.getString(R.string.notice_room_visibility_invited) + RoomHistoryVisibility.JOINED -> stringProvider.getString(R.string.notice_room_visibility_joined) + RoomHistoryVisibility.WORLD_READABLE -> stringProvider.getString(R.string.notice_room_visibility_world_readable) + } + return stringProvider.getString(R.string.notice_made_future_room_visibility, senderName, formattedVisibility) + } + + private fun formatCallEvent(event: Event, senderName: String?): CharSequence? { + return when { + EventType.CALL_INVITE == event.type -> { + val content = event.content.toModel() ?: return null + val isVideoCall = content.offer.sdp == CallInviteContent.Offer.SDP_VIDEO + return if (isVideoCall) { + stringProvider.getString(R.string.notice_placed_video_call, senderName) + } else { + stringProvider.getString(R.string.notice_placed_voice_call, senderName) + } + } + EventType.CALL_ANSWER == event.type -> stringProvider.getString(R.string.notice_answered_call, senderName) + EventType.CALL_HANGUP == event.type -> stringProvider.getString(R.string.notice_ended_call, senderName) + else -> null + } + } + + private fun formatRoomMemberEvent(event: Event, senderName: String?): String? { + val eventContent: RoomMember? = event.content.toModel() + val prevEventContent: RoomMember? = event.prevContent.toModel() + val isMembershipEvent = prevEventContent?.membership != eventContent?.membership + return if (isMembershipEvent) { + buildMembershipNotice(event, senderName, eventContent, prevEventContent) + } else { + buildProfileNotice(event, senderName, eventContent, prevEventContent) + } + } + + private fun buildProfileNotice(event: Event, senderName: String?, eventContent: RoomMember?, prevEventContent: RoomMember?): String? { + val displayText = StringBuilder() + // Check display name has been changed + if (!TextUtils.equals(eventContent?.displayName, prevEventContent?.displayName)) { + val displayNameText = when { + prevEventContent?.displayName.isNullOrEmpty() -> + stringProvider.getString(R.string.notice_display_name_set, event.sender, eventContent?.displayName) + eventContent?.displayName.isNullOrEmpty() -> + stringProvider.getString(R.string.notice_display_name_removed, event.sender, prevEventContent?.displayName) + else -> + stringProvider.getString(R.string.notice_display_name_changed_from, + event.sender, prevEventContent?.displayName, eventContent?.displayName) + } + displayText.append(displayNameText) + } + // Check whether the avatar has been changed + if (!TextUtils.equals(eventContent?.avatarUrl, prevEventContent?.avatarUrl)) { + val displayAvatarText = if (displayText.isNotEmpty()) { + displayText.append(" ") + stringProvider.getString(R.string.notice_avatar_changed_too) + } else { + stringProvider.getString(R.string.notice_avatar_url_changed, senderName) + } + displayText.append(displayAvatarText) + } + return displayText.toString() + } + + private fun buildMembershipNotice(event: Event, senderName: String?, eventContent: RoomMember?, prevEventContent: RoomMember?): String? { + val senderDisplayName = senderName ?: event.sender + val targetDisplayName = eventContent?.displayName ?: event.sender + return when { + Membership.INVITE == eventContent?.membership -> { + // TODO get userId + val selfUserId = "" + when { + eventContent.thirdPartyInvite != null -> + stringProvider.getString(R.string.notice_room_third_party_registered_invite, + targetDisplayName, eventContent.thirdPartyInvite?.displayName) + TextUtils.equals(event.stateKey, selfUserId) -> + stringProvider.getString(R.string.notice_room_invite_you, senderDisplayName) + event.stateKey.isNullOrEmpty() -> + stringProvider.getString(R.string.notice_room_invite_no_invitee, senderDisplayName) + else -> + stringProvider.getString(R.string.notice_room_invite, senderDisplayName, targetDisplayName) + } + } + Membership.JOIN == eventContent?.membership -> + stringProvider.getString(R.string.notice_room_join, senderDisplayName) + Membership.LEAVE == eventContent?.membership -> + // 2 cases here: this member may have left voluntarily or they may have been "left" by someone else ie. kicked + return if (TextUtils.equals(event.sender, event.stateKey)) { + if (prevEventContent?.membership == Membership.INVITE) { + stringProvider.getString(R.string.notice_room_reject, senderDisplayName) + } else { + stringProvider.getString(R.string.notice_room_leave, senderDisplayName) + } + } else if (prevEventContent?.membership == Membership.INVITE) { + stringProvider.getString(R.string.notice_room_withdraw, senderDisplayName, targetDisplayName) + } else if (prevEventContent?.membership == Membership.JOIN) { + stringProvider.getString(R.string.notice_room_kick, senderDisplayName, targetDisplayName) + } else if (prevEventContent?.membership == Membership.BAN) { + stringProvider.getString(R.string.notice_room_unban, senderDisplayName, targetDisplayName) + } else { + null + } + Membership.BAN == eventContent?.membership -> + stringProvider.getString(R.string.notice_room_ban, senderDisplayName, targetDisplayName) + Membership.KNOCK == eventContent?.membership -> + stringProvider.getString(R.string.notice_room_kick, senderDisplayName, targetDisplayName) + else -> null + } + } + +} diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/helper/RoomMemberEventHelper.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/helper/RoomMemberEventHelper.kt index 499f92f7..83b1fa6c 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/helper/RoomMemberEventHelper.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/helper/RoomMemberEventHelper.kt @@ -16,25 +16,11 @@ package im.vector.riotredesign.features.home.room.detail.timeline.helper -import im.vector.matrix.android.api.session.room.model.Membership +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.timeline.TimelineEvent object RoomMemberEventHelper { - fun senderAvatar(eventContent: RoomMember?, prevEventContent: RoomMember?, event: TimelineEvent): String? { - return if (eventContent?.membership == Membership.LEAVE && eventContent.avatarUrl == null && prevEventContent?.avatarUrl != null) { - prevEventContent.avatarUrl - } else { - event.senderAvatar - } - } - fun senderName(eventContent: RoomMember?, prevEventContent: RoomMember?, event: TimelineEvent): String? { - return if (eventContent?.membership == Membership.LEAVE && eventContent.displayName == null && prevEventContent?.displayName != null) { - prevEventContent.displayName - } else { - event.senderName - } - } } \ No newline at end of file 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 53d63f58..282d8a14 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 @@ -17,6 +17,8 @@ package im.vector.riotredesign.features.home.room.detail.timeline.helper 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.RoomMember import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.riotredesign.core.extensions.localDateTime @@ -49,6 +51,26 @@ fun List.filterDisplayableEvents(): List { } } +fun TimelineEvent.senderAvatar(): String? { + // We might have no avatar when user leave, so we try to get it from prevContent + return senderAvatar + ?: if (root.type == EventType.STATE_ROOM_MEMBER) { + root.prevContent.toModel()?.avatarUrl + } else { + null + } +} + +fun TimelineEvent.senderName(): String? { + // We might have no senderName when user leave, so we try to get it from prevContent + return senderName + ?: if (root.type == EventType.STATE_ROOM_MEMBER) { + root.prevContent.toModel()?.displayName + } else { + null + } +} + fun TimelineEvent.canBeMerged(): Boolean { return root.type == EventType.STATE_ROOM_MEMBER } 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 38463337..3286d540 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 @@ -71,7 +71,6 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback { override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) - bindScope(getOrCreateScope(HomeModule.ROOM_LIST_SCOPE)) setupRecyclerView() setupCreateRoomButton() roomListViewModel.subscribe { renderState(it) } diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt index 633deb90..1845b1e1 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt @@ -22,9 +22,11 @@ import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.riotredesign.core.extensions.localDateTime import im.vector.riotredesign.core.resources.DateProvider import im.vector.riotredesign.core.resources.StringProvider +import im.vector.riotredesign.features.home.room.detail.timeline.format.NoticeEventFormatter import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDateFormatter class RoomSummaryController(private val stringProvider: StringProvider, + private val eventFormatter: NoticeEventFormatter, private val timelineDateFormatter: TimelineDateFormatter ) : TypedEpoxyController() {