From 337fd075ddc15f5d25886b1c73986f8277ef8678 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 21 Dec 2018 14:13:06 +0100 Subject: [PATCH 01/25] Start thinking about navigation. WIP --- .../core/HomePermalinkNavigator.kt | 39 +++++++++++++++++++ .../riotredesign/features/home/HomeActions.kt | 3 ++ .../features/home/HomeActivity.kt | 14 ++++++- .../features/home/HomeNavigator.kt | 6 ++- .../home/room/list/RoomListFragment.kt | 8 ++-- 5 files changed, 63 insertions(+), 7 deletions(-) create mode 100644 app/src/main/java/im/vector/riotredesign/core/HomePermalinkNavigator.kt diff --git a/app/src/main/java/im/vector/riotredesign/core/HomePermalinkNavigator.kt b/app/src/main/java/im/vector/riotredesign/core/HomePermalinkNavigator.kt new file mode 100644 index 00000000..dfa8b977 --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/core/HomePermalinkNavigator.kt @@ -0,0 +1,39 @@ +package im.vector.riotredesign.core + +import android.net.Uri +import im.vector.matrix.android.api.permalinks.PermalinkData +import im.vector.matrix.android.api.permalinks.PermalinkParser +import im.vector.riotredesign.features.home.HomeNavigator + +class HomePermalinkNavigator(private val navigator: HomeNavigator) { + + fun launch(deepLink: String?) { + val uri = deepLink?.let { Uri.parse(it) } + launch(uri) + } + + fun launch(deepLink: Uri?) { + if (deepLink == null) { + return + } + val permalinkData = PermalinkParser.parse(deepLink) + when (permalinkData) { + is PermalinkData.EventLink -> { + navigator.openRoomDetail(permalinkData.roomIdOrAlias, permalinkData.eventId) + } + is PermalinkData.RoomLink -> { + navigator.openRoomDetail(permalinkData.roomIdOrAlias, null ) + } + is PermalinkData.GroupLink -> { + navigator.openGroupDetail(permalinkData.groupId) + } + is PermalinkData.UserLink -> { + navigator.openUserDetail(permalinkData.userId) + } + is PermalinkData.FallbackLink -> { + + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/HomeActions.kt b/app/src/main/java/im/vector/riotredesign/features/home/HomeActions.kt index 4c27edbe..c959a6a2 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/HomeActions.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/HomeActions.kt @@ -1,5 +1,6 @@ package im.vector.riotredesign.features.home +import im.vector.matrix.android.api.permalinks.PermalinkData import im.vector.matrix.android.api.session.group.model.GroupSummary import im.vector.matrix.android.api.session.room.model.RoomSummary @@ -9,6 +10,8 @@ sealed class HomeActions { data class SelectGroup(val groupSummary: GroupSummary) : HomeActions() + data class PermalinkClicked(val permalinkData: PermalinkData) : HomeActions() + object RoomDisplayed : HomeActions() } \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt b/app/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt index 43856ec8..7499694a 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt @@ -18,11 +18,11 @@ import im.vector.riotredesign.features.home.room.detail.LoadingRoomDetailFragmen import im.vector.riotredesign.features.home.room.detail.RoomDetailFragment import kotlinx.android.synthetic.main.activity_home.* import org.koin.standalone.StandAloneContext.loadKoinModules +import timber.log.Timber class HomeActivity : RiotActivity(), HomeNavigator, ToolbarConfigurable { - override fun onCreate(savedInstanceState: Bundle?) { loadKoinModules(listOf(HomeModule(this).definition)) super.onCreate(savedInstanceState) @@ -64,7 +64,9 @@ class HomeActivity : RiotActivity(), HomeNavigator, ToolbarConfigurable { } } - override fun openRoomDetail(roomId: String) { + // HomeNavigator ******************************************************************************* + + override fun openRoomDetail(roomId: String, eventId: String?) { val roomDetailFragment = RoomDetailFragment.newInstance(roomId) if (drawerLayout.isDrawerOpen(Gravity.LEFT)) { closeDrawerLayout(Gravity.LEFT) { replaceFragment(roomDetailFragment, R.id.homeDetailFragmentContainer) } @@ -73,6 +75,14 @@ class HomeActivity : RiotActivity(), HomeNavigator, ToolbarConfigurable { } } + override fun openGroupDetail(groupId: String) { + Timber.v("Open group detail $groupId") + } + + override fun openUserDetail(userId: String) { + Timber.v("Open user detail $userId") + } + private fun closeDrawerLayout(gravity: Int, actionOnClose: () -> Unit) { drawerLayout.addDrawerListener(object : DrawerLayout.SimpleDrawerListener() { override fun onDrawerClosed(p0: View) { diff --git a/app/src/main/java/im/vector/riotredesign/features/home/HomeNavigator.kt b/app/src/main/java/im/vector/riotredesign/features/home/HomeNavigator.kt index 1b4382f1..c306f5c5 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/HomeNavigator.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/HomeNavigator.kt @@ -2,6 +2,10 @@ package im.vector.riotredesign.features.home interface HomeNavigator { - fun openRoomDetail(roomId: String) + fun openRoomDetail(roomId: String, eventId: String?) + + fun openGroupDetail(groupId: String) + + fun openUserDetail(userId: String) } \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt index 774841e3..11cbd7d6 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt @@ -47,11 +47,11 @@ class RoomListFragment : RiotFragment(), RoomSummaryController.Callback { private fun renderState(state: HomeViewState) { when (state.asyncRooms) { is Incomplete -> renderLoading() - is Success -> renderSuccess(state) - is Fail -> renderFailure(state.asyncRooms.error) + is Success -> renderSuccess(state) + is Fail -> renderFailure(state.asyncRooms.error) } if (state.shouldOpenRoomDetail && state.selectedRoom != null) { - homeNavigator.openRoomDetail(state.selectedRoom.roomId) + homeNavigator.openRoomDetail(state.selectedRoom.roomId, null) viewModel.accept(HomeActions.RoomDisplayed) } } @@ -72,7 +72,7 @@ class RoomListFragment : RiotFragment(), RoomSummaryController.Callback { private fun renderFailure(error: Throwable) { val message = when (error) { is Failure.NetworkConnection -> getString(R.string.error_no_network) - else -> getString(R.string.error_common) + else -> getString(R.string.error_common) } stateView.state = StateView.State.Error(message) } From c42be8141ea61552b49e2dca8202e6a5b1394121 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 27 Dec 2018 19:32:52 +0100 Subject: [PATCH 02/25] Iterate on navigation... We have to think about backstack, maybe using a finite state machine. WIP --- .../features/RootFlowCoordinator.kt | 4 ++ .../features/home/HomeFlowCoordinator.kt | 4 ++ .../home/HomePermalinkHandler.kt} | 7 ++- .../features/home/HomeViewModel.kt | 45 ++++++++++++++----- .../features/home/HomeViewState.kt | 3 +- .../home/room/detail/RoomDetailFragment.kt | 7 ++- .../home/room/list/RoomListFragment.kt | 4 +- .../home/room/list/RoomSummaryController.kt | 8 ++-- .../features/login/LoginFlowCoordinator.kt | 2 + 9 files changed, 60 insertions(+), 24 deletions(-) create mode 100644 app/src/main/java/im/vector/riotredesign/features/RootFlowCoordinator.kt create mode 100644 app/src/main/java/im/vector/riotredesign/features/home/HomeFlowCoordinator.kt rename app/src/main/java/im/vector/riotredesign/{core/HomePermalinkNavigator.kt => features/home/HomePermalinkHandler.kt} (85%) create mode 100644 app/src/main/java/im/vector/riotredesign/features/login/LoginFlowCoordinator.kt diff --git a/app/src/main/java/im/vector/riotredesign/features/RootFlowCoordinator.kt b/app/src/main/java/im/vector/riotredesign/features/RootFlowCoordinator.kt new file mode 100644 index 00000000..2c5fc206 --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/features/RootFlowCoordinator.kt @@ -0,0 +1,4 @@ +package im.vector.riotredesign.features + +class RootFlowCoordinator { +} \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/HomeFlowCoordinator.kt b/app/src/main/java/im/vector/riotredesign/features/home/HomeFlowCoordinator.kt new file mode 100644 index 00000000..331b22fb --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/features/home/HomeFlowCoordinator.kt @@ -0,0 +1,4 @@ +package im.vector.riotredesign.features.home + +class HomeFlowCoordinator { +} \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/core/HomePermalinkNavigator.kt b/app/src/main/java/im/vector/riotredesign/features/home/HomePermalinkHandler.kt similarity index 85% rename from app/src/main/java/im/vector/riotredesign/core/HomePermalinkNavigator.kt rename to app/src/main/java/im/vector/riotredesign/features/home/HomePermalinkHandler.kt index dfa8b977..b4809b16 100644 --- a/app/src/main/java/im/vector/riotredesign/core/HomePermalinkNavigator.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/HomePermalinkHandler.kt @@ -1,11 +1,10 @@ -package im.vector.riotredesign.core +package im.vector.riotredesign.features.home import android.net.Uri import im.vector.matrix.android.api.permalinks.PermalinkData import im.vector.matrix.android.api.permalinks.PermalinkParser -import im.vector.riotredesign.features.home.HomeNavigator -class HomePermalinkNavigator(private val navigator: HomeNavigator) { +class HomePermalinkHandler(private val navigator: HomeNavigator) { fun launch(deepLink: String?) { val uri = deepLink?.let { Uri.parse(it) } @@ -22,7 +21,7 @@ class HomePermalinkNavigator(private val navigator: HomeNavigator) { navigator.openRoomDetail(permalinkData.roomIdOrAlias, permalinkData.eventId) } is PermalinkData.RoomLink -> { - navigator.openRoomDetail(permalinkData.roomIdOrAlias, null ) + navigator.openRoomDetail(permalinkData.roomIdOrAlias, null) } is PermalinkData.GroupLink -> { navigator.openGroupDetail(permalinkData.groupId) diff --git a/app/src/main/java/im/vector/riotredesign/features/home/HomeViewModel.kt b/app/src/main/java/im/vector/riotredesign/features/home/HomeViewModel.kt index 6d16d1d8..3e7d437d 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/HomeViewModel.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/HomeViewModel.kt @@ -4,6 +4,7 @@ import android.support.v4.app.FragmentActivity import com.airbnb.mvrx.BaseMvRxViewModel import com.airbnb.mvrx.MvRxViewModelFactory import im.vector.matrix.android.api.Matrix +import im.vector.matrix.android.api.permalinks.PermalinkData import im.vector.matrix.android.api.session.Session import im.vector.matrix.rx.rx @@ -25,19 +26,42 @@ class HomeViewModel(initialState: HomeViewState, private val session: Session) : fun accept(action: HomeActions) { when (action) { - is HomeActions.SelectRoom -> handleSelectRoom(action) - is HomeActions.SelectGroup -> handleSelectGroup(action) - is HomeActions.RoomDisplayed -> setState { copy(shouldOpenRoomDetail = false) } + is HomeActions.SelectRoom -> handleSelectRoom(action) + is HomeActions.SelectGroup -> handleSelectGroup(action) + is HomeActions.RoomDisplayed -> setState { copy(shouldOpenRoomDetail = false) } + is HomeActions.PermalinkClicked -> handlePermalinkClicked(action) } } // PRIVATE METHODS ***************************************************************************** + private fun handlePermalinkClicked(action: HomeActions.PermalinkClicked) { + withState { state -> + when (action.permalinkData) { + is PermalinkData.EventLink -> { + + } + is PermalinkData.RoomLink -> { + + } + is PermalinkData.GroupLink -> { + + } + is PermalinkData.UserLink -> { + + } + is PermalinkData.FallbackLink -> { + + } + } + } + } + private fun handleSelectRoom(action: HomeActions.SelectRoom) { withState { state -> - if (state.selectedRoom?.roomId != action.roomSummary.roomId) { + if (state.selectedRoomId != action.roomSummary.roomId) { session.saveLastSelectedRoom(action.roomSummary) - setState { copy(selectedRoom = action.roomSummary, shouldOpenRoomDetail = true) } + setState { copy(selectedRoomId = action.roomSummary.roomId, shouldOpenRoomDetail = true) } } } } @@ -56,21 +80,20 @@ class HomeViewModel(initialState: HomeViewState, private val session: Session) : session .rx().liveRoomSummaries() .execute { async -> - val summaries = async() val directRooms = summaries?.filter { it.isDirect } ?: emptyList() val groupRooms = summaries?.filter { !it.isDirect } ?: emptyList() - val selectedRoom = selectedRoom - ?: session.lastSelectedRoom() - ?: directRooms.firstOrNull() - ?: groupRooms.firstOrNull() + val selectedRoomId = selectedRoomId + ?: session.lastSelectedRoom()?.roomId + ?: directRooms.firstOrNull()?.roomId + ?: groupRooms.firstOrNull()?.roomId copy( asyncRooms = async, directRooms = directRooms, groupRooms = groupRooms, - selectedRoom = selectedRoom + selectedRoomId = selectedRoomId ) } } diff --git a/app/src/main/java/im/vector/riotredesign/features/home/HomeViewState.kt b/app/src/main/java/im/vector/riotredesign/features/home/HomeViewState.kt index 01c3758e..49211b3d 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/HomeViewState.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/HomeViewState.kt @@ -10,7 +10,8 @@ data class HomeViewState( val asyncRooms: Async> = Uninitialized, val directRooms: List = emptyList(), val groupRooms: List = emptyList(), - val selectedRoom: RoomSummary? = null, + val selectedRoomId: String? = null, + val selectedEventId: String? = null, val shouldOpenRoomDetail: Boolean = true, val asyncGroups: Async> = Uninitialized, val selectedGroup: GroupSummary? = null diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt index 7410c13c..a9acc877 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt @@ -7,6 +7,7 @@ import android.support.v7.widget.LinearLayoutManager import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import com.airbnb.mvrx.activityViewModel import im.vector.matrix.android.api.Matrix import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.permalinks.PermalinkParser @@ -20,11 +21,12 @@ import im.vector.riotredesign.core.platform.ToolbarConfigurable import im.vector.riotredesign.core.utils.FragmentArgumentDelegate import im.vector.riotredesign.core.utils.UnsafeFragmentArgumentDelegate import im.vector.riotredesign.features.home.AvatarRenderer +import im.vector.riotredesign.features.home.HomeActions +import im.vector.riotredesign.features.home.HomeViewModel import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController import kotlinx.android.synthetic.main.fragment_room_detail.* import org.koin.android.ext.android.inject import org.koin.core.parameter.parametersOf -import timber.log.Timber class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback { @@ -38,6 +40,7 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback { } } + private val viewModel: HomeViewModel by activityViewModel() private val currentSession = Matrix.getInstance().currentSession private var roomId: String by UnsafeFragmentArgumentDelegate() private var eventId: String? by FragmentArgumentDelegate() @@ -106,7 +109,7 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback { override fun onUrlClicked(url: String) { val permalinkData = PermalinkParser.parse(url) - Timber.v("Permalink data : $permalinkData") + viewModel.accept(HomeActions.PermalinkClicked(permalinkData)) } } diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt index 11cbd7d6..4830ba8d 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt @@ -50,8 +50,8 @@ class RoomListFragment : RiotFragment(), RoomSummaryController.Callback { is Success -> renderSuccess(state) is Fail -> renderFailure(state.asyncRooms.error) } - if (state.shouldOpenRoomDetail && state.selectedRoom != null) { - homeNavigator.openRoomDetail(state.selectedRoom.roomId, null) + if (state.shouldOpenRoomDetail && state.selectedRoomId != null) { + homeNavigator.openRoomDetail(state.selectedRoomId, null) viewModel.accept(HomeActions.RoomDisplayed) } } diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt index 6aeb893c..998e59fd 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt @@ -34,7 +34,7 @@ class RoomSummaryController(private val callback: Callback? = null .isNotEmpty() } } - buildRoomModels(filteredDirectRooms, viewState.selectedRoom) + buildRoomModels(filteredDirectRooms, viewState.selectedRoomId) } RoomCategoryItem( @@ -52,14 +52,14 @@ class RoomSummaryController(private val callback: Callback? = null val filteredGroupRooms = viewState.groupRooms.filter { viewState.selectedGroup?.roomIds?.contains(it.roomId) ?: true } - buildRoomModels(filteredGroupRooms, viewState.selectedRoom) + buildRoomModels(filteredGroupRooms, viewState.selectedRoomId) } } - private fun buildRoomModels(summaries: List, selected: RoomSummary?) { + private fun buildRoomModels(summaries: List, selectedRoomId: String?) { summaries.forEach { roomSummary -> - val isSelected = roomSummary.roomId == selected?.roomId + val isSelected = roomSummary.roomId == selectedRoomId RoomSummaryItem( roomName = roomSummary.displayName, avatarUrl = roomSummary.avatarUrl, diff --git a/app/src/main/java/im/vector/riotredesign/features/login/LoginFlowCoordinator.kt b/app/src/main/java/im/vector/riotredesign/features/login/LoginFlowCoordinator.kt new file mode 100644 index 00000000..147879d1 --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/features/login/LoginFlowCoordinator.kt @@ -0,0 +1,2 @@ +package im.vector.riotredesign.features.login + From b696e4a6deac35278957b4b0497fc0f3793e12a4 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 28 Dec 2018 12:35:40 +0100 Subject: [PATCH 03/25] Navigation : add simple fragment backstack management. --- .../core/platform/OnBackPressed.kt | 10 ++++++++ .../core/platform/RiotFragment.kt | 5 +++- .../riotredesign/features/MainActivity.kt | 2 -- .../features/RootFlowCoordinator.kt | 4 --- .../features/home/HomeActivity.kt | 25 +++++++++++++++++-- .../features/home/HomeFlowCoordinator.kt | 4 --- .../features/login/LoginFlowCoordinator.kt | 2 -- 7 files changed, 37 insertions(+), 15 deletions(-) create mode 100644 app/src/main/java/im/vector/riotredesign/core/platform/OnBackPressed.kt delete mode 100644 app/src/main/java/im/vector/riotredesign/features/RootFlowCoordinator.kt delete mode 100644 app/src/main/java/im/vector/riotredesign/features/home/HomeFlowCoordinator.kt delete mode 100644 app/src/main/java/im/vector/riotredesign/features/login/LoginFlowCoordinator.kt diff --git a/app/src/main/java/im/vector/riotredesign/core/platform/OnBackPressed.kt b/app/src/main/java/im/vector/riotredesign/core/platform/OnBackPressed.kt new file mode 100644 index 00000000..00faa0c7 --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/core/platform/OnBackPressed.kt @@ -0,0 +1,10 @@ +package im.vector.riotredesign.core.platform + +interface OnBackPressed { + + /** + * Returns true, if the on back pressed event has been handled by this Fragment. + * Otherwise return false + */ + fun onBackPressed(): Boolean +} \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/core/platform/RiotFragment.kt b/app/src/main/java/im/vector/riotredesign/core/platform/RiotFragment.kt index 88300a4e..79d39066 100644 --- a/app/src/main/java/im/vector/riotredesign/core/platform/RiotFragment.kt +++ b/app/src/main/java/im/vector/riotredesign/core/platform/RiotFragment.kt @@ -2,12 +2,15 @@ package im.vector.riotredesign.core.platform import com.airbnb.mvrx.BaseMvRxFragment -abstract class RiotFragment : BaseMvRxFragment() { +abstract class RiotFragment : BaseMvRxFragment(), OnBackPressed { val riotActivity: RiotActivity by lazy { activity as RiotActivity } + override fun onBackPressed(): Boolean { + return false + } override fun invalidate() { //no-ops by default diff --git a/app/src/main/java/im/vector/riotredesign/features/MainActivity.kt b/app/src/main/java/im/vector/riotredesign/features/MainActivity.kt index 32258059..c4363a86 100644 --- a/app/src/main/java/im/vector/riotredesign/features/MainActivity.kt +++ b/app/src/main/java/im/vector/riotredesign/features/MainActivity.kt @@ -5,14 +5,12 @@ import im.vector.matrix.android.api.Matrix import im.vector.riotredesign.core.platform.RiotActivity import im.vector.riotredesign.features.home.HomeActivity import im.vector.riotredesign.features.login.LoginActivity -import org.koin.android.ext.android.inject class MainActivity : RiotActivity() { private val authenticator = Matrix.getInstance().authenticator() - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val intent = if (authenticator.hasActiveSessions()) { diff --git a/app/src/main/java/im/vector/riotredesign/features/RootFlowCoordinator.kt b/app/src/main/java/im/vector/riotredesign/features/RootFlowCoordinator.kt deleted file mode 100644 index 2c5fc206..00000000 --- a/app/src/main/java/im/vector/riotredesign/features/RootFlowCoordinator.kt +++ /dev/null @@ -1,4 +0,0 @@ -package im.vector.riotredesign.features - -class RootFlowCoordinator { -} \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt b/app/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt index 7499694a..b9305742 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt @@ -3,6 +3,7 @@ package im.vector.riotredesign.features.home import android.content.Context import android.content.Intent import android.os.Bundle +import android.support.v4.app.FragmentManager import android.support.v4.view.GravityCompat import android.support.v4.widget.DrawerLayout import android.support.v7.app.ActionBarDrawerToggle @@ -12,6 +13,7 @@ import android.view.MenuItem import android.view.View import im.vector.riotredesign.R import im.vector.riotredesign.core.extensions.replaceFragment +import im.vector.riotredesign.core.platform.OnBackPressed import im.vector.riotredesign.core.platform.RiotActivity import im.vector.riotredesign.core.platform.ToolbarConfigurable import im.vector.riotredesign.features.home.room.detail.LoadingRoomDetailFragment @@ -46,7 +48,6 @@ class HomeActivity : RiotActivity(), HomeNavigator, ToolbarConfigurable { override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { - // Android home android.R.id.home -> { drawerLayout.openDrawer(GravityCompat.START) return true @@ -60,10 +61,30 @@ class HomeActivity : RiotActivity(), HomeNavigator, ToolbarConfigurable { if (drawerLayout.isDrawerOpen(Gravity.LEFT)) { drawerLayout.closeDrawer(Gravity.LEFT) } else { - super.onBackPressed() + val handled = recursivelyDispatchOnBackPressed(supportFragmentManager) + if (!handled) { + super.onBackPressed() + } } } + private fun recursivelyDispatchOnBackPressed(fm: FragmentManager): Boolean { + if (fm.backStackEntryCount == 0) + return false + val reverseOrder = fm.fragments.filter { it is OnBackPressed }.reversed() + for (f in reverseOrder) { + val handledByChildFragments = recursivelyDispatchOnBackPressed(f.childFragmentManager) + if (handledByChildFragments) { + return true + } + val backPressable = f as OnBackPressed + if (backPressable.onBackPressed()) { + return true + } + } + return false + } + // HomeNavigator ******************************************************************************* override fun openRoomDetail(roomId: String, eventId: String?) { diff --git a/app/src/main/java/im/vector/riotredesign/features/home/HomeFlowCoordinator.kt b/app/src/main/java/im/vector/riotredesign/features/home/HomeFlowCoordinator.kt deleted file mode 100644 index 331b22fb..00000000 --- a/app/src/main/java/im/vector/riotredesign/features/home/HomeFlowCoordinator.kt +++ /dev/null @@ -1,4 +0,0 @@ -package im.vector.riotredesign.features.home - -class HomeFlowCoordinator { -} \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/login/LoginFlowCoordinator.kt b/app/src/main/java/im/vector/riotredesign/features/login/LoginFlowCoordinator.kt deleted file mode 100644 index 147879d1..00000000 --- a/app/src/main/java/im/vector/riotredesign/features/login/LoginFlowCoordinator.kt +++ /dev/null @@ -1,2 +0,0 @@ -package im.vector.riotredesign.features.login - From 5a75e3db810a1a127512838b95b2f1e2a9de3181 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 28 Dec 2018 12:56:52 +0100 Subject: [PATCH 04/25] Clean some of the session code API --- .../vector/riotredesign/core/di/AppModule.kt | 10 ++++++ .../features/home/HomeViewModel.kt | 31 +++++++++-------- .../home/room/list/RoomSelectionRepository.kt | 20 +++++++++++ .../android/api/session/room/RoomService.kt | 9 ----- .../internal/session/DefaultSession.kt | 19 ----------- .../session/room/DefaultRoomService.kt | 33 ------------------- 6 files changed, 48 insertions(+), 74 deletions(-) create mode 100644 app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSelectionRepository.kt diff --git a/app/src/main/java/im/vector/riotredesign/core/di/AppModule.kt b/app/src/main/java/im/vector/riotredesign/core/di/AppModule.kt index 92daa091..7a25d46d 100644 --- a/app/src/main/java/im/vector/riotredesign/core/di/AppModule.kt +++ b/app/src/main/java/im/vector/riotredesign/core/di/AppModule.kt @@ -1,7 +1,9 @@ package im.vector.riotredesign.core.di import android.content.Context +import android.content.Context.MODE_PRIVATE import im.vector.riotredesign.core.resources.LocaleProvider +import im.vector.riotredesign.features.home.room.list.RoomSelectionRepository import org.koin.dsl.module.module class AppModule(private val context: Context) { @@ -12,5 +14,13 @@ class AppModule(private val context: Context) { LocaleProvider(context.resources) } + single { + context.getSharedPreferences("im.vector.riot", MODE_PRIVATE) + } + + single { + RoomSelectionRepository(get()) + } + } } \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/HomeViewModel.kt b/app/src/main/java/im/vector/riotredesign/features/home/HomeViewModel.kt index 3e7d437d..e6487c14 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/HomeViewModel.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/HomeViewModel.kt @@ -7,15 +7,20 @@ import im.vector.matrix.android.api.Matrix import im.vector.matrix.android.api.permalinks.PermalinkData import im.vector.matrix.android.api.session.Session import im.vector.matrix.rx.rx +import im.vector.riotredesign.features.home.room.list.RoomSelectionRepository +import org.koin.android.ext.android.get -class HomeViewModel(initialState: HomeViewState, private val session: Session) : BaseMvRxViewModel(initialState) { +class HomeViewModel(initialState: HomeViewState, + private val session: Session, + private val roomSelectionRepository: RoomSelectionRepository) : BaseMvRxViewModel(initialState) { companion object : MvRxViewModelFactory { @JvmStatic override fun create(activity: FragmentActivity, state: HomeViewState): HomeViewModel { val currentSession = Matrix.getInstance().currentSession - return HomeViewModel(state, currentSession) + val roomSelectionRepository = activity.get() + return HomeViewModel(state, currentSession, roomSelectionRepository) } } @@ -26,9 +31,9 @@ class HomeViewModel(initialState: HomeViewState, private val session: Session) : fun accept(action: HomeActions) { when (action) { - is HomeActions.SelectRoom -> handleSelectRoom(action) - is HomeActions.SelectGroup -> handleSelectGroup(action) - is HomeActions.RoomDisplayed -> setState { copy(shouldOpenRoomDetail = false) } + is HomeActions.SelectRoom -> handleSelectRoom(action) + is HomeActions.SelectGroup -> handleSelectGroup(action) + is HomeActions.RoomDisplayed -> setState { copy(shouldOpenRoomDetail = false) } is HomeActions.PermalinkClicked -> handlePermalinkClicked(action) } } @@ -38,16 +43,16 @@ class HomeViewModel(initialState: HomeViewState, private val session: Session) : private fun handlePermalinkClicked(action: HomeActions.PermalinkClicked) { withState { state -> when (action.permalinkData) { - is PermalinkData.EventLink -> { + is PermalinkData.EventLink -> { } - is PermalinkData.RoomLink -> { + is PermalinkData.RoomLink -> { } - is PermalinkData.GroupLink -> { + is PermalinkData.GroupLink -> { } - is PermalinkData.UserLink -> { + is PermalinkData.UserLink -> { } is PermalinkData.FallbackLink -> { @@ -60,7 +65,7 @@ class HomeViewModel(initialState: HomeViewState, private val session: Session) : private fun handleSelectRoom(action: HomeActions.SelectRoom) { withState { state -> if (state.selectedRoomId != action.roomSummary.roomId) { - session.saveLastSelectedRoom(action.roomSummary) + roomSelectionRepository.saveLastSelectedRoom(action.roomSummary.roomId) setState { copy(selectedRoomId = action.roomSummary.roomId, shouldOpenRoomDetail = true) } } } @@ -85,9 +90,9 @@ class HomeViewModel(initialState: HomeViewState, private val session: Session) : val groupRooms = summaries?.filter { !it.isDirect } ?: emptyList() val selectedRoomId = selectedRoomId - ?: session.lastSelectedRoom()?.roomId - ?: directRooms.firstOrNull()?.roomId - ?: groupRooms.firstOrNull()?.roomId + ?: roomSelectionRepository.lastSelectedRoom() + ?: directRooms.firstOrNull()?.roomId + ?: groupRooms.firstOrNull()?.roomId copy( asyncRooms = async, diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSelectionRepository.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSelectionRepository.kt new file mode 100644 index 00000000..f2a7af75 --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSelectionRepository.kt @@ -0,0 +1,20 @@ +package im.vector.riotredesign.features.home.room.list + +import android.content.SharedPreferences + +private const val SHARED_PREFS_SELECTED_ROOM_KEY = "SHARED_PREFS_SELECTED_ROOM_KEY" + +class RoomSelectionRepository(private val sharedPreferences: SharedPreferences) { + + fun lastSelectedRoom(): String? { + return sharedPreferences.getString(SHARED_PREFS_SELECTED_ROOM_KEY, null) + } + + fun saveLastSelectedRoom(roomId: String) { + sharedPreferences.edit() + .putString(SHARED_PREFS_SELECTED_ROOM_KEY, roomId) + .apply() + } + +} + diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt index 79170bdb..3f02d203 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt @@ -7,15 +7,6 @@ interface RoomService { fun getRoom(roomId: String): Room? - fun getAllRooms(): List - - fun liveRooms(): LiveData> - fun liveRoomSummaries(): LiveData> - fun lastSelectedRoom(): RoomSummary? - - fun saveLastSelectedRoom(roomSummary: RoomSummary) - - } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt index 7047b1dc..2841f089 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt @@ -69,31 +69,12 @@ internal class DefaultSession(override val sessionParams: SessionParams) : Sessi return roomService.getRoom(roomId) } - override fun getAllRooms(): List { - assert(isOpen) - return roomService.getAllRooms() - } - - override fun liveRooms(): LiveData> { - assert(isOpen) - return roomService.liveRooms() - } override fun liveRoomSummaries(): LiveData> { assert(isOpen) return roomService.liveRoomSummaries() } - override fun lastSelectedRoom(): RoomSummary? { - assert(isOpen) - return roomService.lastSelectedRoom() - } - - override fun saveLastSelectedRoom(roomSummary: RoomSummary) { - assert(isOpen) - roomService.saveLastSelectedRoom(roomSummary) - } - // GROUP SERVICE override fun getGroup(groupId: String): Group? { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt index 90c10659..e9b0aac4 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt @@ -9,19 +9,10 @@ import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields -import im.vector.matrix.android.internal.database.query.lastSelected import im.vector.matrix.android.internal.database.query.where internal class DefaultRoomService(private val monarchy: Monarchy) : RoomService { - override fun getAllRooms(): List { - var rooms: List = emptyList() - monarchy.doWithRealm { realm -> - rooms = RoomEntity.where(realm).findAll().map { it.asDomain() } - } - return rooms - } - override fun getRoom(roomId: String): Room? { var room: Room? = null monarchy.doWithRealm { realm -> @@ -30,34 +21,10 @@ internal class DefaultRoomService(private val monarchy: Monarchy) : RoomService return room } - override fun liveRooms(): LiveData> { - return monarchy.findAllMappedWithChanges( - { realm -> RoomEntity.where(realm) }, - { it.asDomain() } - ) - } - override fun liveRoomSummaries(): LiveData> { return monarchy.findAllMappedWithChanges( { realm -> RoomSummaryEntity.where(realm).isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME) }, { it.asDomain() } ) } - - override fun lastSelectedRoom(): RoomSummary? { - var lastSelected: RoomSummary? = null - monarchy.doWithRealm { realm -> - lastSelected = RoomSummaryEntity.lastSelected(realm)?.asDomain() - } - return lastSelected - } - - override fun saveLastSelectedRoom(roomSummary: RoomSummary) { - monarchy.writeAsync { realm -> - val lastSelected = RoomSummaryEntity.lastSelected(realm) - val roomSummaryEntity = RoomSummaryEntity.where(realm, roomSummary.roomId).findFirst() - lastSelected?.isLatestSelected = false - roomSummaryEntity?.isLatestSelected = true - } - } } \ No newline at end of file From 7f11c141c720c5875a5d8ba7451356d5e17d97ae Mon Sep 17 00:00:00 2001 From: ganfra Date: Sat, 29 Dec 2018 17:54:03 +0100 Subject: [PATCH 05/25] Use MvRx in room detail --- app/build.gradle | 5 +- .../core/platform/RiotFragment.kt | 6 ++ .../features/home/HomeActivity.kt | 4 +- .../features/home/HomeViewModel.kt | 46 ++++++-------- .../home/room/detail/RoomDetailActions.kt | 7 +++ .../home/room/detail/RoomDetailFragment.kt | 60 +++++++++--------- .../home/room/detail/RoomDetailViewModel.kt | 62 +++++++++++++++++++ .../home/room/detail/RoomDetailViewState.kt | 21 +++++++ .../timeline/TimelineEventController.kt | 3 +- .../home/room/list/RoomListFragment.kt | 14 ++--- matrix-sdk-android-rx/build.gradle | 3 + .../main/java/im/vector/matrix/rx/RxRoom.kt | 24 +++++++ 12 files changed, 191 insertions(+), 64 deletions(-) create mode 100644 app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailActions.kt create mode 100644 app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt create mode 100644 app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewState.kt create mode 100644 matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt diff --git a/app/build.gradle b/app/build.gradle index 98e9b05a..cae6db86 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -7,6 +7,10 @@ kapt { correctErrorTypes = true } +androidExtensions { + experimental = true +} + android { compileSdkVersion 28 defaultConfig { @@ -64,7 +68,6 @@ dependencies { implementation 'com.github.bumptech.glide:glide:4.8.0' kapt 'com.github.bumptech.glide:compiler:4.8.0' - //todo remove that implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1' diff --git a/app/src/main/java/im/vector/riotredesign/core/platform/RiotFragment.kt b/app/src/main/java/im/vector/riotredesign/core/platform/RiotFragment.kt index 79d39066..17899698 100644 --- a/app/src/main/java/im/vector/riotredesign/core/platform/RiotFragment.kt +++ b/app/src/main/java/im/vector/riotredesign/core/platform/RiotFragment.kt @@ -1,6 +1,9 @@ package im.vector.riotredesign.core.platform +import android.os.Bundle +import android.os.Parcelable import com.airbnb.mvrx.BaseMvRxFragment +import com.airbnb.mvrx.MvRx abstract class RiotFragment : BaseMvRxFragment(), OnBackPressed { @@ -16,5 +19,8 @@ abstract class RiotFragment : BaseMvRxFragment(), OnBackPressed { //no-ops by default } + protected fun setArguments(args: Parcelable? = null) { + arguments = args?.let { Bundle().apply { putParcelable(MvRx.KEY_ARG, it) } } + } } \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt b/app/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt index b9305742..262f040c 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt @@ -17,6 +17,7 @@ import im.vector.riotredesign.core.platform.OnBackPressed import im.vector.riotredesign.core.platform.RiotActivity import im.vector.riotredesign.core.platform.ToolbarConfigurable import im.vector.riotredesign.features.home.room.detail.LoadingRoomDetailFragment +import im.vector.riotredesign.features.home.room.detail.RoomDetailArgs import im.vector.riotredesign.features.home.room.detail.RoomDetailFragment import kotlinx.android.synthetic.main.activity_home.* import org.koin.standalone.StandAloneContext.loadKoinModules @@ -88,7 +89,8 @@ class HomeActivity : RiotActivity(), HomeNavigator, ToolbarConfigurable { // HomeNavigator ******************************************************************************* override fun openRoomDetail(roomId: String, eventId: String?) { - val roomDetailFragment = RoomDetailFragment.newInstance(roomId) + val args = RoomDetailArgs(roomId, eventId) + val roomDetailFragment = RoomDetailFragment.newInstance(args) if (drawerLayout.isDrawerOpen(Gravity.LEFT)) { closeDrawerLayout(Gravity.LEFT) { replaceFragment(roomDetailFragment, R.id.homeDetailFragmentContainer) } } else { diff --git a/app/src/main/java/im/vector/riotredesign/features/home/HomeViewModel.kt b/app/src/main/java/im/vector/riotredesign/features/home/HomeViewModel.kt index e6487c14..9fe633a6 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/HomeViewModel.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/HomeViewModel.kt @@ -40,44 +40,38 @@ class HomeViewModel(initialState: HomeViewState, // PRIVATE METHODS ***************************************************************************** - private fun handlePermalinkClicked(action: HomeActions.PermalinkClicked) { - withState { state -> - when (action.permalinkData) { - is PermalinkData.EventLink -> { + private fun handlePermalinkClicked(action: HomeActions.PermalinkClicked) = withState { state -> + when (action.permalinkData) { + is PermalinkData.EventLink -> { - } - is PermalinkData.RoomLink -> { + } + is PermalinkData.RoomLink -> { - } - is PermalinkData.GroupLink -> { + } + is PermalinkData.GroupLink -> { - } - is PermalinkData.UserLink -> { + } + is PermalinkData.UserLink -> { - } - is PermalinkData.FallbackLink -> { + } + is PermalinkData.FallbackLink -> { - } } } } - private fun handleSelectRoom(action: HomeActions.SelectRoom) { - withState { state -> - if (state.selectedRoomId != action.roomSummary.roomId) { - roomSelectionRepository.saveLastSelectedRoom(action.roomSummary.roomId) - setState { copy(selectedRoomId = action.roomSummary.roomId, shouldOpenRoomDetail = true) } - } + private fun handleSelectRoom(action: HomeActions.SelectRoom) = withState { state -> + if (state.selectedRoomId != action.roomSummary.roomId) { + roomSelectionRepository.saveLastSelectedRoom(action.roomSummary.roomId) + setState { copy(selectedRoomId = action.roomSummary.roomId, shouldOpenRoomDetail = true) } } } - private fun handleSelectGroup(action: HomeActions.SelectGroup) { - withState { state -> - if (state.selectedGroup?.groupId != action.groupSummary.groupId) { - setState { copy(selectedGroup = action.groupSummary) } - } else { - setState { copy(selectedGroup = null) } - } + private fun handleSelectGroup(action: HomeActions.SelectGroup) = withState { state -> + if (state.selectedGroup?.groupId != action.groupSummary.groupId) { + setState { copy(selectedGroup = action.groupSummary) } + } else { + setState { copy(selectedGroup = null) } } } diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailActions.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailActions.kt new file mode 100644 index 00000000..032caa8a --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailActions.kt @@ -0,0 +1,7 @@ +package im.vector.riotredesign.features.home.room.detail + +sealed class RoomDetailActions { + + data class SendMessage(val text: String) : RoomDetailActions() + +} \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt index a9acc877..4ed0272e 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt @@ -1,51 +1,51 @@ package im.vector.riotredesign.features.home.room.detail -import android.arch.lifecycle.Observer -import android.arch.paging.PagedList import android.os.Bundle +import android.os.Parcelable import android.support.v7.widget.LinearLayoutManager import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import com.airbnb.mvrx.Success import com.airbnb.mvrx.activityViewModel -import im.vector.matrix.android.api.Matrix -import im.vector.matrix.android.api.MatrixCallback +import com.airbnb.mvrx.args +import com.airbnb.mvrx.fragmentViewModel import im.vector.matrix.android.api.permalinks.PermalinkParser -import im.vector.matrix.android.api.session.events.model.EnrichedEvent -import im.vector.matrix.android.api.session.events.model.Event -import im.vector.matrix.android.api.session.room.Room import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.riotredesign.R import im.vector.riotredesign.core.platform.RiotFragment import im.vector.riotredesign.core.platform.ToolbarConfigurable -import im.vector.riotredesign.core.utils.FragmentArgumentDelegate -import im.vector.riotredesign.core.utils.UnsafeFragmentArgumentDelegate import im.vector.riotredesign.features.home.AvatarRenderer import im.vector.riotredesign.features.home.HomeActions import im.vector.riotredesign.features.home.HomeViewModel import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController +import kotlinx.android.parcel.Parcelize import kotlinx.android.synthetic.main.fragment_room_detail.* import org.koin.android.ext.android.inject import org.koin.core.parameter.parametersOf +@Parcelize +data class RoomDetailArgs( + val roomId: String, + val eventId: String? = null +) : Parcelable + class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback { companion object { - fun newInstance(roomId: String, eventId: String? = null): RoomDetailFragment { + fun newInstance(args: RoomDetailArgs): RoomDetailFragment { return RoomDetailFragment().apply { - this.roomId = roomId - this.eventId = eventId + setArguments(args) } } } - private val viewModel: HomeViewModel by activityViewModel() - private val currentSession = Matrix.getInstance().currentSession - private var roomId: String by UnsafeFragmentArgumentDelegate() - private var eventId: String? by FragmentArgumentDelegate() - private val timelineEventController by inject { parametersOf(roomId) } - private lateinit var room: Room + private val homeViewModel: HomeViewModel by activityViewModel() + private val roomDetailViewModel: RoomDetailViewModel by fragmentViewModel() + private val roomDetailArgs: RoomDetailArgs by args() + + private val timelineEventController by inject { parametersOf(roomDetailArgs.roomId) } private lateinit var scrollOnNewMessageCallback: ScrollOnNewMessageCallback override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { @@ -54,21 +54,16 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback { override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) - room = currentSession.getRoom(roomId)!! setupRecyclerView() setupToolbar() - room.loadRoomMembersIfNeeded() - room.timeline(eventId).observe(this, Observer { renderEvents(it) }) - room.roomSummary.observe(this, Observer { renderRoomSummary(it) }) sendButton.setOnClickListener { val textMessage = composerEditText.text.toString() if (textMessage.isNotBlank()) { composerEditText.text = null - room.sendTextMessage(textMessage, object : MatrixCallback { - - }) + roomDetailViewModel.accept(RoomDetailActions.SendMessage(textMessage)) } } + roomDetailViewModel.subscribe { renderState(it) } } private fun setupToolbar() { @@ -87,6 +82,15 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback { timelineEventController.callback = this } + private fun renderState(state: RoomDetailViewState) { + when (state.asyncTimeline) { + is Success -> renderTimeline(state.asyncTimeline()) + } + when (state.asyncRoomSummary) { + is Success -> renderRoomSummary(state.asyncRoomSummary()) + } + } + private fun renderRoomSummary(roomSummary: RoomSummary?) { roomSummary?.let { toolbarTitleView.text = it.displayName @@ -100,16 +104,16 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback { } } - private fun renderEvents(events: PagedList?) { + private fun renderTimeline(timeline: Timeline?) { scrollOnNewMessageCallback.hasBeenUpdated.set(true) - timelineEventController.timeline = events + timelineEventController.timeline = timeline } // TimelineEventController.Callback ************************************************************ override fun onUrlClicked(url: String) { val permalinkData = PermalinkParser.parse(url) - viewModel.accept(HomeActions.PermalinkClicked(permalinkData)) + homeViewModel.accept(HomeActions.PermalinkClicked(permalinkData)) } } diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt new file mode 100644 index 00000000..89c70a0c --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt @@ -0,0 +1,62 @@ +package im.vector.riotredesign.features.home.room.detail + +import android.support.v4.app.FragmentActivity +import com.airbnb.mvrx.BaseMvRxViewModel +import com.airbnb.mvrx.MvRxViewModelFactory +import im.vector.matrix.android.api.Matrix +import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.rx.rx + +class RoomDetailViewModel(initialState: RoomDetailViewState, + session: Session +) : BaseMvRxViewModel(initialState) { + + private val room = session.getRoom(initialState.roomId)!! + private val roomId = initialState.roomId + private val eventId = initialState.eventId + + companion object : MvRxViewModelFactory { + + @JvmStatic + override fun create(activity: FragmentActivity, state: RoomDetailViewState): RoomDetailViewModel { + val currentSession = Matrix.getInstance().currentSession + return RoomDetailViewModel(state, currentSession) + } + } + + init { + observeRoomSummary() + observeTimeline() + room.loadRoomMembersIfNeeded() + } + + fun accept(action: RoomDetailActions) { + when (action) { + is RoomDetailActions.SendMessage -> handleSendMessage(action) + } + } + + // PRIVATE METHODS ***************************************************************************** + + private fun handleSendMessage(action: RoomDetailActions.SendMessage) { + room.sendTextMessage(action.text, callback = object : MatrixCallback {}) + } + + private fun observeRoomSummary() { + room.rx().liveRoomSummary() + .execute { async -> + copy(asyncRoomSummary = async) + } + } + + private fun observeTimeline() { + room.rx().timeline(eventId) + .execute { async -> + copy(asyncTimeline = async) + } + } + + +} \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewState.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewState.kt new file mode 100644 index 00000000..83f04851 --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewState.kt @@ -0,0 +1,21 @@ +package im.vector.riotredesign.features.home.room.detail + +import android.arch.paging.PagedList +import com.airbnb.mvrx.Async +import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.Uninitialized +import im.vector.matrix.android.api.session.events.model.EnrichedEvent +import im.vector.matrix.android.api.session.room.model.RoomSummary + +typealias Timeline = PagedList + +data class RoomDetailViewState( + val roomId: String, + val eventId: String?, + val asyncRoomSummary: Async = Uninitialized, + val asyncTimeline: Async = Uninitialized +) : MvRxState { + + constructor(args: RoomDetailArgs) : this(roomId = args.roomId, eventId = args.eventId) + +} \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt index 73888167..ecda3c02 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt @@ -7,6 +7,7 @@ import im.vector.matrix.android.api.session.events.model.EnrichedEvent import im.vector.matrix.android.api.session.events.model.EventType import im.vector.riotredesign.core.extensions.localDateTime import im.vector.riotredesign.features.home.LoadingItemModel_ +import im.vector.riotredesign.features.home.room.detail.Timeline class TimelineEventController(private val roomId: String, private val messageItemFactory: MessageItemFactory, @@ -36,7 +37,7 @@ class TimelineEventController(private val roomId: String, } private var snapshotList: List? = emptyList() - var timeline: PagedList? = null + var timeline: Timeline? = null set(value) { field?.removeWeakCallback(pagedListCallback) field = value diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt index 4830ba8d..eeab8374 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt @@ -29,7 +29,7 @@ class RoomListFragment : RiotFragment(), RoomSummaryController.Callback { } private val homeNavigator by inject() - private val viewModel: HomeViewModel by activityViewModel() + private val homeViewModel: HomeViewModel by activityViewModel() private lateinit var roomController: RoomSummaryController override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { @@ -41,18 +41,18 @@ class RoomListFragment : RiotFragment(), RoomSummaryController.Callback { roomController = RoomSummaryController(this) stateView.contentView = epoxyRecyclerView epoxyRecyclerView.setController(roomController) - viewModel.subscribe { renderState(it) } + homeViewModel.subscribe { renderState(it) } } private fun renderState(state: HomeViewState) { when (state.asyncRooms) { is Incomplete -> renderLoading() - is Success -> renderSuccess(state) - is Fail -> renderFailure(state.asyncRooms.error) + is Success -> renderSuccess(state) + is Fail -> renderFailure(state.asyncRooms.error) } if (state.shouldOpenRoomDetail && state.selectedRoomId != null) { homeNavigator.openRoomDetail(state.selectedRoomId, null) - viewModel.accept(HomeActions.RoomDisplayed) + homeViewModel.accept(HomeActions.RoomDisplayed) } } @@ -72,13 +72,13 @@ class RoomListFragment : RiotFragment(), RoomSummaryController.Callback { private fun renderFailure(error: Throwable) { val message = when (error) { is Failure.NetworkConnection -> getString(R.string.error_no_network) - else -> getString(R.string.error_common) + else -> getString(R.string.error_common) } stateView.state = StateView.State.Error(message) } override fun onRoomSelected(room: RoomSummary) { - viewModel.accept(HomeActions.SelectRoom(room)) + homeViewModel.accept(HomeActions.SelectRoom(room)) } } \ No newline at end of file diff --git a/matrix-sdk-android-rx/build.gradle b/matrix-sdk-android-rx/build.gradle index 8f51158f..c05a36e8 100644 --- a/matrix-sdk-android-rx/build.gradle +++ b/matrix-sdk-android-rx/build.gradle @@ -39,6 +39,9 @@ dependencies { implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0' implementation 'io.reactivex.rxjava2:rxandroid:2.0.2' + // Paging + api "android.arch.paging:runtime:1.0.1" + testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.2' diff --git a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt new file mode 100644 index 00000000..e588d078 --- /dev/null +++ b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt @@ -0,0 +1,24 @@ +package im.vector.matrix.rx + +import android.arch.paging.PagedList +import im.vector.matrix.android.api.session.events.model.EnrichedEvent +import im.vector.matrix.android.api.session.group.model.GroupSummary +import im.vector.matrix.android.api.session.room.Room +import im.vector.matrix.android.api.session.room.model.RoomSummary +import io.reactivex.Observable + +class RxRoom(private val room: Room) { + + fun liveRoomSummary(): Observable { + return room.roomSummary.asObservable() + } + + fun timeline(eventId: String? = null): Observable> { + return room.timeline(eventId).asObservable() + } + +} + +fun Room.rx(): RxRoom { + return RxRoom(this) +} \ No newline at end of file From 7c0df91a58b4c7db7de069b2f0df1e4f0b594ca6 Mon Sep 17 00:00:00 2001 From: ganfra Date: Sat, 29 Dec 2018 19:57:38 +0100 Subject: [PATCH 06/25] Remove HomeViewModel and dispatch in multiple view models (one for each fragment) --- app/build.gradle | 14 ++- .../core/platform/RiotViewModel.kt | 7 ++ .../riotredesign/features/home/HomeActions.kt | 17 --- .../features/home/HomeActivity.kt | 19 +--- .../riotredesign/features/home/HomeModule.kt | 5 + .../features/home/HomeViewModel.kt | 107 ------------------ .../features/home/HomeViewState.kt | 18 --- .../features/home/group/GroupListActions.kt | 9 ++ .../features/home/group/GroupListFragment.kt | 13 +-- .../features/home/group/GroupListViewModel.kt | 64 +++++++++++ .../features/home/group/GroupListViewState.kt | 11 ++ .../home/group/GroupSummaryController.kt | 5 +- .../home/group/SelectedGroupHolder.kt | 22 ++++ .../home/room/detail/RoomDetailFragment.kt | 15 +-- .../home/room/detail/RoomDetailViewModel.kt | 9 +- .../home/room/list/RoomListActions.kt | 11 ++ .../home/room/list/RoomListFragment.kt | 16 +-- .../home/room/list/RoomListViewModel.kt | 93 +++++++++++++++ .../home/room/list/RoomListViewState.kt | 20 ++++ .../home/room/list/RoomSummaryController.kt | 24 +--- matrix-sdk-android-rx/build.gradle | 2 +- .../main/java/im/vector/matrix/rx/RxRoom.kt | 3 +- 22 files changed, 282 insertions(+), 222 deletions(-) create mode 100644 app/src/main/java/im/vector/riotredesign/core/platform/RiotViewModel.kt delete mode 100644 app/src/main/java/im/vector/riotredesign/features/home/HomeActions.kt delete mode 100644 app/src/main/java/im/vector/riotredesign/features/home/HomeViewModel.kt delete mode 100644 app/src/main/java/im/vector/riotredesign/features/home/HomeViewState.kt create mode 100644 app/src/main/java/im/vector/riotredesign/features/home/group/GroupListActions.kt create mode 100644 app/src/main/java/im/vector/riotredesign/features/home/group/GroupListViewModel.kt create mode 100644 app/src/main/java/im/vector/riotredesign/features/home/group/GroupListViewState.kt create mode 100644 app/src/main/java/im/vector/riotredesign/features/home/group/SelectedGroupHolder.kt create mode 100644 app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListActions.kt create mode 100644 app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewModel.kt create mode 100644 app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewState.kt diff --git a/app/build.gradle b/app/build.gradle index cae6db86..1caa4065 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -47,7 +47,7 @@ configurations.all { strategy -> dependencies { def epoxy_version = "2.19.0" - + def arrow_version = "0.8.0" implementation fileTree(dir: 'libs', include: ['*.jar']) implementation project(":matrix-sdk-android") @@ -58,22 +58,30 @@ dependencies { implementation 'com.android.support.constraint:constraint-layout:1.1.3' implementation 'com.jakewharton.threetenabp:threetenabp:1.1.1' - implementation 'com.jakewharton.timber:timber:4.7.1' + // rx + implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0' + implementation 'io.reactivex.rxjava2:rxandroid:2.0.2' + implementation("com.airbnb.android:epoxy:$epoxy_version") kapt "com.airbnb.android:epoxy-processor:$epoxy_version" implementation "com.airbnb.android:epoxy-paging:$epoxy_version" implementation 'com.airbnb.android:mvrx:0.6.0' + // FP + implementation "io.arrow-kt:arrow-core:$arrow_version" + + // UI implementation 'com.github.bumptech.glide:glide:4.8.0' kapt 'com.github.bumptech.glide:compiler:4.8.0' implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1' - + // DI implementation "org.koin:koin-android:$koin_version" implementation "org.koin:koin-android-scope:$koin_version" + // TESTS testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' diff --git a/app/src/main/java/im/vector/riotredesign/core/platform/RiotViewModel.kt b/app/src/main/java/im/vector/riotredesign/core/platform/RiotViewModel.kt new file mode 100644 index 00000000..da438d3d --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/core/platform/RiotViewModel.kt @@ -0,0 +1,7 @@ +package im.vector.riotredesign.core.platform + +import com.airbnb.mvrx.BaseMvRxViewModel +import com.airbnb.mvrx.MvRxState + +abstract class RiotViewModel(initialState: S) + : BaseMvRxViewModel(initialState, debugMode = false) \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/HomeActions.kt b/app/src/main/java/im/vector/riotredesign/features/home/HomeActions.kt deleted file mode 100644 index c959a6a2..00000000 --- a/app/src/main/java/im/vector/riotredesign/features/home/HomeActions.kt +++ /dev/null @@ -1,17 +0,0 @@ -package im.vector.riotredesign.features.home - -import im.vector.matrix.android.api.permalinks.PermalinkData -import im.vector.matrix.android.api.session.group.model.GroupSummary -import im.vector.matrix.android.api.session.room.model.RoomSummary - -sealed class HomeActions { - - data class SelectRoom(val roomSummary: RoomSummary) : HomeActions() - - data class SelectGroup(val groupSummary: GroupSummary) : HomeActions() - - data class PermalinkClicked(val permalinkData: PermalinkData) : HomeActions() - - object RoomDisplayed : HomeActions() - -} \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt b/app/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt index 262f040c..c75cc051 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt @@ -5,12 +5,10 @@ import android.content.Intent import android.os.Bundle import android.support.v4.app.FragmentManager import android.support.v4.view.GravityCompat -import android.support.v4.widget.DrawerLayout import android.support.v7.app.ActionBarDrawerToggle import android.support.v7.widget.Toolbar import android.view.Gravity import android.view.MenuItem -import android.view.View import im.vector.riotredesign.R import im.vector.riotredesign.core.extensions.replaceFragment import im.vector.riotredesign.core.platform.OnBackPressed @@ -91,11 +89,8 @@ class HomeActivity : RiotActivity(), HomeNavigator, ToolbarConfigurable { override fun openRoomDetail(roomId: String, eventId: String?) { val args = RoomDetailArgs(roomId, eventId) val roomDetailFragment = RoomDetailFragment.newInstance(args) - if (drawerLayout.isDrawerOpen(Gravity.LEFT)) { - closeDrawerLayout(Gravity.LEFT) { replaceFragment(roomDetailFragment, R.id.homeDetailFragmentContainer) } - } else { - replaceFragment(roomDetailFragment, R.id.homeDetailFragmentContainer) - } + drawerLayout.closeDrawer(Gravity.LEFT) + replaceFragment(roomDetailFragment, R.id.homeDetailFragmentContainer) } override fun openGroupDetail(groupId: String) { @@ -106,16 +101,6 @@ class HomeActivity : RiotActivity(), HomeNavigator, ToolbarConfigurable { Timber.v("Open user detail $userId") } - private fun closeDrawerLayout(gravity: Int, actionOnClose: () -> Unit) { - drawerLayout.addDrawerListener(object : DrawerLayout.SimpleDrawerListener() { - override fun onDrawerClosed(p0: View) { - drawerLayout.removeDrawerListener(this) - actionOnClose() - } - }) - drawerLayout.closeDrawer(gravity) - } - companion object { fun newIntent(context: Context): Intent { return Intent(context, HomeActivity::class.java) diff --git a/app/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt b/app/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt index 84e8e8bc..75491d0a 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt @@ -1,5 +1,6 @@ package im.vector.riotredesign.features.home +import im.vector.riotredesign.features.home.group.SelectedGroupHolder import im.vector.riotredesign.features.home.room.detail.timeline.MessageItemFactory import im.vector.riotredesign.features.home.room.detail.timeline.TextItemFactory import im.vector.riotredesign.features.home.room.detail.timeline.TimelineDateFormatter @@ -30,5 +31,9 @@ class HomeModule(private val homeActivity: HomeActivity) { TimelineEventController(roomId, get(), get(), get()) } + single { + SelectedGroupHolder() + } + } } \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/HomeViewModel.kt b/app/src/main/java/im/vector/riotredesign/features/home/HomeViewModel.kt deleted file mode 100644 index 9fe633a6..00000000 --- a/app/src/main/java/im/vector/riotredesign/features/home/HomeViewModel.kt +++ /dev/null @@ -1,107 +0,0 @@ -package im.vector.riotredesign.features.home - -import android.support.v4.app.FragmentActivity -import com.airbnb.mvrx.BaseMvRxViewModel -import com.airbnb.mvrx.MvRxViewModelFactory -import im.vector.matrix.android.api.Matrix -import im.vector.matrix.android.api.permalinks.PermalinkData -import im.vector.matrix.android.api.session.Session -import im.vector.matrix.rx.rx -import im.vector.riotredesign.features.home.room.list.RoomSelectionRepository -import org.koin.android.ext.android.get - -class HomeViewModel(initialState: HomeViewState, - private val session: Session, - private val roomSelectionRepository: RoomSelectionRepository) : BaseMvRxViewModel(initialState) { - - companion object : MvRxViewModelFactory { - - @JvmStatic - override fun create(activity: FragmentActivity, state: HomeViewState): HomeViewModel { - val currentSession = Matrix.getInstance().currentSession - val roomSelectionRepository = activity.get() - return HomeViewModel(state, currentSession, roomSelectionRepository) - } - } - - init { - observeRoomSummaries() - observeGroupSummaries() - } - - fun accept(action: HomeActions) { - when (action) { - is HomeActions.SelectRoom -> handleSelectRoom(action) - is HomeActions.SelectGroup -> handleSelectGroup(action) - is HomeActions.RoomDisplayed -> setState { copy(shouldOpenRoomDetail = false) } - is HomeActions.PermalinkClicked -> handlePermalinkClicked(action) - } - } - - // PRIVATE METHODS ***************************************************************************** - - private fun handlePermalinkClicked(action: HomeActions.PermalinkClicked) = withState { state -> - when (action.permalinkData) { - is PermalinkData.EventLink -> { - - } - is PermalinkData.RoomLink -> { - - } - is PermalinkData.GroupLink -> { - - } - is PermalinkData.UserLink -> { - - } - is PermalinkData.FallbackLink -> { - - } - } - } - - private fun handleSelectRoom(action: HomeActions.SelectRoom) = withState { state -> - if (state.selectedRoomId != action.roomSummary.roomId) { - roomSelectionRepository.saveLastSelectedRoom(action.roomSummary.roomId) - setState { copy(selectedRoomId = action.roomSummary.roomId, shouldOpenRoomDetail = true) } - } - } - - private fun handleSelectGroup(action: HomeActions.SelectGroup) = withState { state -> - if (state.selectedGroup?.groupId != action.groupSummary.groupId) { - setState { copy(selectedGroup = action.groupSummary) } - } else { - setState { copy(selectedGroup = null) } - } - } - - private fun observeRoomSummaries() { - session - .rx().liveRoomSummaries() - .execute { async -> - val summaries = async() - val directRooms = summaries?.filter { it.isDirect } ?: emptyList() - val groupRooms = summaries?.filter { !it.isDirect } ?: emptyList() - - val selectedRoomId = selectedRoomId - ?: roomSelectionRepository.lastSelectedRoom() - ?: directRooms.firstOrNull()?.roomId - ?: groupRooms.firstOrNull()?.roomId - - copy( - asyncRooms = async, - directRooms = directRooms, - groupRooms = groupRooms, - selectedRoomId = selectedRoomId - ) - } - } - - private fun observeGroupSummaries() { - session - .rx().liveGroupSummaries() - .execute { async -> - copy(asyncGroups = async) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/HomeViewState.kt b/app/src/main/java/im/vector/riotredesign/features/home/HomeViewState.kt deleted file mode 100644 index 49211b3d..00000000 --- a/app/src/main/java/im/vector/riotredesign/features/home/HomeViewState.kt +++ /dev/null @@ -1,18 +0,0 @@ -package im.vector.riotredesign.features.home - -import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState -import com.airbnb.mvrx.Uninitialized -import im.vector.matrix.android.api.session.group.model.GroupSummary -import im.vector.matrix.android.api.session.room.model.RoomSummary - -data class HomeViewState( - val asyncRooms: Async> = Uninitialized, - val directRooms: List = emptyList(), - val groupRooms: List = emptyList(), - val selectedRoomId: String? = null, - val selectedEventId: String? = null, - val shouldOpenRoomDetail: Boolean = true, - val asyncGroups: Async> = Uninitialized, - val selectedGroup: GroupSummary? = null -) : MvRxState \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/group/GroupListActions.kt b/app/src/main/java/im/vector/riotredesign/features/home/group/GroupListActions.kt new file mode 100644 index 00000000..92f758a7 --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/features/home/group/GroupListActions.kt @@ -0,0 +1,9 @@ +package im.vector.riotredesign.features.home.group + +import im.vector.matrix.android.api.session.group.model.GroupSummary + +sealed class GroupListActions { + + data class SelectGroup(val groupSummary: GroupSummary) : GroupListActions() + +} \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/group/GroupListFragment.kt b/app/src/main/java/im/vector/riotredesign/features/home/group/GroupListFragment.kt index 16e4bbd8..b4799151 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/group/GroupListFragment.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/group/GroupListFragment.kt @@ -6,14 +6,11 @@ import android.view.View import android.view.ViewGroup import com.airbnb.mvrx.Incomplete import com.airbnb.mvrx.Success -import com.airbnb.mvrx.activityViewModel +import com.airbnb.mvrx.fragmentViewModel import im.vector.matrix.android.api.session.group.model.GroupSummary import im.vector.riotredesign.R import im.vector.riotredesign.core.platform.RiotFragment import im.vector.riotredesign.core.platform.StateView -import im.vector.riotredesign.features.home.HomeActions -import im.vector.riotredesign.features.home.HomeViewModel -import im.vector.riotredesign.features.home.HomeViewState import kotlinx.android.synthetic.main.fragment_group_list.* class GroupListFragment : RiotFragment(), GroupSummaryController.Callback { @@ -24,7 +21,7 @@ class GroupListFragment : RiotFragment(), GroupSummaryController.Callback { } } - private val viewModel: HomeViewModel by activityViewModel() + private val viewModel: GroupListViewModel by fragmentViewModel() private lateinit var groupController: GroupSummaryController @@ -40,14 +37,14 @@ class GroupListFragment : RiotFragment(), GroupSummaryController.Callback { viewModel.subscribe { renderState(it) } } - private fun renderState(state: HomeViewState) { + private fun renderState(state: GroupListViewState) { when (state.asyncGroups) { is Incomplete -> renderLoading() is Success -> renderSuccess(state) } } - private fun renderSuccess(state: HomeViewState) { + private fun renderSuccess(state: GroupListViewState) { stateView.state = StateView.State.Content groupController.setData(state) } @@ -57,7 +54,7 @@ class GroupListFragment : RiotFragment(), GroupSummaryController.Callback { } override fun onGroupSelected(groupSummary: GroupSummary) { - viewModel.accept(HomeActions.SelectGroup(groupSummary)) + viewModel.accept(GroupListActions.SelectGroup(groupSummary)) } } \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/group/GroupListViewModel.kt b/app/src/main/java/im/vector/riotredesign/features/home/group/GroupListViewModel.kt new file mode 100644 index 00000000..ca88f95a --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/features/home/group/GroupListViewModel.kt @@ -0,0 +1,64 @@ +package im.vector.riotredesign.features.home.group + +import android.support.v4.app.FragmentActivity +import com.airbnb.mvrx.BaseMvRxViewModel +import com.airbnb.mvrx.MvRxViewModelFactory +import im.vector.matrix.android.api.Matrix +import im.vector.matrix.android.api.session.Session +import im.vector.matrix.rx.rx +import im.vector.riotredesign.core.platform.RiotViewModel +import org.koin.android.ext.android.get + +class GroupListViewModel(initialState: GroupListViewState, + private val selectedGroupHolder: SelectedGroupHolder, + private val session: Session +) : RiotViewModel(initialState) { + + companion object : MvRxViewModelFactory { + + @JvmStatic + override fun create(activity: FragmentActivity, state: GroupListViewState): GroupListViewModel { + val currentSession = Matrix.getInstance().currentSession + val selectedGroupHolder = activity.get() + return GroupListViewModel(state, selectedGroupHolder, currentSession) + } + } + + init { + observeGroupSummaries() + observeState() + } + + private fun observeState() { + subscribe { + selectedGroupHolder.setSelectedGroup(it.selectedGroup) + } + } + + fun accept(action: GroupListActions) { + when (action) { + is GroupListActions.SelectGroup -> handleSelectGroup(action) + } + } + + // PRIVATE METHODS ***************************************************************************** + + private fun handleSelectGroup(action: GroupListActions.SelectGroup) = withState { state -> + if (state.selectedGroup?.groupId != action.groupSummary.groupId) { + setState { copy(selectedGroup = action.groupSummary) } + } else { + setState { copy(selectedGroup = null) } + } + } + + + private fun observeGroupSummaries() { + session + .rx().liveGroupSummaries() + .execute { async -> + copy(asyncGroups = async) + } + } + + +} \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/group/GroupListViewState.kt b/app/src/main/java/im/vector/riotredesign/features/home/group/GroupListViewState.kt new file mode 100644 index 00000000..6594b5bd --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/features/home/group/GroupListViewState.kt @@ -0,0 +1,11 @@ +package im.vector.riotredesign.features.home.group + +import com.airbnb.mvrx.Async +import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.Uninitialized +import im.vector.matrix.android.api.session.group.model.GroupSummary + +data class GroupListViewState( + val asyncGroups: Async> = Uninitialized, + val selectedGroup: GroupSummary? = null +) : MvRxState \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/group/GroupSummaryController.kt b/app/src/main/java/im/vector/riotredesign/features/home/group/GroupSummaryController.kt index edd7c19e..3d34b98d 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/group/GroupSummaryController.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/group/GroupSummaryController.kt @@ -2,12 +2,11 @@ package im.vector.riotredesign.features.home.group import com.airbnb.epoxy.TypedEpoxyController import im.vector.matrix.android.api.session.group.model.GroupSummary -import im.vector.riotredesign.features.home.HomeViewState class GroupSummaryController(private val callback: Callback? = null -) : TypedEpoxyController() { +) : TypedEpoxyController() { - override fun buildModels(viewState: HomeViewState) { + override fun buildModels(viewState: GroupListViewState) { buildGroupModels(viewState.asyncGroups(), viewState.selectedGroup) } diff --git a/app/src/main/java/im/vector/riotredesign/features/home/group/SelectedGroupHolder.kt b/app/src/main/java/im/vector/riotredesign/features/home/group/SelectedGroupHolder.kt new file mode 100644 index 00000000..990e6fe8 --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/features/home/group/SelectedGroupHolder.kt @@ -0,0 +1,22 @@ +package im.vector.riotredesign.features.home.group + +import arrow.core.Option +import im.vector.matrix.android.api.session.group.model.GroupSummary +import io.reactivex.Observable +import io.reactivex.subjects.BehaviorSubject + +class SelectedGroupHolder { + + private val selectedGroupStream = BehaviorSubject.createDefault>(Option.empty()) + + fun setSelectedGroup(group: GroupSummary?) { + val optionValue = Option.fromNullable(group) + selectedGroupStream.onNext(optionValue) + } + + fun selectedGroup(): Observable> { + return selectedGroupStream.hide() + } + + +} \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt index 4ed0272e..3cd1258c 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt @@ -6,8 +6,6 @@ import android.support.v7.widget.LinearLayoutManager import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import com.airbnb.mvrx.Success -import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import im.vector.matrix.android.api.permalinks.PermalinkParser @@ -16,8 +14,6 @@ import im.vector.riotredesign.R import im.vector.riotredesign.core.platform.RiotFragment import im.vector.riotredesign.core.platform.ToolbarConfigurable import im.vector.riotredesign.features.home.AvatarRenderer -import im.vector.riotredesign.features.home.HomeActions -import im.vector.riotredesign.features.home.HomeViewModel import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController import kotlinx.android.parcel.Parcelize import kotlinx.android.synthetic.main.fragment_room_detail.* @@ -41,7 +37,6 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback { } } - private val homeViewModel: HomeViewModel by activityViewModel() private val roomDetailViewModel: RoomDetailViewModel by fragmentViewModel() private val roomDetailArgs: RoomDetailArgs by args() @@ -83,12 +78,8 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback { } private fun renderState(state: RoomDetailViewState) { - when (state.asyncTimeline) { - is Success -> renderTimeline(state.asyncTimeline()) - } - when (state.asyncRoomSummary) { - is Success -> renderRoomSummary(state.asyncRoomSummary()) - } + renderTimeline(state.asyncTimeline()) + renderRoomSummary(state.asyncRoomSummary()) } private fun renderRoomSummary(roomSummary: RoomSummary?) { @@ -113,7 +104,7 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback { override fun onUrlClicked(url: String) { val permalinkData = PermalinkParser.parse(url) - homeViewModel.accept(HomeActions.PermalinkClicked(permalinkData)) + } } diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt index 89c70a0c..de89ed16 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt @@ -1,17 +1,17 @@ package im.vector.riotredesign.features.home.room.detail import android.support.v4.app.FragmentActivity -import com.airbnb.mvrx.BaseMvRxViewModel import com.airbnb.mvrx.MvRxViewModelFactory import im.vector.matrix.android.api.Matrix import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.rx.rx +import im.vector.riotredesign.core.platform.RiotViewModel class RoomDetailViewModel(initialState: RoomDetailViewState, session: Session -) : BaseMvRxViewModel(initialState) { +) : RiotViewModel(initialState) { private val room = session.getRoom(initialState.roomId)!! private val roomId = initialState.roomId @@ -53,10 +53,9 @@ class RoomDetailViewModel(initialState: RoomDetailViewState, private fun observeTimeline() { room.rx().timeline(eventId) - .execute { async -> - copy(asyncTimeline = async) + .execute { asyncTimeline -> + copy(asyncTimeline = asyncTimeline) } } - } \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListActions.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListActions.kt new file mode 100644 index 00000000..17b8ec9b --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListActions.kt @@ -0,0 +1,11 @@ +package im.vector.riotredesign.features.home.room.list + +import im.vector.matrix.android.api.session.room.model.RoomSummary + +sealed class RoomListActions { + + data class SelectRoom(val roomSummary: RoomSummary) : RoomListActions() + + object RoomDisplayed : RoomListActions() + +} \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt index eeab8374..05729d40 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt @@ -13,10 +13,7 @@ import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.riotredesign.R import im.vector.riotredesign.core.platform.RiotFragment import im.vector.riotredesign.core.platform.StateView -import im.vector.riotredesign.features.home.HomeActions import im.vector.riotredesign.features.home.HomeNavigator -import im.vector.riotredesign.features.home.HomeViewModel -import im.vector.riotredesign.features.home.HomeViewState import kotlinx.android.synthetic.main.fragment_room_list.* import org.koin.android.ext.android.inject @@ -29,7 +26,7 @@ class RoomListFragment : RiotFragment(), RoomSummaryController.Callback { } private val homeNavigator by inject() - private val homeViewModel: HomeViewModel by activityViewModel() + private val homeViewModel: RoomListViewModel by activityViewModel() private lateinit var roomController: RoomSummaryController override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { @@ -44,19 +41,15 @@ class RoomListFragment : RiotFragment(), RoomSummaryController.Callback { homeViewModel.subscribe { renderState(it) } } - private fun renderState(state: HomeViewState) { + private fun renderState(state: RoomListViewState) { when (state.asyncRooms) { is Incomplete -> renderLoading() is Success -> renderSuccess(state) is Fail -> renderFailure(state.asyncRooms.error) } - if (state.shouldOpenRoomDetail && state.selectedRoomId != null) { - homeNavigator.openRoomDetail(state.selectedRoomId, null) - homeViewModel.accept(HomeActions.RoomDisplayed) - } } - private fun renderSuccess(state: HomeViewState) { + private fun renderSuccess(state: RoomListViewState) { if (state.asyncRooms().isNullOrEmpty()) { stateView.state = StateView.State.Empty(getString(R.string.room_list_empty)) } else { @@ -78,7 +71,8 @@ class RoomListFragment : RiotFragment(), RoomSummaryController.Callback { } override fun onRoomSelected(room: RoomSummary) { - homeViewModel.accept(HomeActions.SelectRoom(room)) + homeViewModel.accept(RoomListActions.SelectRoom(room)) + homeNavigator.openRoomDetail(room.roomId, null) } } \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewModel.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewModel.kt new file mode 100644 index 00000000..198e3fee --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewModel.kt @@ -0,0 +1,93 @@ +package im.vector.riotredesign.features.home.room.list + +import android.support.v4.app.FragmentActivity +import arrow.core.Option +import com.airbnb.mvrx.MvRxViewModelFactory +import im.vector.matrix.android.api.Matrix +import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.session.group.model.GroupSummary +import im.vector.matrix.android.api.session.room.model.RoomSummary +import im.vector.matrix.rx.rx +import im.vector.riotredesign.core.platform.RiotViewModel +import im.vector.riotredesign.features.home.group.SelectedGroupHolder +import io.reactivex.Observable +import io.reactivex.functions.BiFunction +import org.koin.android.ext.android.get + +class RoomListViewModel(initialState: RoomListViewState, + private val session: Session, + private val selectedGroupHolder: SelectedGroupHolder, + private val roomSelectionRepository: RoomSelectionRepository) + : RiotViewModel(initialState) { + + companion object : MvRxViewModelFactory { + + @JvmStatic + override fun create(activity: FragmentActivity, state: RoomListViewState): RoomListViewModel { + val currentSession = Matrix.getInstance().currentSession + val roomSelectionRepository = activity.get() + val selectedGroupHolder = activity.get() + return RoomListViewModel(state, currentSession, selectedGroupHolder, roomSelectionRepository) + } + } + + init { + observeRoomSummaries() + } + + fun accept(action: RoomListActions) { + when (action) { + is RoomListActions.SelectRoom -> handleSelectRoom(action) + } + } + + // PRIVATE METHODS ***************************************************************************** + + private fun handleSelectRoom(action: RoomListActions.SelectRoom) = withState { state -> + if (state.selectedRoomId != action.roomSummary.roomId) { + roomSelectionRepository.saveLastSelectedRoom(action.roomSummary.roomId) + setState { copy(selectedRoomId = action.roomSummary.roomId) } + } + } + + private fun observeRoomSummaries() { + Observable.combineLatest, Option, RoomSummaries>( + session.rx().liveRoomSummaries(), + selectedGroupHolder.selectedGroup(), + BiFunction { rooms, selectedGroupOption -> + val selectedGroup = selectedGroupOption.orNull() + + val filteredDirectRooms = rooms + .filter { it.isDirect } + .filter { + if (selectedGroup == null) { + true + } else { + it.otherMemberIds + .intersect(selectedGroup.userIds) + .isNotEmpty() + } + } + + val filteredGroupRooms = rooms + .filter { !it.isDirect } + .filter { + selectedGroup?.roomIds?.contains(it.roomId) ?: true + } + RoomSummaries(filteredDirectRooms, filteredGroupRooms) + } + ) + .execute { async -> + val summaries = async() + val selectedRoomId = selectedRoomId + ?: roomSelectionRepository.lastSelectedRoom() + ?: summaries?.directRooms?.firstOrNull()?.roomId + ?: summaries?.groupRooms?.firstOrNull()?.roomId + + copy( + asyncRooms = async, + selectedRoomId = selectedRoomId + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewState.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewState.kt new file mode 100644 index 00000000..2d183a55 --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewState.kt @@ -0,0 +1,20 @@ +package im.vector.riotredesign.features.home.room.list + +import com.airbnb.mvrx.Async +import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.Uninitialized +import im.vector.matrix.android.api.session.room.model.RoomSummary + +data class RoomListViewState( + val asyncRooms: Async = Uninitialized, + val selectedRoomId: String? = null +) : MvRxState + +data class RoomSummaries( + val directRooms: List, + val groupRooms: List +) + +fun RoomSummaries?.isNullOrEmpty(): Boolean { + return this == null || (directRooms.isEmpty() && groupRooms.isEmpty()) +} \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt index 998e59fd..30cdf7f7 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt @@ -2,17 +2,15 @@ package im.vector.riotredesign.features.home.room.list import com.airbnb.epoxy.TypedEpoxyController import im.vector.matrix.android.api.session.room.model.RoomSummary -import im.vector.riotredesign.features.home.HomeViewState class RoomSummaryController(private val callback: Callback? = null -) : TypedEpoxyController() { - +) : TypedEpoxyController() { private var isDirectRoomsExpanded = true private var isGroupRoomsExpanded = true - override fun buildModels(viewState: HomeViewState) { - + override fun buildModels(viewState: RoomListViewState) { + val roomSummaries = viewState.asyncRooms() RoomCategoryItem( title = "DIRECT MESSAGES", isExpanded = isDirectRoomsExpanded, @@ -25,16 +23,7 @@ class RoomSummaryController(private val callback: Callback? = null .addTo(this) if (isDirectRoomsExpanded) { - val filteredDirectRooms = viewState.directRooms.filter { - if (viewState.selectedGroup == null) { - true - } else { - it.otherMemberIds - .intersect(viewState.selectedGroup.userIds) - .isNotEmpty() - } - } - buildRoomModels(filteredDirectRooms, viewState.selectedRoomId) + buildRoomModels(roomSummaries?.directRooms ?: emptyList(), viewState.selectedRoomId) } RoomCategoryItem( @@ -49,10 +38,7 @@ class RoomSummaryController(private val callback: Callback? = null .addTo(this) if (isGroupRoomsExpanded) { - val filteredGroupRooms = viewState.groupRooms.filter { - viewState.selectedGroup?.roomIds?.contains(it.roomId) ?: true - } - buildRoomModels(filteredGroupRooms, viewState.selectedRoomId) + buildRoomModels(roomSummaries?.groupRooms ?: emptyList(), viewState.selectedRoomId) } } diff --git a/matrix-sdk-android-rx/build.gradle b/matrix-sdk-android-rx/build.gradle index c05a36e8..1b5ffeed 100644 --- a/matrix-sdk-android-rx/build.gradle +++ b/matrix-sdk-android-rx/build.gradle @@ -40,7 +40,7 @@ dependencies { implementation 'io.reactivex.rxjava2:rxandroid:2.0.2' // Paging - api "android.arch.paging:runtime:1.0.1" + implementation "android.arch.paging:runtime:1.0.1" testImplementation 'junit:junit:4.12' diff --git a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt index e588d078..a6104cfc 100644 --- a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt +++ b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt @@ -2,10 +2,10 @@ package im.vector.matrix.rx import android.arch.paging.PagedList import im.vector.matrix.android.api.session.events.model.EnrichedEvent -import im.vector.matrix.android.api.session.group.model.GroupSummary import im.vector.matrix.android.api.session.room.Room import im.vector.matrix.android.api.session.room.model.RoomSummary import io.reactivex.Observable +import io.reactivex.schedulers.Schedulers class RxRoom(private val room: Room) { @@ -15,6 +15,7 @@ class RxRoom(private val room: Room) { fun timeline(eventId: String? = null): Observable> { return room.timeline(eventId).asObservable() + .subscribeOn(Schedulers.io()) } } From fd06606c452f53c28b237d4489868e496d6e5528 Mon Sep 17 00:00:00 2001 From: ganfra Date: Sun, 30 Dec 2018 10:53:56 +0100 Subject: [PATCH 07/25] Room : load room members is totally off the main thread now --- .../internal/session/room/DefaultRoom.kt | 19 ++--------- .../internal/session/room/RoomModule.kt | 10 ++---- .../room/members/LoadRoomMembersTask.kt | 32 +++++++++++++------ 3 files changed, 27 insertions(+), 34 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt index 44ff5e80..6b373842 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt @@ -15,13 +15,11 @@ import im.vector.matrix.android.api.session.room.model.MyMembership import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.internal.database.mapper.asDomain -import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.di.MatrixKoinComponent import im.vector.matrix.android.internal.session.room.members.LoadRoomMembersTask -import im.vector.matrix.android.internal.session.sync.SyncTokenStore import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith import org.koin.core.parameter.parametersOf @@ -33,7 +31,6 @@ internal data class DefaultRoom( ) : Room, MatrixKoinComponent { private val loadRoomMembersTask by inject() - private val syncTokenStore by inject() private val monarchy by inject() private val timelineHolder by inject { parametersOf(roomId) } private val sendService by inject { parametersOf(roomId) } @@ -55,20 +52,8 @@ internal data class DefaultRoom( } override fun loadRoomMembersIfNeeded(): Cancelable { - return if (areAllMembersLoaded()) { - object : Cancelable {} - } else { - val token = syncTokenStore.getLastToken() - val params = LoadRoomMembersTask.Params(roomId, token, Membership.LEAVE) - loadRoomMembersTask.configureWith(params).executeBy(taskExecutor) - } - } - - private fun areAllMembersLoaded(): Boolean { - return monarchy - .fetchAllCopiedSync { RoomEntity.where(it, roomId) } - .firstOrNull() - ?.areAllMembersLoaded ?: false + val params = LoadRoomMembersTask.Params(roomId, Membership.LEAVE) + return loadRoomMembersTask.configureWith(params).executeBy(taskExecutor) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt index 3ecf6b77..6ecc63ee 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt @@ -9,13 +9,7 @@ import im.vector.matrix.android.internal.session.room.members.DefaultLoadRoomMem import im.vector.matrix.android.internal.session.room.members.LoadRoomMembersTask import im.vector.matrix.android.internal.session.room.members.RoomMemberExtractor import im.vector.matrix.android.internal.session.room.send.DefaultSendService -import im.vector.matrix.android.internal.session.room.timeline.DefaultGetContextOfEventTask -import im.vector.matrix.android.internal.session.room.timeline.DefaultPaginationTask -import im.vector.matrix.android.internal.session.room.timeline.DefaultTimelineHolder -import im.vector.matrix.android.internal.session.room.timeline.GetContextOfEventTask -import im.vector.matrix.android.internal.session.room.timeline.PaginationTask -import im.vector.matrix.android.internal.session.room.timeline.TimelineBoundaryCallback -import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEventPersistor +import im.vector.matrix.android.internal.session.room.timeline.* import im.vector.matrix.android.internal.util.PagingRequestHelper import org.koin.dsl.module.module import retrofit2.Retrofit @@ -32,7 +26,7 @@ class RoomModule { } scope(DefaultSession.SCOPE) { - DefaultLoadRoomMembersTask(get(), get()) as LoadRoomMembersTask + DefaultLoadRoomMembersTask(get(), get(), get()) as LoadRoomMembersTask } scope(DefaultSession.SCOPE) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/members/LoadRoomMembersTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/members/LoadRoomMembersTask.kt index aa0160bf..e62014b4 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/members/LoadRoomMembersTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/members/LoadRoomMembersTask.kt @@ -3,33 +3,40 @@ package im.vector.matrix.android.internal.session.room.members import arrow.core.Try import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.session.room.model.Membership -import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.database.helper.addStateEvents import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.room.RoomAPI +import im.vector.matrix.android.internal.session.sync.SyncTokenStore +import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.util.tryTransactionSync internal interface LoadRoomMembersTask : Task { data class Params( val roomId: String, - val streamToken: String?, val excludeMembership: Membership? = null ) } internal class DefaultLoadRoomMembersTask(private val roomAPI: RoomAPI, - private val monarchy: Monarchy + private val monarchy: Monarchy, + private val syncTokenStore: SyncTokenStore ) : LoadRoomMembersTask { override fun execute(params: LoadRoomMembersTask.Params): Try { - return executeRequest { - apiCall = roomAPI.getMembers(params.roomId, null, null, params.excludeMembership?.value) - }.flatMap { response -> - insertInDb(response, params.roomId) - }.map { true } + return if (areAllMembersAlreadyLoaded(params.roomId)) { + Try.just(true) + } else { + //TODO use this token + val lastToken = syncTokenStore.getLastToken() + executeRequest { + apiCall = roomAPI.getMembers(params.roomId, null, null, params.excludeMembership?.value) + }.flatMap { response -> + insertInDb(response, params.roomId) + }.map { true } + } } private fun insertInDb(response: RoomMembersResponse, roomId: String): Try { @@ -37,7 +44,7 @@ internal class DefaultLoadRoomMembersTask(private val roomAPI: RoomAPI, .tryTransactionSync { realm -> // We ignore all the already known members val roomEntity = RoomEntity.where(realm, roomId).findFirst() - ?: throw IllegalStateException("You shouldn't use this method without a room") + ?: throw IllegalStateException("You shouldn't use this method without a room") val roomMembers = RoomMembers(realm, roomId).getLoaded() val eventsToInsert = response.roomMemberEvents.filter { !roomMembers.containsKey(it.stateKey) } @@ -48,4 +55,11 @@ internal class DefaultLoadRoomMembersTask(private val roomAPI: RoomAPI, .map { response } } + private fun areAllMembersAlreadyLoaded(roomId: String): Boolean { + return monarchy + .fetchAllCopiedSync { RoomEntity.where(it, roomId) } + .firstOrNull() + ?.areAllMembersLoaded ?: false + } + } \ No newline at end of file From 51000c4711a116ca814a93d71ecf50c22f1ce9d6 Mon Sep 17 00:00:00 2001 From: ganfra Date: Sun, 30 Dec 2018 13:02:20 +0100 Subject: [PATCH 08/25] Re-add opening detail when opening app --- .../features/home/HomeActivity.kt | 30 +++++--------- .../riotredesign/features/home/HomeModule.kt | 4 +- .../features/home/HomeNavigator.kt | 39 +++++++++++++++++-- .../home/room/detail/RoomDetailFragment.kt | 4 +- .../home/room/list/RoomListFragment.kt | 3 ++ 5 files changed, 53 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt b/app/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt index c75cc051..2fd2b653 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt @@ -15,17 +15,18 @@ import im.vector.riotredesign.core.platform.OnBackPressed import im.vector.riotredesign.core.platform.RiotActivity import im.vector.riotredesign.core.platform.ToolbarConfigurable import im.vector.riotredesign.features.home.room.detail.LoadingRoomDetailFragment -import im.vector.riotredesign.features.home.room.detail.RoomDetailArgs -import im.vector.riotredesign.features.home.room.detail.RoomDetailFragment import kotlinx.android.synthetic.main.activity_home.* +import org.koin.android.ext.android.inject import org.koin.standalone.StandAloneContext.loadKoinModules -import timber.log.Timber -class HomeActivity : RiotActivity(), HomeNavigator, ToolbarConfigurable { +class HomeActivity : RiotActivity(), ToolbarConfigurable { + + private val homeNavigator by inject() override fun onCreate(savedInstanceState: Bundle?) { loadKoinModules(listOf(HomeModule(this).definition)) + homeNavigator.activity = this super.onCreate(savedInstanceState) setContentView(R.layout.activity_home) if (savedInstanceState == null) { @@ -36,6 +37,11 @@ class HomeActivity : RiotActivity(), HomeNavigator, ToolbarConfigurable { } } + override fun onDestroy() { + homeNavigator.activity = null + super.onDestroy() + } + override fun configure(toolbar: Toolbar) { setSupportActionBar(toolbar) supportActionBar?.setHomeButtonEnabled(true) @@ -84,22 +90,6 @@ class HomeActivity : RiotActivity(), HomeNavigator, ToolbarConfigurable { return false } - // HomeNavigator ******************************************************************************* - - override fun openRoomDetail(roomId: String, eventId: String?) { - val args = RoomDetailArgs(roomId, eventId) - val roomDetailFragment = RoomDetailFragment.newInstance(args) - drawerLayout.closeDrawer(Gravity.LEFT) - replaceFragment(roomDetailFragment, R.id.homeDetailFragmentContainer) - } - - override fun openGroupDetail(groupId: String) { - Timber.v("Open group detail $groupId") - } - - override fun openUserDetail(userId: String) { - Timber.v("Open user detail $userId") - } companion object { fun newIntent(context: Context): Intent { diff --git a/app/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt b/app/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt index 75491d0a..f9a6a263 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt @@ -23,8 +23,8 @@ class HomeModule(private val homeActivity: HomeActivity) { TextItemFactory() } - factory { - homeActivity as HomeNavigator + single { + HomeNavigator() } factory { (roomId: String) -> diff --git a/app/src/main/java/im/vector/riotredesign/features/home/HomeNavigator.kt b/app/src/main/java/im/vector/riotredesign/features/home/HomeNavigator.kt index c306f5c5..91a64011 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/HomeNavigator.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/HomeNavigator.kt @@ -1,11 +1,42 @@ package im.vector.riotredesign.features.home -interface HomeNavigator { +import android.view.Gravity +import im.vector.riotredesign.R +import im.vector.riotredesign.core.extensions.replaceFragment +import im.vector.riotredesign.features.home.room.detail.RoomDetailArgs +import im.vector.riotredesign.features.home.room.detail.RoomDetailFragment +import kotlinx.android.synthetic.main.activity_home.* +import timber.log.Timber - fun openRoomDetail(roomId: String, eventId: String?) +class HomeNavigator { - fun openGroupDetail(groupId: String) + var activity: HomeActivity? = null - fun openUserDetail(userId: String) + private var currentRoomId: String? = null + + fun openRoomDetail(roomId: String, eventId: String?) { + if (isRoomOpened(roomId)) { + return + } + currentRoomId = roomId + activity?.let { + val args = RoomDetailArgs(roomId, eventId) + val roomDetailFragment = RoomDetailFragment.newInstance(args) + it.drawerLayout?.closeDrawer(Gravity.LEFT) + it.replaceFragment(roomDetailFragment, R.id.homeDetailFragmentContainer) + } + } + + fun openGroupDetail(groupId: String) { + Timber.v("Open group detail $groupId") + } + + fun openUserDetail(userId: String) { + Timber.v("Open user detail $userId") + } + + fun isRoomOpened(roomId: String): Boolean { + return currentRoomId == roomId + } } \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt index 3cd1258c..6d7c0571 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt @@ -78,7 +78,9 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback { } private fun renderState(state: RoomDetailViewState) { - renderTimeline(state.asyncTimeline()) + if (state.asyncTimeline.complete) { + renderTimeline(state.asyncTimeline()) + } renderRoomSummary(state.asyncRoomSummary()) } diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt index 05729d40..35b4d272 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt @@ -50,6 +50,9 @@ class RoomListFragment : RiotFragment(), RoomSummaryController.Callback { } private fun renderSuccess(state: RoomListViewState) { + if (state.selectedRoomId != null) { + homeNavigator.openRoomDetail(state.selectedRoomId, null) + } if (state.asyncRooms().isNullOrEmpty()) { stateView.state = StateView.State.Empty(getString(R.string.room_list_empty)) } else { From e9737dff75c070975ff4d860908d301497557fc9 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 3 Jan 2019 17:10:02 +0100 Subject: [PATCH 09/25] Add roomId to EventEntity as it makes queries a lot faster and fixes performance issues. --- .../riotredesign/core/extensions/Fragment.kt | 8 +++--- .../features/home/HomeDrawerFragment.kt | 6 ++--- .../main/java/im/vector/matrix/rx/RxRoom.kt | 2 -- .../database/helper/ChunkEntityHelper.kt | 25 ++++++++++--------- .../database/helper/RoomEntityHelper.kt | 8 +++--- .../internal/database/mapper/EventMapper.kt | 14 ++++++++--- .../internal/database/model/EventEntity.kt | 5 ++-- .../database/query/EventEntityQueries.kt | 11 ++------ .../session/room/send/DefaultSendService.kt | 11 +++----- .../room/timeline/TokenChunkEventPersistor.kt | 15 ++++------- .../internal/session/sync/RoomSyncHandler.kt | 16 +++++------- 11 files changed, 52 insertions(+), 69 deletions(-) diff --git a/app/src/main/java/im/vector/riotredesign/core/extensions/Fragment.kt b/app/src/main/java/im/vector/riotredesign/core/extensions/Fragment.kt index 05921312..3d1bb9d2 100644 --- a/app/src/main/java/im/vector/riotredesign/core/extensions/Fragment.kt +++ b/app/src/main/java/im/vector/riotredesign/core/extensions/Fragment.kt @@ -10,6 +10,10 @@ fun Fragment.replaceFragment(fragment: Fragment, frameId: Int) { fragmentManager?.inTransaction { replace(frameId, fragment) } } +fun Fragment.addFragmentToBackstack(fragment: Fragment, frameId: Int, tag: String? = null) { + fragmentManager?.inTransaction { replace(frameId, fragment).addToBackStack(tag) } +} + fun Fragment.addChildFragment(fragment: Fragment, frameId: Int) { childFragmentManager.inTransaction { add(frameId, fragment) } } @@ -18,10 +22,6 @@ fun Fragment.replaceChildFragment(fragment: Fragment, frameId: Int) { childFragmentManager.inTransaction { replace(frameId, fragment) } } -fun Fragment.addFragmentToBackstack(fragment: Fragment, frameId: Int, tag: String? = null) { - fragmentManager?.inTransaction { replace(frameId, fragment).addToBackStack(tag) } -} - fun Fragment.addChildFragmentToBackstack(fragment: Fragment, frameId: Int, tag: String? = null) { childFragmentManager.inTransaction { replace(frameId, fragment).addToBackStack(tag) } } \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/HomeDrawerFragment.kt b/app/src/main/java/im/vector/riotredesign/features/home/HomeDrawerFragment.kt index 1ef0bcb8..a0ccd361 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/HomeDrawerFragment.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/HomeDrawerFragment.kt @@ -5,7 +5,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import im.vector.riotredesign.R -import im.vector.riotredesign.core.extensions.replaceFragment +import im.vector.riotredesign.core.extensions.replaceChildFragment import im.vector.riotredesign.core.platform.RiotFragment import im.vector.riotredesign.features.home.group.GroupListFragment import im.vector.riotredesign.features.home.room.list.RoomListFragment @@ -27,9 +27,9 @@ class HomeDrawerFragment : RiotFragment() { super.onActivityCreated(savedInstanceState) if (savedInstanceState == null) { val groupListFragment = GroupListFragment.newInstance() - replaceFragment(groupListFragment, R.id.groupListFragmentContainer) + replaceChildFragment(groupListFragment, R.id.groupListFragmentContainer) val roomListFragment = RoomListFragment.newInstance() - replaceFragment(roomListFragment, R.id.roomListFragmentContainer) + replaceChildFragment(roomListFragment, R.id.roomListFragmentContainer) } } diff --git a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt index a6104cfc..86992356 100644 --- a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt +++ b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt @@ -5,7 +5,6 @@ import im.vector.matrix.android.api.session.events.model.EnrichedEvent import im.vector.matrix.android.api.session.room.Room import im.vector.matrix.android.api.session.room.model.RoomSummary import io.reactivex.Observable -import io.reactivex.schedulers.Schedulers class RxRoom(private val room: Room) { @@ -15,7 +14,6 @@ class RxRoom(private val room: Room) { fun timeline(eventId: String? = null): Observable> { return room.timeline(eventId).asObservable() - .subscribeOn(Schedulers.io()) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt index f8d8207a..519e69e1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt @@ -2,7 +2,8 @@ package im.vector.matrix.android.internal.database.helper 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.internal.database.mapper.asEntity +import im.vector.matrix.android.internal.database.mapper.toEntity +import im.vector.matrix.android.internal.database.mapper.updateWith import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.model.EventEntityFields @@ -44,13 +45,14 @@ internal fun ChunkEntity.merge(chunkToMerge: ChunkEntity, } } -internal fun ChunkEntity.addAll(events: List, +internal fun ChunkEntity.addAll(roomId: String, + events: List, direction: PaginationDirection, stateIndexOffset: Int = 0, isUnlinked: Boolean = false) { events.forEach { event -> - add(event, direction, stateIndexOffset, isUnlinked) + add(roomId, event, direction, stateIndexOffset, isUnlinked) } } @@ -58,11 +60,13 @@ internal fun ChunkEntity.updateDisplayIndexes() { events.forEachIndexed { index, eventEntity -> eventEntity.displayIndex = index } } -internal fun ChunkEntity.add(event: Event, +internal fun ChunkEntity.add(roomId: String, + event: Event, direction: PaginationDirection, stateIndexOffset: Int = 0, isUnlinked: Boolean = false) { - add(event.asEntity(), direction, stateIndexOffset, isUnlinked) + + add(event.toEntity(roomId), direction, stateIndexOffset, isUnlinked) } internal fun ChunkEntity.add(eventEntity: EventEntity, @@ -76,7 +80,6 @@ internal fun ChunkEntity.add(eventEntity: EventEntity, if (eventEntity.eventId.isEmpty() || events.fastContains(eventEntity.eventId)) { return } - var currentStateIndex = lastStateIndex(direction, defaultValue = stateIndexOffset) if (direction == PaginationDirection.FORWARDS && EventType.isStateEvent(eventEntity.type)) { currentStateIndex += 1 @@ -86,16 +89,14 @@ internal fun ChunkEntity.add(eventEntity: EventEntity, currentStateIndex -= 1 } } - - eventEntity.stateIndex = currentStateIndex - eventEntity.isUnlinked = isUnlinked + eventEntity.updateWith(currentStateIndex, isUnlinked) val position = if (direction == PaginationDirection.FORWARDS) 0 else this.events.size events.add(position, eventEntity) } internal fun ChunkEntity.lastStateIndex(direction: PaginationDirection, defaultValue: Int = 0): Int { return when (direction) { - PaginationDirection.FORWARDS -> events.where().sort(EventEntityFields.STATE_INDEX, Sort.DESCENDING).findFirst()?.stateIndex - PaginationDirection.BACKWARDS -> events.where().sort(EventEntityFields.STATE_INDEX, Sort.ASCENDING).findFirst()?.stateIndex - } ?: defaultValue + PaginationDirection.FORWARDS -> events.where().sort(EventEntityFields.STATE_INDEX, Sort.DESCENDING).findFirst()?.stateIndex + PaginationDirection.BACKWARDS -> events.where().sort(EventEntityFields.STATE_INDEX, Sort.ASCENDING).findFirst()?.stateIndex + } ?: defaultValue } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/RoomEntityHelper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/RoomEntityHelper.kt index 4df958b4..ea4c5a7e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/RoomEntityHelper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/RoomEntityHelper.kt @@ -1,7 +1,8 @@ package im.vector.matrix.android.internal.database.helper import im.vector.matrix.android.api.session.events.model.Event -import im.vector.matrix.android.internal.database.mapper.asEntity +import im.vector.matrix.android.internal.database.mapper.toEntity +import im.vector.matrix.android.internal.database.mapper.updateWith import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.model.RoomEntity @@ -28,9 +29,8 @@ internal fun RoomEntity.addStateEvents(stateEvents: List, if (event.eventId == null) { return@forEach } - val eventEntity = event.asEntity() - eventEntity.stateIndex = stateIndex - eventEntity.isUnlinked = isUnlinked + val eventEntity = event.toEntity(roomId) + eventEntity.updateWith(stateIndex, isUnlinked) untimelinedStateEvents.add(eventEntity) } } \ No newline at end of file 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 5d1669b9..d46d0f93 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 @@ -8,9 +8,10 @@ import im.vector.matrix.android.internal.database.model.EventEntity internal object EventMapper { - fun map(event: Event): EventEntity { + fun map(event: Event, roomId: String): EventEntity { val eventEntity = EventEntity() eventEntity.eventId = event.eventId ?: "" + eventEntity.roomId = event.roomId ?: roomId eventEntity.content = ContentMapper.map(event.content) val resolvedPrevContent = event.prevContent ?: event.unsignedData?.prevContent eventEntity.prevContent = ContentMapper.map(resolvedPrevContent) @@ -32,19 +33,24 @@ internal object EventMapper { originServerTs = eventEntity.originServerTs, sender = eventEntity.sender, stateKey = eventEntity.stateKey, - roomId = null, + roomId = eventEntity.roomId, unsignedData = UnsignedData(eventEntity.age), redacts = eventEntity.redacts ) } +} +internal fun EventEntity.updateWith(stateIndex: Int, isUnlinked: Boolean) { + this.stateIndex = stateIndex + this.isUnlinked = isUnlinked } internal fun EventEntity.asDomain(): Event { return EventMapper.map(this) } -internal fun Event.asEntity(): EventEntity { - return EventMapper.map(this) +internal fun Event.toEntity(roomId: String): EventEntity { + return EventMapper.map(this, roomId) } + 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 46451ee9..2ac7c0ff 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 @@ -8,6 +8,7 @@ import java.util.* internal open class EventEntity(@PrimaryKey var localId: String = UUID.randomUUID().toString(), var eventId: String = "", + var roomId: String = "", var type: String = "", var content: String? = null, var prevContent: String? = null, @@ -27,9 +28,7 @@ internal open class EventEntity(@PrimaryKey var localId: String = UUID.randomUUI BOTH } - companion object { - const val DEFAULT_STATE_INDEX = Int.MIN_VALUE - } + companion object @LinkingObjects("events") val chunk: RealmResults? = null diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt index 7b582287..2d846d8b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt @@ -1,10 +1,8 @@ package im.vector.matrix.android.internal.database.query -import im.vector.matrix.android.internal.database.model.ChunkEntityFields import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.model.EventEntity.LinkFilterMode.* import im.vector.matrix.android.internal.database.model.EventEntityFields -import im.vector.matrix.android.internal.database.model.RoomEntityFields import io.realm.Realm import io.realm.RealmList import io.realm.RealmQuery @@ -22,11 +20,7 @@ internal fun EventEntity.Companion.where(realm: Realm, linkFilterMode: EventEntity.LinkFilterMode = LINKED_ONLY): RealmQuery { val query = realm.where() if (roomId != null) { - query.beginGroup() - .equalTo("${EventEntityFields.CHUNK}.${ChunkEntityFields.ROOM}.${RoomEntityFields.ROOM_ID}", roomId) - .or() - .equalTo("${EventEntityFields.ROOM}.${RoomEntityFields.ROOM_ID}", roomId) - .endGroup() + query.equalTo(EventEntityFields.ROOM_ID, roomId) } if (type != null) { query.equalTo(EventEntityFields.TYPE, type) @@ -69,7 +63,6 @@ internal fun RealmList.find(eventId: String): EventEntity? { return this.where().equalTo(EventEntityFields.EVENT_ID, eventId).findFirst() } -internal fun RealmList. - fastContains(eventId: String): Boolean { +internal fun RealmList.fastContains(eventId: String): Boolean { return this.find(eventId) != null } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt index 1623df07..7e96cc27 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt @@ -1,11 +1,6 @@ package im.vector.matrix.android.internal.session.room.send -import androidx.work.BackoffPolicy -import androidx.work.Constraints -import androidx.work.ExistingWorkPolicy -import androidx.work.NetworkType -import androidx.work.OneTimeWorkRequestBuilder -import androidx.work.WorkManager +import androidx.work.* import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.session.events.model.Event @@ -37,8 +32,8 @@ internal class DefaultSendService(private val roomId: String, monarchy.tryTransactionAsync { realm -> val chunkEntity = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId) - ?: return@tryTransactionAsync - chunkEntity.add(event, PaginationDirection.FORWARDS) + ?: return@tryTransactionAsync + chunkEntity.add(roomId, event, PaginationDirection.FORWARDS) chunkEntity.updateDisplayIndexes() } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEventPersistor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEventPersistor.kt index bf4e9b49..cd2b4868 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEventPersistor.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEventPersistor.kt @@ -2,12 +2,7 @@ package im.vector.matrix.android.internal.session.room.timeline import arrow.core.Try import com.zhuinden.monarchy.Monarchy -import im.vector.matrix.android.internal.database.helper.addAll -import im.vector.matrix.android.internal.database.helper.addOrUpdate -import im.vector.matrix.android.internal.database.helper.addStateEvents -import im.vector.matrix.android.internal.database.helper.deleteOnCascade -import im.vector.matrix.android.internal.database.helper.isUnlinked -import im.vector.matrix.android.internal.database.helper.merge +import im.vector.matrix.android.internal.database.helper.* import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.query.create @@ -26,7 +21,7 @@ internal class TokenChunkEventPersistor(private val monarchy: Monarchy) { return monarchy .tryTransactionSync { realm -> val roomEntity = RoomEntity.where(realm, roomId).findFirst() - ?: throw IllegalStateException("You shouldn't use this method without a room") + ?: throw IllegalStateException("You shouldn't use this method without a room") val nextToken: String? val prevToken: String? @@ -46,13 +41,13 @@ internal class TokenChunkEventPersistor(private val monarchy: Monarchy) { var currentChunk = if (direction == PaginationDirection.FORWARDS) { prevChunk?.apply { this.nextToken = nextToken } - ?: ChunkEntity.create(realm, prevToken, nextToken) + ?: ChunkEntity.create(realm, prevToken, nextToken) } else { nextChunk?.apply { this.prevToken = prevToken } - ?: ChunkEntity.create(realm, prevToken, nextToken) + ?: ChunkEntity.create(realm, prevToken, nextToken) } - currentChunk.addAll(receivedChunk.events, direction, isUnlinked = currentChunk.isUnlinked()) + currentChunk.addAll(roomId, receivedChunk.events, direction, isUnlinked = currentChunk.isUnlinked()) // Then we merge chunks if needed if (currentChunk != prevChunk && prevChunk != null) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt index d5305c04..b71160b2 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt @@ -15,11 +15,7 @@ import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection -import im.vector.matrix.android.internal.session.sync.model.InvitedRoomSync -import im.vector.matrix.android.internal.session.sync.model.RoomSync -import im.vector.matrix.android.internal.session.sync.model.RoomSyncEphemeral -import im.vector.matrix.android.internal.session.sync.model.RoomSyncSummary -import im.vector.matrix.android.internal.session.sync.model.RoomsSyncResponse +import im.vector.matrix.android.internal.session.sync.model.* import io.realm.Realm import io.realm.kotlin.createObject @@ -45,9 +41,9 @@ internal class RoomSyncHandler(private val monarchy: Monarchy, private fun handleRoomSync(realm: Realm, handlingStrategy: HandlingStrategy) { val rooms = when (handlingStrategy) { - is HandlingStrategy.JOINED -> handlingStrategy.data.map { handleJoinedRoom(realm, it.key, it.value) } + is HandlingStrategy.JOINED -> handlingStrategy.data.map { handleJoinedRoom(realm, it.key, it.value) } is HandlingStrategy.INVITED -> handlingStrategy.data.map { handleInvitedRoom(realm, it.key, it.value) } - is HandlingStrategy.LEFT -> handlingStrategy.data.map { handleLeftRoom(it.key, it.value) } + is HandlingStrategy.LEFT -> handlingStrategy.data.map { handleLeftRoom(it.key, it.value) } } realm.insertOrUpdate(rooms) } @@ -57,7 +53,7 @@ internal class RoomSyncHandler(private val monarchy: Monarchy, roomSync: RoomSync): RoomEntity { val roomEntity = RoomEntity.where(realm, roomId).findFirst() - ?: realm.createObject(roomId) + ?: realm.createObject(roomId) if (roomEntity.membership == MyMembership.INVITED) { roomEntity.chunks.deleteAllFromRealm() @@ -138,7 +134,7 @@ internal class RoomSyncHandler(private val monarchy: Monarchy, lastChunk?.isLast = false chunkEntity.isLast = true - chunkEntity.addAll(eventList, PaginationDirection.FORWARDS, stateIndexOffset) + chunkEntity.addAll(roomId, eventList, PaginationDirection.FORWARDS, stateIndexOffset) return chunkEntity } @@ -147,7 +143,7 @@ internal class RoomSyncHandler(private val monarchy: Monarchy, roomSummary: RoomSyncSummary) { val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst() - ?: RoomSummaryEntity(roomId) + ?: RoomSummaryEntity(roomId) if (roomSummary.heroes.isNotEmpty()) { roomSummaryEntity.heroes.clear() From 84645c17c8a9c40495358b481df5622151a79820 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 3 Jan 2019 21:25:51 +0100 Subject: [PATCH 10/25] Makes tests compile --- .../matrix/android/auth/AuthenticatorTest.kt | 3 +- .../session/room/timeline/ChunkEntityTest.kt | 57 ++++++++++++------- .../session/room/timeline/RoomDataHelper.kt | 2 +- .../room/timeline/TimelineHolderTest.kt | 5 +- .../im/vector/matrix/android/api/Matrix.kt | 6 +- 5 files changed, 45 insertions(+), 28 deletions(-) diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/auth/AuthenticatorTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/auth/AuthenticatorTest.kt index e0c1c0f5..fb0c6853 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/auth/AuthenticatorTest.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/auth/AuthenticatorTest.kt @@ -6,7 +6,6 @@ import android.support.test.runner.AndroidJUnit4 import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.InstrumentedTest import im.vector.matrix.android.OkReplayRuleChainNoActivity -import im.vector.matrix.android.api.MatrixOptions import im.vector.matrix.android.api.auth.Authenticator import im.vector.matrix.android.internal.auth.AuthModule import im.vector.matrix.android.internal.di.MatrixModule @@ -26,7 +25,7 @@ internal class AuthenticatorTest : InstrumentedTest, KoinTest { init { Monarchy.init(context()) - val matrixModule = MatrixModule(MatrixOptions(context())).definition + val matrixModule = MatrixModule(context()).definition val networkModule = NetworkModule().definition val authModule = AuthModule().definition loadKoinModules(listOf(matrixModule, networkModule, authModule)) diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/ChunkEntityTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/ChunkEntityTest.kt index 137ec790..181f0c1e 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/ChunkEntityTest.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/ChunkEntityTest.kt @@ -4,11 +4,7 @@ import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.InstrumentedTest 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.internal.database.helper.add -import im.vector.matrix.android.internal.database.helper.addAll -import im.vector.matrix.android.internal.database.helper.isUnlinked -import im.vector.matrix.android.internal.database.helper.lastStateIndex -import im.vector.matrix.android.internal.database.helper.merge +import im.vector.matrix.android.internal.database.helper.* import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection import io.realm.Realm @@ -39,7 +35,7 @@ internal class ChunkEntityTest : InstrumentedTest { monarchy.runTransactionSync { realm -> val chunk: ChunkEntity = realm.createObject() val fakeEvent = createFakeEvent(false) - chunk.add(fakeEvent, PaginationDirection.FORWARDS) + chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS) chunk.events.size shouldEqual 1 } } @@ -49,8 +45,8 @@ internal class ChunkEntityTest : InstrumentedTest { monarchy.runTransactionSync { realm -> val chunk: ChunkEntity = realm.createObject() val fakeEvent = createFakeEvent(false) - chunk.add(fakeEvent, PaginationDirection.FORWARDS) - chunk.add(fakeEvent, PaginationDirection.FORWARDS) + chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS) + chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS) chunk.events.size shouldEqual 1 } } @@ -60,7 +56,7 @@ internal class ChunkEntityTest : InstrumentedTest { monarchy.runTransactionSync { realm -> val chunk: ChunkEntity = realm.createObject() val fakeEvent = createFakeEvent(true) - chunk.add(fakeEvent, PaginationDirection.FORWARDS) + chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS) chunk.lastStateIndex(PaginationDirection.FORWARDS) shouldEqual 1 } } @@ -70,7 +66,7 @@ internal class ChunkEntityTest : InstrumentedTest { monarchy.runTransactionSync { realm -> val chunk: ChunkEntity = realm.createObject() val fakeEvent = createFakeEvent(false) - chunk.add(fakeEvent, PaginationDirection.FORWARDS) + chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS) chunk.lastStateIndex(PaginationDirection.FORWARDS) shouldEqual 0 } } @@ -81,7 +77,7 @@ internal class ChunkEntityTest : InstrumentedTest { val chunk: ChunkEntity = realm.createObject() val fakeEvents = createFakeListOfEvents(30) val numberOfStateEvents = fakeEvents.filter { it.isStateEvent() }.size - chunk.addAll(fakeEvents, PaginationDirection.FORWARDS) + chunk.addAll("roomId", fakeEvents, PaginationDirection.FORWARDS) chunk.lastStateIndex(PaginationDirection.FORWARDS) shouldEqual numberOfStateEvents } } @@ -94,7 +90,7 @@ internal class ChunkEntityTest : InstrumentedTest { val numberOfStateEvents = fakeEvents.filter { it.isStateEvent() }.size val lastIsState = fakeEvents.last().isStateEvent() val expectedStateIndex = if (lastIsState) -numberOfStateEvents + 1 else -numberOfStateEvents - chunk.addAll(fakeEvents, PaginationDirection.BACKWARDS) + chunk.addAll("roomId", fakeEvents, PaginationDirection.BACKWARDS) chunk.lastStateIndex(PaginationDirection.BACKWARDS) shouldEqual expectedStateIndex } } @@ -104,20 +100,37 @@ internal class ChunkEntityTest : InstrumentedTest { monarchy.runTransactionSync { realm -> val chunk1: ChunkEntity = realm.createObject() val chunk2: ChunkEntity = realm.createObject() - chunk1.addAll(createFakeListOfEvents(30), PaginationDirection.BACKWARDS) - chunk2.addAll(createFakeListOfEvents(30), PaginationDirection.BACKWARDS) + chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS) + chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS) chunk1.merge(chunk2, PaginationDirection.BACKWARDS) chunk1.events.size shouldEqual 60 } } + @Test + fun merge_shouldAddOnlyDifferentEvents_whenMergingBackward() { + monarchy.runTransactionSync { realm -> + val chunk1: ChunkEntity = realm.createObject() + val chunk2: ChunkEntity = realm.createObject() + val eventsForChunk1 = createFakeListOfEvents(30) + val eventsForChunk2 = eventsForChunk1 + createFakeListOfEvents(10) + chunk1.isLast = true + chunk2.isLast = false + chunk1.addAll("roomId", eventsForChunk1, PaginationDirection.FORWARDS) + chunk2.addAll("roomId", eventsForChunk2, PaginationDirection.BACKWARDS) + chunk1.merge(chunk2, PaginationDirection.BACKWARDS) + chunk1.events.size shouldEqual 40 + chunk1.isLast.shouldBeTrue() + } + } + @Test fun merge_shouldEventsBeLinked_whenMergingLinkedWithUnlinked() { monarchy.runTransactionSync { realm -> val chunk1: ChunkEntity = realm.createObject() val chunk2: ChunkEntity = realm.createObject() - chunk1.addAll(createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true) - chunk2.addAll(createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = false) + chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true) + chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = false) chunk1.merge(chunk2, PaginationDirection.BACKWARDS) chunk1.isUnlinked().shouldBeFalse() } @@ -128,8 +141,8 @@ internal class ChunkEntityTest : InstrumentedTest { monarchy.runTransactionSync { realm -> val chunk1: ChunkEntity = realm.createObject() val chunk2: ChunkEntity = realm.createObject() - chunk1.addAll(createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true) - chunk2.addAll(createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true) + chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true) + chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true) chunk1.merge(chunk2, PaginationDirection.BACKWARDS) chunk1.isUnlinked().shouldBeTrue() } @@ -142,8 +155,8 @@ internal class ChunkEntityTest : InstrumentedTest { val chunk2: ChunkEntity = realm.createObject() val prevToken = "prev_token" chunk1.prevToken = prevToken - chunk1.addAll(createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true) - chunk2.addAll(createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true) + chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true) + chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true) chunk1.merge(chunk2, PaginationDirection.FORWARDS) chunk1.prevToken shouldEqual prevToken } @@ -156,8 +169,8 @@ internal class ChunkEntityTest : InstrumentedTest { val chunk2: ChunkEntity = realm.createObject() val nextToken = "next_token" chunk1.nextToken = nextToken - chunk1.addAll(createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true) - chunk2.addAll(createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true) + chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true) + chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true) chunk1.merge(chunk2, PaginationDirection.BACKWARDS) chunk1.nextToken shouldEqual nextToken } diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/RoomDataHelper.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/RoomDataHelper.kt index 2385d794..c7279bf2 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/RoomDataHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/RoomDataHelper.kt @@ -34,7 +34,7 @@ object RoomDataHelper { prevToken = Random.nextLong(System.currentTimeMillis()).toString() isLast = true } - chunkEntity.addAll(eventList, PaginationDirection.FORWARDS) + chunkEntity.addAll("roomId", eventList, PaginationDirection.FORWARDS) roomEntity.addOrUpdate(chunkEntity) } } diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelineHolderTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelineHolderTest.kt index fc8731ab..38b465d8 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelineHolderTest.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelineHolderTest.kt @@ -6,10 +6,11 @@ import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.InstrumentedTest import im.vector.matrix.android.LiveDataTestObserver import im.vector.matrix.android.api.thread.MainThreadExecutor -import im.vector.matrix.android.internal.task.TaskExecutor +import im.vector.matrix.android.internal.session.room.members.RoomMemberExtractor import im.vector.matrix.android.internal.session.room.timeline.DefaultTimelineHolder import im.vector.matrix.android.internal.session.room.timeline.TimelineBoundaryCallback import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEventPersistor +import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.util.PagingRequestHelper import im.vector.matrix.android.testCoroutineDispatchers import io.realm.Realm @@ -43,7 +44,7 @@ internal class TimelineHolderTest : InstrumentedTest { val boundaryCallback = TimelineBoundaryCallback(roomId, taskExecutor, paginationTask, monarchy, PagingRequestHelper(MainThreadExecutor())) RoomDataHelper.fakeInitialSync(monarchy, roomId) - val timelineHolder = DefaultTimelineHolder(roomId, monarchy, taskExecutor, boundaryCallback, getContextOfEventTask) + val timelineHolder = DefaultTimelineHolder(roomId, monarchy, taskExecutor, boundaryCallback, getContextOfEventTask, RoomMemberExtractor(monarchy, roomId)) val timelineObserver = LiveDataTestObserver.test(timelineHolder.timeline()) timelineObserver.awaitNextValue().assertHasValue() var pagedList = timelineObserver.value() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/Matrix.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/Matrix.kt index 714ac0f7..467329c8 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/Matrix.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/Matrix.kt @@ -12,6 +12,7 @@ import im.vector.matrix.android.internal.di.MatrixModule import im.vector.matrix.android.internal.di.NetworkModule import im.vector.matrix.android.internal.util.BackgroundDetectionObserver import org.koin.standalone.inject +import java.util.concurrent.atomic.AtomicBoolean class Matrix private constructor(context: Context) : MatrixKoinComponent { @@ -40,9 +41,12 @@ class Matrix private constructor(context: Context) : MatrixKoinComponent { companion object { private lateinit var instance: Matrix + private val isInit = AtomicBoolean(false) internal fun initialize(context: Context) { - instance = Matrix(context.applicationContext) + if (isInit.compareAndSet(false, true)) { + instance = Matrix(context.applicationContext) + } } fun getInstance(): Matrix { From 14fc75a5f3f0f222c443049cf8cb16f9b8b6d6d0 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 3 Jan 2019 21:26:39 +0100 Subject: [PATCH 11/25] Add to backstack when navigating from permalink in room details --- .../riotredesign/features/home/HomeModule.kt | 4 ++++ .../features/home/HomeNavigator.kt | 23 ++++++++++++++++--- .../home/room/detail/RoomDetailFragment.kt | 19 ++++++++------- .../home/room/detail/timeline/MessageItem.kt | 4 +++- 4 files changed, 38 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt b/app/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt index f9a6a263..86a414f1 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt @@ -35,5 +35,9 @@ class HomeModule(private val homeActivity: HomeActivity) { SelectedGroupHolder() } + single { + HomePermalinkHandler(get()) + } + } } \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/HomeNavigator.kt b/app/src/main/java/im/vector/riotredesign/features/home/HomeNavigator.kt index 91a64011..41f61688 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/HomeNavigator.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/HomeNavigator.kt @@ -1,7 +1,9 @@ package im.vector.riotredesign.features.home +import android.support.v4.app.FragmentManager import android.view.Gravity import im.vector.riotredesign.R +import im.vector.riotredesign.core.extensions.addFragmentToBackstack import im.vector.riotredesign.core.extensions.replaceFragment import im.vector.riotredesign.features.home.room.detail.RoomDetailArgs import im.vector.riotredesign.features.home.room.detail.RoomDetailFragment @@ -14,8 +16,11 @@ class HomeNavigator { private var currentRoomId: String? = null - fun openRoomDetail(roomId: String, eventId: String?) { - if (isRoomOpened(roomId)) { + fun openRoomDetail(roomId: String, + eventId: String?, + addToBackstack: Boolean = false) { + Timber.v("Open room detail $roomId - $eventId - $addToBackstack") + if (!addToBackstack && isRoomOpened(roomId)) { return } currentRoomId = roomId @@ -23,7 +28,12 @@ class HomeNavigator { val args = RoomDetailArgs(roomId, eventId) val roomDetailFragment = RoomDetailFragment.newInstance(args) it.drawerLayout?.closeDrawer(Gravity.LEFT) - it.replaceFragment(roomDetailFragment, R.id.homeDetailFragmentContainer) + if (addToBackstack) { + it.addFragmentToBackstack(roomDetailFragment, R.id.homeDetailFragmentContainer, roomId) + } else { + clearBackStack(it.supportFragmentManager) + it.replaceFragment(roomDetailFragment, R.id.homeDetailFragmentContainer) + } } } @@ -39,4 +49,11 @@ class HomeNavigator { return currentRoomId == roomId } + private fun clearBackStack(fragmentManager: FragmentManager) { + if (fragmentManager.backStackEntryCount > 0) { + val first = fragmentManager.getBackStackEntryAt(0) + fragmentManager.popBackStack(first.id, FragmentManager.POP_BACK_STACK_INCLUSIVE) + } + } + } \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt index 6d7c0571..3eb67ccf 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt @@ -8,17 +8,18 @@ import android.view.View import android.view.ViewGroup import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel -import im.vector.matrix.android.api.permalinks.PermalinkParser import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.riotredesign.R import im.vector.riotredesign.core.platform.RiotFragment import im.vector.riotredesign.core.platform.ToolbarConfigurable import im.vector.riotredesign.features.home.AvatarRenderer +import im.vector.riotredesign.features.home.HomePermalinkHandler import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController import kotlinx.android.parcel.Parcelize import kotlinx.android.synthetic.main.fragment_room_detail.* import org.koin.android.ext.android.inject import org.koin.core.parameter.parametersOf +import timber.log.Timber @Parcelize data class RoomDetailArgs( @@ -41,6 +42,7 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback { private val roomDetailArgs: RoomDetailArgs by args() private val timelineEventController by inject { parametersOf(roomDetailArgs.roomId) } + private val homePermalinkHandler by inject() private lateinit var scrollOnNewMessageCallback: ScrollOnNewMessageCallback override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { @@ -70,16 +72,18 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback { private fun setupRecyclerView() { val layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, true) - scrollOnNewMessageCallback = ScrollOnNewMessageCallback(layoutManager) + //scrollOnNewMessageCallback = ScrollOnNewMessageCallback(layoutManager) recyclerView.layoutManager = layoutManager - timelineEventController.addModelBuildListener { it.dispatchTo(scrollOnNewMessageCallback) } + //timelineEventController.addModelBuildListener { it.dispatchTo(scrollOnNewMessageCallback) } recyclerView.setController(timelineEventController) timelineEventController.callback = this } private fun renderState(state: RoomDetailViewState) { - if (state.asyncTimeline.complete) { - renderTimeline(state.asyncTimeline()) + Timber.v("Render state") + val timeline = state.asyncTimeline() + if (timeline != null) { + renderTimeline(timeline) } renderRoomSummary(state.asyncRoomSummary()) } @@ -98,15 +102,14 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback { } private fun renderTimeline(timeline: Timeline?) { - scrollOnNewMessageCallback.hasBeenUpdated.set(true) + //scrollOnNewMessageCallback.hasBeenUpdated.set(true) timelineEventController.timeline = timeline } // TimelineEventController.Callback ************************************************************ override fun onUrlClicked(url: String) { - val permalinkData = PermalinkParser.parse(url) - + homePermalinkHandler.launch(url) } } diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageItem.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageItem.kt index 44a9b2f6..4fe08c18 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageItem.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageItem.kt @@ -1,10 +1,11 @@ package im.vector.riotredesign.features.home.room.detail.timeline +import android.text.util.Linkify import android.view.View import android.widget.ImageView import android.widget.TextView -import im.vector.matrix.android.api.permalinks.MatrixPermalinkSpan import im.vector.matrix.android.api.permalinks.MatrixLinkify +import im.vector.matrix.android.api.permalinks.MatrixPermalinkSpan import im.vector.riotredesign.R import im.vector.riotredesign.core.epoxy.KotlinModel import im.vector.riotredesign.features.home.AvatarRenderer @@ -30,6 +31,7 @@ data class MessageItem( onUrlClickedListener?.invoke(url) } }) + Linkify.addLinks(messageView, Linkify.ALL) if (showInformation) { avatarImageView.visibility = View.VISIBLE memberNameView.visibility = View.VISIBLE From 281c25e66d22f3b6731ab6449d7962baf2ed7d10 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 3 Jan 2019 21:31:45 +0100 Subject: [PATCH 12/25] Add to backstack when navigating from permalink in room details #2 --- .../features/home/HomePermalinkHandler.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/im/vector/riotredesign/features/home/HomePermalinkHandler.kt b/app/src/main/java/im/vector/riotredesign/features/home/HomePermalinkHandler.kt index b4809b16..d7088504 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/HomePermalinkHandler.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/HomePermalinkHandler.kt @@ -17,16 +17,16 @@ class HomePermalinkHandler(private val navigator: HomeNavigator) { } val permalinkData = PermalinkParser.parse(deepLink) when (permalinkData) { - is PermalinkData.EventLink -> { - navigator.openRoomDetail(permalinkData.roomIdOrAlias, permalinkData.eventId) + is PermalinkData.EventLink -> { + navigator.openRoomDetail(permalinkData.roomIdOrAlias, permalinkData.eventId, true) } - is PermalinkData.RoomLink -> { - navigator.openRoomDetail(permalinkData.roomIdOrAlias, null) + is PermalinkData.RoomLink -> { + navigator.openRoomDetail(permalinkData.roomIdOrAlias, null, true) } - is PermalinkData.GroupLink -> { + is PermalinkData.GroupLink -> { navigator.openGroupDetail(permalinkData.groupId) } - is PermalinkData.UserLink -> { + is PermalinkData.UserLink -> { navigator.openUserDetail(permalinkData.userId) } is PermalinkData.FallbackLink -> { From d288fb7c9c5aa2f27f130be554f1302b5d7a0ebc Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 7 Jan 2019 11:39:26 +0100 Subject: [PATCH 13/25] Timeline : improve scrolling performances --- .../home/room/detail/RoomDetailFragment.kt | 2 ++ .../home/room/detail/timeline/MessageItem.kt | 12 ++--------- .../detail/timeline/MessageItemFactory.kt | 20 ++++++++++++++++--- .../android/api/permalinks/MatrixLinkify.kt | 2 +- .../room/timeline/DefaultTimelineHolder.kt | 4 ++-- 5 files changed, 24 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt index 3eb67ccf..812b64b3 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt @@ -75,6 +75,8 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback { //scrollOnNewMessageCallback = ScrollOnNewMessageCallback(layoutManager) recyclerView.layoutManager = layoutManager //timelineEventController.addModelBuildListener { it.dispatchTo(scrollOnNewMessageCallback) } + recyclerView.setHasFixedSize(true) + recyclerView.setItemViewCacheSize(20) recyclerView.setController(timelineEventController) timelineEventController.callback = this } diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageItem.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageItem.kt index 4fe08c18..fb06ef9b 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageItem.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageItem.kt @@ -1,11 +1,9 @@ package im.vector.riotredesign.features.home.room.detail.timeline -import android.text.util.Linkify import android.view.View import android.widget.ImageView import android.widget.TextView import im.vector.matrix.android.api.permalinks.MatrixLinkify -import im.vector.matrix.android.api.permalinks.MatrixPermalinkSpan import im.vector.riotredesign.R import im.vector.riotredesign.core.epoxy.KotlinModel import im.vector.riotredesign.features.home.AvatarRenderer @@ -15,8 +13,7 @@ data class MessageItem( val time: CharSequence? = null, val avatarUrl: String?, val memberName: CharSequence? = null, - val showInformation: Boolean = true, - val onUrlClickedListener: ((url: String) -> Unit)? = null + val showInformation: Boolean = true ) : KotlinModel(R.layout.item_event_message) { private val avatarImageView by bind(R.id.messageAvatarImageView) @@ -26,12 +23,7 @@ data class MessageItem( override fun bind() { messageView.text = message - MatrixLinkify.addLinks(messageView, object : MatrixPermalinkSpan.Callback { - override fun onUrlClicked(url: String) { - onUrlClickedListener?.invoke(url) - } - }) - Linkify.addLinks(messageView, Linkify.ALL) + MatrixLinkify.addLinkMovementMethod(messageView) if (showInformation) { avatarImageView.visibility = View.VISIBLE memberNameView.visibility = View.VISIBLE diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageItemFactory.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageItemFactory.kt index 407a4e7a..5e5d1b1e 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageItemFactory.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageItemFactory.kt @@ -1,5 +1,9 @@ package im.vector.riotredesign.features.home.room.detail.timeline +import android.text.SpannableStringBuilder +import android.text.util.Linkify +import im.vector.matrix.android.api.permalinks.MatrixLinkify +import im.vector.matrix.android.api.permalinks.MatrixPermalinkSpan import im.vector.matrix.android.api.session.events.model.EnrichedEvent import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.MessageContent @@ -25,14 +29,24 @@ class MessageItemFactory(private val timelineDateFormatter: TimelineDateFormatte if (addDaySeparator || nextRoomMember != roomMember) { messagesDisplayedWithInformation.add(event.root.eventId) } + + val message = messageContent.body?.let { + val spannable = SpannableStringBuilder(it) + MatrixLinkify.addLinks(spannable, object : MatrixPermalinkSpan.Callback { + override fun onUrlClicked(url: String) { + callback?.onUrlClicked(url) + } + }) + Linkify.addLinks(spannable, Linkify.ALL) + spannable + } val showInformation = messagesDisplayedWithInformation.contains(event.root.eventId) return MessageItem( - message = messageContent.body, + message = message, avatarUrl = roomMember.avatarUrl, showInformation = showInformation, time = timelineDateFormatter.formatMessageHour(date), - memberName = roomMember.displayName ?: event.root.sender, - onUrlClickedListener = { callback?.onUrlClicked(it) } + memberName = roomMember.displayName ?: event.root.sender ) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/MatrixLinkify.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/MatrixLinkify.kt index 66c1d32e..d30a7109 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/MatrixLinkify.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/MatrixLinkify.kt @@ -58,7 +58,7 @@ object MatrixLinkify { } - private fun addLinkMovementMethod(textView: TextView) { + fun addLinkMovementMethod(textView: TextView) { val movementMethod = textView.movementMethod if (movementMethod == null || movementMethod !is LinkMovementMethod) { if (textView.linksClickable) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineHolder.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineHolder.kt index 3e03b4fb..8db811ef 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineHolder.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineHolder.kt @@ -15,7 +15,7 @@ import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.session.room.members.RoomMemberExtractor import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith -import im.vector.matrix.android.internal.util.tryTransactionSync +import im.vector.matrix.android.internal.util.tryTransactionAsync import io.realm.Realm import io.realm.RealmQuery @@ -55,7 +55,7 @@ internal class DefaultTimelineHolder(private val roomId: String, } private fun clearUnlinkedEvents() { - monarchy.tryTransactionSync { realm -> + monarchy.tryTransactionAsync { realm -> val unlinkedEvents = EventEntity .where(realm, roomId = roomId) .equalTo(EventEntityFields.IS_UNLINKED, true) From 7669a94a647f6057bbca02e097fe49818afa2bf5 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 7 Jan 2019 12:12:22 +0100 Subject: [PATCH 14/25] Introduce retry on task executor and use it for pagination --- .../room/timeline/TimelineBoundaryCallback.kt | 5 ++-- .../android/internal/task/ConfigurableTask.kt | 9 ++++++ .../android/internal/task/TaskExecutor.kt | 28 +++++++++++++++++-- .../android/internal/util/CoroutineRetry.kt | 24 ---------------- 4 files changed, 38 insertions(+), 28 deletions(-) delete mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/CoroutineRetry.kt diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineBoundaryCallback.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineBoundaryCallback.kt index 3c9cb1bb..ba574386 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineBoundaryCallback.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineBoundaryCallback.kt @@ -4,10 +4,10 @@ import android.arch.paging.PagedList import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.session.events.model.EnrichedEvent -import im.vector.matrix.android.internal.task.TaskExecutor -import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.query.findAllIncludingEvents +import im.vector.matrix.android.internal.task.TaskExecutor +import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.util.PagingRequestHelper import java.util.* @@ -53,6 +53,7 @@ internal class TimelineBoundaryCallback(private val roomId: String, limit = limit) paginationTask.configureWith(params) + .enableRetry() .dispatchTo(object : MatrixCallback { override fun onSuccess(data: TokenChunkEvent) { requestCallback.recordSuccess() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/ConfigurableTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/ConfigurableTask.kt index e5cc7b33..faf2fb3a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/ConfigurableTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/ConfigurableTask.kt @@ -13,6 +13,7 @@ internal data class ConfigurableTask( val params: PARAMS, val callbackThread: TaskThread = TaskThread.MAIN, val executionThread: TaskThread = TaskThread.IO, + val retryCount: Int = 0, val callback: MatrixCallback = object : MatrixCallback {} ) : Task { @@ -33,10 +34,18 @@ internal data class ConfigurableTask( return copy(callback = matrixCallback) } + fun enableRetry(retryCount: Int = Int.MAX_VALUE): ConfigurableTask { + return copy(retryCount = retryCount) + } + fun executeBy(taskExecutor: TaskExecutor): Cancelable { return taskExecutor.execute(this) } + override fun toString(): String { + return task.javaClass.name + } + } 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 fce0cd79..67354642 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 @@ -1,9 +1,11 @@ package im.vector.matrix.android.internal.task +import arrow.core.Try import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.internal.util.CancelableCoroutine import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import timber.log.Timber @@ -15,14 +17,36 @@ internal class TaskExecutor(private val coroutineDispatchers: MatrixCoroutineDis val job = GlobalScope.launch(task.callbackThread.toDispatcher()) { val resultOrFailure = withContext(task.executionThread.toDispatcher()) { - Timber.v("Executing ${task.javaClass} on ${Thread.currentThread().name}") - task.execute(task.params) + Timber.v("Executing $task on ${Thread.currentThread().name}") + retry(task.retryCount) { + task.execute(task.params) + } } resultOrFailure.fold({ task.callback.onFailure(it) }, { task.callback.onSuccess(it) }) } return CancelableCoroutine(job) } + private suspend fun retry( + times: Int = Int.MAX_VALUE, + initialDelay: Long = 100, // 0.1 second + maxDelay: Long = 10_000, // 10 second + factor: Double = 2.0, + block: suspend () -> Try): Try { + + var currentDelay = initialDelay + repeat(times - 1) { + val blockResult = block() + if (blockResult.isSuccess()) { + return blockResult + } else { + delay(currentDelay) + currentDelay = (currentDelay * factor).toLong().coerceAtMost(maxDelay) + } + } + return block() + } + private fun TaskThread.toDispatcher() = when (this) { TaskThread.MAIN -> coroutineDispatchers.main TaskThread.COMPUTATION -> coroutineDispatchers.computation diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/CoroutineRetry.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/CoroutineRetry.kt deleted file mode 100644 index 28520a69..00000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/CoroutineRetry.kt +++ /dev/null @@ -1,24 +0,0 @@ -package im.vector.matrix.android.internal.util - -import arrow.core.Try -import kotlinx.coroutines.delay - -suspend fun retry( - times: Int = Int.MAX_VALUE, - initialDelay: Long = 100, // 0.1 second - maxDelay: Long = 10_000, // 10 second - factor: Double = 2.0, - block: suspend () -> Try): Try { - - var currentDelay = initialDelay - repeat(times - 1) { - val blockResult = block() - if (blockResult.isSuccess()) { - return blockResult - } else { - delay(currentDelay) - currentDelay = (currentDelay * factor).toLong().coerceAtMost(maxDelay) - } - } - return block() -} \ No newline at end of file From f5d64a5707303379278554d3e5fbbe5e718defa2 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 7 Jan 2019 15:20:56 +0100 Subject: [PATCH 15/25] RoomSummary : clean some old code --- .../database/model/RoomSummaryEntity.kt | 19 +++++++++---------- .../query/RoomSummaryEntityQueries.kt | 6 ------ 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomSummaryEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomSummaryEntity.kt index de2a3b04..e3a12a53 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomSummaryEntity.kt @@ -5,16 +5,15 @@ import io.realm.RealmObject import io.realm.annotations.PrimaryKey internal open class RoomSummaryEntity(@PrimaryKey var roomId: String = "", - var displayName: String? = "", - var avatarUrl: String? = "", - var topic: String? = "", - var lastMessage: EventEntity? = null, - var heroes: RealmList = RealmList(), - var joinedMembersCount: Int? = 0, - var invitedMembersCount: Int? = 0, - var isDirect: Boolean = false, - var isLatestSelected: Boolean = false, - var otherMemberIds: RealmList = RealmList() + var displayName: String? = "", + var avatarUrl: String? = "", + var topic: String? = "", + var lastMessage: EventEntity? = null, + var heroes: RealmList = RealmList(), + var joinedMembersCount: Int? = 0, + var invitedMembersCount: Int? = 0, + var isDirect: Boolean = false, + var otherMemberIds: RealmList = RealmList() ) : RealmObject() { companion object diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/RoomSummaryEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/RoomSummaryEntityQueries.kt index 90cc0684..36418420 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/RoomSummaryEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/RoomSummaryEntityQueries.kt @@ -13,9 +13,3 @@ internal fun RoomSummaryEntity.Companion.where(realm: Realm, roomId: String? = n } return query } - -internal fun RoomSummaryEntity.Companion.lastSelected(realm: Realm): RoomSummaryEntity? { - return realm.where() - .equalTo(RoomSummaryEntityFields.IS_LATEST_SELECTED, true) - .findFirst() -} From 1269715b5cdc3e4d50467511c03f6fb044c7c06d Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 7 Jan 2019 19:38:36 +0100 Subject: [PATCH 16/25] Timeline : introduce timeline data class to allow listening for isLoadingForward and isLoadingBackward --- .../home/room/detail/RoomDetailFragment.kt | 29 +++++----- .../home/room/detail/RoomDetailViewModel.kt | 5 +- .../home/room/detail/RoomDetailViewState.kt | 7 +-- .../timeline/TimelineEventController.kt | 58 +++++++++++-------- .../main/java/im/vector/matrix/rx/RxRoom.kt | 5 +- .../matrix/android/api/session/room/Room.kt | 3 +- .../api/session/room/TimelineHolder.kt | 11 ---- .../api/session/room/timeline/TimelineData.kt | 10 ++++ .../session/room/timeline/TimelineService.kt | 9 +++ .../internal/session/room/DefaultRoom.kt | 9 +-- .../internal/session/room/RoomModule.kt | 12 +++- .../room/timeline/DefaultPaginationTask.kt | 9 +-- ...ineHolder.kt => DefaultTimelineService.kt} | 30 ++++++---- .../room/timeline/TimelineBoundaryCallback.kt | 46 +++++++++++---- .../android/internal/util/LiveDataUtils.kt | 38 ++++++++++++ .../internal/util/PagingRequestHelper.java | 5 ++ 16 files changed, 192 insertions(+), 94 deletions(-) delete mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/TimelineHolder.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineData.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineService.kt rename matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/{DefaultTimelineHolder.kt => DefaultTimelineService.kt} (73%) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/LiveDataUtils.kt diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt index 812b64b3..fe1f3798 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt @@ -6,9 +6,9 @@ import android.support.v7.widget.LinearLayoutManager import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import com.airbnb.mvrx.Success import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel -import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.riotredesign.R import im.vector.riotredesign.core.platform.RiotFragment import im.vector.riotredesign.core.platform.ToolbarConfigurable @@ -19,7 +19,6 @@ import kotlinx.android.parcel.Parcelize import kotlinx.android.synthetic.main.fragment_room_detail.* import org.koin.android.ext.android.inject import org.koin.core.parameter.parametersOf -import timber.log.Timber @Parcelize data class RoomDetailArgs( @@ -72,7 +71,7 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback { private fun setupRecyclerView() { val layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, true) - //scrollOnNewMessageCallback = ScrollOnNewMessageCallback(layoutManager) + scrollOnNewMessageCallback = ScrollOnNewMessageCallback(layoutManager) recyclerView.layoutManager = layoutManager //timelineEventController.addModelBuildListener { it.dispatchTo(scrollOnNewMessageCallback) } recyclerView.setHasFixedSize(true) @@ -82,16 +81,19 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback { } private fun renderState(state: RoomDetailViewState) { - Timber.v("Render state") - val timeline = state.asyncTimeline() - if (timeline != null) { - renderTimeline(timeline) - } - renderRoomSummary(state.asyncRoomSummary()) + renderRoomSummary(state) + renderTimeline(state) } - private fun renderRoomSummary(roomSummary: RoomSummary?) { - roomSummary?.let { + private fun renderTimeline(state: RoomDetailViewState) { + when (state.asyncTimelineData) { + is Success -> timelineEventController.update(state.asyncTimelineData()) + + } + } + + private fun renderRoomSummary(state: RoomDetailViewState) { + state.asyncRoomSummary()?.let { toolbarTitleView.text = it.displayName AvatarRenderer.render(it, toolbarAvatarImageView) if (it.topic.isNotEmpty()) { @@ -103,11 +105,6 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback { } } - private fun renderTimeline(timeline: Timeline?) { - //scrollOnNewMessageCallback.hasBeenUpdated.set(true) - timelineEventController.timeline = timeline - } - // TimelineEventController.Callback ************************************************************ override fun onUrlClicked(url: String) { diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt index de89ed16..05a68813 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt @@ -53,9 +53,10 @@ class RoomDetailViewModel(initialState: RoomDetailViewState, private fun observeTimeline() { room.rx().timeline(eventId) - .execute { asyncTimeline -> - copy(asyncTimeline = asyncTimeline) + .execute { timelineData -> + copy(asyncTimelineData= timelineData) } } + } \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewState.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewState.kt index 83f04851..1bd9e3fc 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewState.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewState.kt @@ -1,19 +1,16 @@ package im.vector.riotredesign.features.home.room.detail -import android.arch.paging.PagedList import com.airbnb.mvrx.Async import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.Uninitialized -import im.vector.matrix.android.api.session.events.model.EnrichedEvent import im.vector.matrix.android.api.session.room.model.RoomSummary - -typealias Timeline = PagedList +import im.vector.matrix.android.api.session.room.timeline.TimelineData data class RoomDetailViewState( val roomId: String, val eventId: String?, val asyncRoomSummary: Async = Uninitialized, - val asyncTimeline: Async = Uninitialized + val asyncTimelineData: Async = Uninitialized ) : MvRxState { constructor(args: RoomDetailArgs) : this(roomId = args.roomId, eventId = args.eventId) diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt index ecda3c02..88649561 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt @@ -5,9 +5,9 @@ import com.airbnb.epoxy.EpoxyAsyncUtil import com.airbnb.epoxy.EpoxyController import im.vector.matrix.android.api.session.events.model.EnrichedEvent import im.vector.matrix.android.api.session.events.model.EventType +import im.vector.matrix.android.api.session.room.timeline.TimelineData import im.vector.riotredesign.core.extensions.localDateTime import im.vector.riotredesign.features.home.LoadingItemModel_ -import im.vector.riotredesign.features.home.room.detail.Timeline class TimelineEventController(private val roomId: String, private val messageItemFactory: MessageItemFactory, @@ -36,28 +36,38 @@ class TimelineEventController(private val roomId: String, } } - private var snapshotList: List? = emptyList() - var timeline: Timeline? = null - set(value) { - field?.removeWeakCallback(pagedListCallback) - field = value - field?.addWeakCallback(null, pagedListCallback) - buildSnapshotList() - } - + private var snapshotList: List = emptyList() + private var timelineData: TimelineData? = null var callback: Callback? = null - override fun buildModels() { - buildModels(snapshotList) + fun update(timelineData: TimelineData?) { + timelineData?.events?.removeWeakCallback(pagedListCallback) + this.timelineData = timelineData + timelineData?.events?.addWeakCallback(null, pagedListCallback) + buildSnapshotList() } - private fun buildModels(data: List?) { - if (data.isNullOrEmpty()) { + override fun buildModels() { + buildModelsWith( + snapshotList, + timelineData?.isLoadingForward ?: false, + timelineData?.isLoadingBackward ?: false + ) + } + + private fun buildModelsWith(events: List, + isLoadingForward: Boolean, + isLoadingBackward: Boolean) { + if (events.isEmpty()) { return } - for (index in 0 until data.size) { - val event = data[index] ?: continue - val nextEvent = if (index + 1 < data.size) data[index + 1] else null + LoadingItemModel_() + .id(roomId + "forward_loading_item") + .addIf(isLoadingForward, this) + + for (index in 0 until events.size) { + val event = events[index] ?: continue + val nextEvent = if (index + 1 < events.size) events[index + 1] else null val date = event.root.localDateTime() val nextDate = nextEvent?.root?.localDateTime() @@ -65,10 +75,13 @@ class TimelineEventController(private val roomId: String, val item = when (event.root.type) { EventType.MESSAGE -> messageItemFactory.create(event, nextEvent, addDaySeparator, date, callback) - else -> textItemFactory.create(event) + else -> textItemFactory.create(event) } + item - ?.onBind { timeline?.loadAround(index) } + ?.onBind { + timelineData?.events?.loadAround(index) + } ?.id(event.localId) ?.addTo(this) @@ -78,15 +91,14 @@ class TimelineEventController(private val roomId: String, } } - //It's a hack at the moment - val isLastEvent = data.last()?.root?.type == EventType.STATE_ROOM_CREATE LoadingItemModel_() .id(roomId + "backward_loading_item") - .addIf(!isLastEvent, this) + .addIf(isLoadingBackward, this) + } private fun buildSnapshotList() { - snapshotList = timeline?.snapshot() ?: emptyList() + snapshotList = timelineData?.events?.snapshot() ?: emptyList() requestModelBuild() } diff --git a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt index 86992356..510d7e01 100644 --- a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt +++ b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt @@ -1,9 +1,8 @@ package im.vector.matrix.rx -import android.arch.paging.PagedList -import im.vector.matrix.android.api.session.events.model.EnrichedEvent import im.vector.matrix.android.api.session.room.Room import im.vector.matrix.android.api.session.room.model.RoomSummary +import im.vector.matrix.android.api.session.room.timeline.TimelineData import io.reactivex.Observable class RxRoom(private val room: Room) { @@ -12,7 +11,7 @@ class RxRoom(private val room: Room) { return room.roomSummary.asObservable() } - fun timeline(eventId: String? = null): Observable> { + fun timeline(eventId: String? = null): Observable { return room.timeline(eventId).asObservable() } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt index 412131f1..8e17a0a1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt @@ -3,9 +3,10 @@ package im.vector.matrix.android.api.session.room import android.arch.lifecycle.LiveData import im.vector.matrix.android.api.session.room.model.MyMembership import im.vector.matrix.android.api.session.room.model.RoomSummary +import im.vector.matrix.android.api.session.room.timeline.TimelineService import im.vector.matrix.android.api.util.Cancelable -interface Room : TimelineHolder, SendService { +interface Room : TimelineService, SendService { val roomId: String diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/TimelineHolder.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/TimelineHolder.kt deleted file mode 100644 index fc01a47b..00000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/TimelineHolder.kt +++ /dev/null @@ -1,11 +0,0 @@ -package im.vector.matrix.android.api.session.room - -import android.arch.lifecycle.LiveData -import android.arch.paging.PagedList -import im.vector.matrix.android.api.session.events.model.EnrichedEvent - -interface TimelineHolder { - - fun timeline(eventId: String? = null): LiveData> - -} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineData.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineData.kt new file mode 100644 index 00000000..556c95f9 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineData.kt @@ -0,0 +1,10 @@ +package im.vector.matrix.android.api.session.room.timeline + +import android.arch.paging.PagedList +import im.vector.matrix.android.api.session.events.model.EnrichedEvent + +data class TimelineData( + val events: PagedList, + val isLoadingForward: Boolean = false, + val isLoadingBackward: Boolean = false +) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineService.kt new file mode 100644 index 00000000..d1069dad --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineService.kt @@ -0,0 +1,9 @@ +package im.vector.matrix.android.api.session.room.timeline + +import android.arch.lifecycle.LiveData + +interface TimelineService { + + fun timeline(eventId: String? = null): LiveData + +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt index 6b373842..4cf645a5 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt @@ -9,10 +9,11 @@ import im.vector.matrix.android.api.session.events.model.EnrichedEvent import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.room.Room import im.vector.matrix.android.api.session.room.SendService -import im.vector.matrix.android.api.session.room.TimelineHolder +import im.vector.matrix.android.api.session.room.timeline.TimelineService import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.MyMembership import im.vector.matrix.android.api.session.room.model.RoomSummary +import im.vector.matrix.android.api.session.room.timeline.TimelineData import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.model.RoomSummaryEntity @@ -32,7 +33,7 @@ internal data class DefaultRoom( private val loadRoomMembersTask by inject() private val monarchy by inject() - private val timelineHolder by inject { parametersOf(roomId) } + private val timelineService by inject { parametersOf(roomId) } private val sendService by inject { parametersOf(roomId) } private val taskExecutor by inject() @@ -47,8 +48,8 @@ internal data class DefaultRoom( } } - override fun timeline(eventId: String?): LiveData> { - return timelineHolder.timeline(eventId) + override fun timeline(eventId: String?): LiveData { + return timelineService.timeline(eventId) } override fun loadRoomMembersIfNeeded(): Cancelable { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt index 6ecc63ee..eaee0798 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt @@ -2,14 +2,20 @@ package im.vector.matrix.android.internal.session.room import im.vector.matrix.android.api.auth.data.SessionParams import im.vector.matrix.android.api.session.room.SendService -import im.vector.matrix.android.api.session.room.TimelineHolder import im.vector.matrix.android.api.session.room.send.EventFactory +import im.vector.matrix.android.api.session.room.timeline.TimelineService import im.vector.matrix.android.internal.session.DefaultSession import im.vector.matrix.android.internal.session.room.members.DefaultLoadRoomMembersTask import im.vector.matrix.android.internal.session.room.members.LoadRoomMembersTask import im.vector.matrix.android.internal.session.room.members.RoomMemberExtractor import im.vector.matrix.android.internal.session.room.send.DefaultSendService -import im.vector.matrix.android.internal.session.room.timeline.* +import im.vector.matrix.android.internal.session.room.timeline.DefaultGetContextOfEventTask +import im.vector.matrix.android.internal.session.room.timeline.DefaultPaginationTask +import im.vector.matrix.android.internal.session.room.timeline.DefaultTimelineService +import im.vector.matrix.android.internal.session.room.timeline.GetContextOfEventTask +import im.vector.matrix.android.internal.session.room.timeline.PaginationTask +import im.vector.matrix.android.internal.session.room.timeline.TimelineBoundaryCallback +import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEventPersistor import im.vector.matrix.android.internal.util.PagingRequestHelper import org.koin.dsl.module.module import retrofit2.Retrofit @@ -50,7 +56,7 @@ class RoomModule { val helper = PagingRequestHelper(Executors.newSingleThreadExecutor()) val timelineBoundaryCallback = TimelineBoundaryCallback(roomId, get(), get(), get(), helper) val roomMemberExtractor = RoomMemberExtractor(get(), roomId) - DefaultTimelineHolder(roomId, get(), get(), timelineBoundaryCallback, get(), roomMemberExtractor) as TimelineHolder + DefaultTimelineService(roomId, get(), get(), timelineBoundaryCallback, get(), roomMemberExtractor) as TimelineService } factory { (roomId: String) -> diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultPaginationTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultPaginationTask.kt index 5dfb3c3f..a9a549a3 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultPaginationTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultPaginationTask.kt @@ -1,10 +1,9 @@ package im.vector.matrix.android.internal.session.room.timeline import arrow.core.Try -import arrow.core.failure -import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.room.RoomAPI +import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.util.FilterUtil @@ -12,7 +11,7 @@ internal interface PaginationTask : Task data class Params( val roomId: String, - val from: String?, + val from: String, val direction: PaginationDirection, val limit: Int ) @@ -24,9 +23,6 @@ internal class DefaultPaginationTask(private val roomAPI: RoomAPI, ) : PaginationTask { override fun execute(params: PaginationTask.Params): Try { - if (params.from == null) { - return RuntimeException("From token shouldn't be null").failure() - } val filter = FilterUtil.createRoomEventFilter(true)?.toJSONString() return executeRequest { apiCall = roomAPI.getRoomMessagesFrom(params.roomId, params.from, params.direction.value, params.limit, filter) @@ -36,4 +32,5 @@ internal class DefaultPaginationTask(private val roomAPI: RoomAPI, .map { chunk } } } + } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineHolder.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt similarity index 73% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineHolder.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt index 8db811ef..401da51d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineHolder.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt @@ -6,7 +6,8 @@ import android.arch.paging.PagedList import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.session.events.interceptor.EnrichedEventInterceptor import im.vector.matrix.android.api.session.events.model.EnrichedEvent -import im.vector.matrix.android.api.session.room.TimelineHolder +import im.vector.matrix.android.api.session.room.timeline.TimelineData +import im.vector.matrix.android.api.session.room.timeline.TimelineService import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.model.ChunkEntityFields import im.vector.matrix.android.internal.database.model.EventEntity @@ -15,23 +16,25 @@ import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.session.room.members.RoomMemberExtractor import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith +import im.vector.matrix.android.internal.util.LiveDataUtils +import im.vector.matrix.android.internal.util.PagingRequestHelper import im.vector.matrix.android.internal.util.tryTransactionAsync import io.realm.Realm import io.realm.RealmQuery private const val PAGE_SIZE = 30 -internal class DefaultTimelineHolder(private val roomId: String, - private val monarchy: Monarchy, - private val taskExecutor: TaskExecutor, - private val boundaryCallback: TimelineBoundaryCallback, - private val contextOfEventTask: GetContextOfEventTask, - private val roomMemberExtractor: RoomMemberExtractor -) : TimelineHolder { +internal class DefaultTimelineService(private val roomId: String, + private val monarchy: Monarchy, + private val taskExecutor: TaskExecutor, + private val boundaryCallback: TimelineBoundaryCallback, + private val contextOfEventTask: GetContextOfEventTask, + private val roomMemberExtractor: RoomMemberExtractor +) : TimelineService { private val eventInterceptors = ArrayList() - override fun timeline(eventId: String?): LiveData> { + override fun timeline(eventId: String?): LiveData { clearUnlinkedEvents() if (eventId != null) { fetchEventIfNeeded(eventId) @@ -51,9 +54,16 @@ internal class DefaultTimelineHolder(private val roomId: String, .build() val livePagedListBuilder = LivePagedListBuilder(domainSourceFactory, pagedListConfig).setBoundaryCallback(boundaryCallback) - return monarchy.findAllPagedWithChanges(realmDataSourceFactory, livePagedListBuilder) + val eventsLiveData = monarchy.findAllPagedWithChanges(realmDataSourceFactory, livePagedListBuilder) + + return LiveDataUtils.combine(eventsLiveData, boundaryCallback.status) { events, status -> + val isLoadingForward = status.before == PagingRequestHelper.Status.RUNNING + val isLoadingBackward = status.after == PagingRequestHelper.Status.RUNNING + TimelineData(events, isLoadingForward, isLoadingBackward) + } } + private fun clearUnlinkedEvents() { monarchy.tryTransactionAsync { realm -> val unlinkedEvents = EventEntity diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineBoundaryCallback.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineBoundaryCallback.kt index ba574386..a9ff32d0 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineBoundaryCallback.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineBoundaryCallback.kt @@ -1,5 +1,6 @@ package im.vector.matrix.android.internal.session.room.timeline +import android.arch.lifecycle.LiveData import android.arch.paging.PagedList import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.MatrixCallback @@ -20,35 +21,60 @@ internal class TimelineBoundaryCallback(private val roomId: String, var limit = 30 + val status = object : LiveData() { + + init { + value = PagingRequestHelper.StatusReport.createDefault() + } + + val listener = PagingRequestHelper.Listener { postValue(it) } + + override fun onActive() { + helper.addListener(listener) + } + + override fun onInactive() { + helper.removeListener(listener) + } + } + override fun onZeroItemsLoaded() { // actually, it's not possible } override fun onItemAtEndLoaded(itemAtEnd: EnrichedEvent) { + val token = itemAtEnd.root.eventId?.let { getToken(it, PaginationDirection.BACKWARDS) } + ?: return + helper.runIfNotRunning(PagingRequestHelper.RequestType.AFTER) { - runPaginationRequest(it, itemAtEnd, PaginationDirection.BACKWARDS) + runPaginationRequest(it, token, PaginationDirection.BACKWARDS) } } override fun onItemAtFrontLoaded(itemAtFront: EnrichedEvent) { + val token = itemAtFront.root.eventId?.let { getToken(it, PaginationDirection.FORWARDS) } + ?: return + helper.runIfNotRunning(PagingRequestHelper.RequestType.BEFORE) { - runPaginationRequest(it, itemAtFront, PaginationDirection.FORWARDS) + runPaginationRequest(it, token, PaginationDirection.FORWARDS) } } - private fun runPaginationRequest(requestCallback: PagingRequestHelper.Request.Callback, - item: EnrichedEvent, - direction: PaginationDirection) { + private fun getToken(eventId: String, direction: PaginationDirection): String? { var token: String? = null monarchy.doWithRealm { realm -> - if (item.root.eventId == null) { - return@doWithRealm - } - val chunkEntity = ChunkEntity.findAllIncludingEvents(realm, Collections.singletonList(item.root.eventId)).firstOrNull() + val chunkEntity = ChunkEntity.findAllIncludingEvents(realm, Collections.singletonList(eventId)).firstOrNull() token = if (direction == PaginationDirection.FORWARDS) chunkEntity?.nextToken else chunkEntity?.prevToken } + return token + } + + private fun runPaginationRequest(requestCallback: PagingRequestHelper.Request.Callback, + from: String, + direction: PaginationDirection) { + val params = PaginationTask.Params(roomId = roomId, - from = token, + from = from, direction = direction, limit = limit) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/LiveDataUtils.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/LiveDataUtils.kt new file mode 100644 index 00000000..55de4d34 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/LiveDataUtils.kt @@ -0,0 +1,38 @@ +package im.vector.matrix.android.internal.util + +import android.arch.lifecycle.LiveData +import android.arch.lifecycle.MediatorLiveData + +object LiveDataUtils { + + fun combine(firstSource: LiveData, + secondSource: LiveData, + mapper: (FIRST, SECOND) -> OUT): LiveData { + + return MediatorLiveData().apply { + var firstValue: FIRST? = null + var secondValue: SECOND? = null + + val valueDispatcher = { + firstValue?.let { safeFirst -> + secondValue?.let { safeSecond -> + val mappedValue = mapper(safeFirst, safeSecond) + postValue(mappedValue) + } + } + } + + + addSource(firstSource) { + firstValue = it + valueDispatcher() + } + + addSource(secondSource) { + secondValue = it + valueDispatcher() + } + } + } + +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/PagingRequestHelper.java b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/PagingRequestHelper.java index 7e77fead..fa1ad086 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/PagingRequestHelper.java +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/PagingRequestHelper.java @@ -379,6 +379,11 @@ public class PagingRequestHelper { @NonNull private final Throwable[] mErrors; + public static StatusReport createDefault() { + final Throwable[] errors = {}; + return new StatusReport(Status.SUCCESS, Status.SUCCESS, Status.SUCCESS, errors); + } + StatusReport(@NonNull Status initial, @NonNull Status before, @NonNull Status after, @NonNull Throwable[] errors) { this.initial = initial; From de90cbe73e675fc4afadf4b08c1f49f6954370ed Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 7 Jan 2019 19:38:49 +0100 Subject: [PATCH 17/25] Timeline : fix merging issues --- .../session/room/timeline/ChunkEntityTest.kt | 18 ++++---- .../database/helper/ChunkEntityHelper.kt | 41 ++++++++++--------- .../room/timeline/TokenChunkEventPersistor.kt | 17 +++++--- 3 files changed, 43 insertions(+), 33 deletions(-) diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/ChunkEntityTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/ChunkEntityTest.kt index 181f0c1e..7460f5b5 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/ChunkEntityTest.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/ChunkEntityTest.kt @@ -4,7 +4,11 @@ import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.InstrumentedTest 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.internal.database.helper.* +import im.vector.matrix.android.internal.database.helper.add +import im.vector.matrix.android.internal.database.helper.addAll +import im.vector.matrix.android.internal.database.helper.isUnlinked +import im.vector.matrix.android.internal.database.helper.lastStateIndex +import im.vector.matrix.android.internal.database.helper.merge import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection import io.realm.Realm @@ -102,7 +106,7 @@ internal class ChunkEntityTest : InstrumentedTest { val chunk2: ChunkEntity = realm.createObject() chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS) chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS) - chunk1.merge(chunk2, PaginationDirection.BACKWARDS) + chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS) chunk1.events.size shouldEqual 60 } } @@ -118,7 +122,7 @@ internal class ChunkEntityTest : InstrumentedTest { chunk2.isLast = false chunk1.addAll("roomId", eventsForChunk1, PaginationDirection.FORWARDS) chunk2.addAll("roomId", eventsForChunk2, PaginationDirection.BACKWARDS) - chunk1.merge(chunk2, PaginationDirection.BACKWARDS) + chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS) chunk1.events.size shouldEqual 40 chunk1.isLast.shouldBeTrue() } @@ -131,7 +135,7 @@ internal class ChunkEntityTest : InstrumentedTest { val chunk2: ChunkEntity = realm.createObject() chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true) chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = false) - chunk1.merge(chunk2, PaginationDirection.BACKWARDS) + chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS) chunk1.isUnlinked().shouldBeFalse() } } @@ -143,7 +147,7 @@ internal class ChunkEntityTest : InstrumentedTest { val chunk2: ChunkEntity = realm.createObject() chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true) chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true) - chunk1.merge(chunk2, PaginationDirection.BACKWARDS) + chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS) chunk1.isUnlinked().shouldBeTrue() } } @@ -157,7 +161,7 @@ internal class ChunkEntityTest : InstrumentedTest { chunk1.prevToken = prevToken chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true) chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true) - chunk1.merge(chunk2, PaginationDirection.FORWARDS) + chunk1.merge("roomId", chunk2, PaginationDirection.FORWARDS) chunk1.prevToken shouldEqual prevToken } } @@ -171,7 +175,7 @@ internal class ChunkEntityTest : InstrumentedTest { chunk1.nextToken = nextToken chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true) chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true) - chunk1.merge(chunk2, PaginationDirection.BACKWARDS) + chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS) chunk1.nextToken shouldEqual nextToken } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt index 519e69e1..514a0158 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt @@ -2,6 +2,7 @@ package im.vector.matrix.android.internal.database.helper 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.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.mapper.toEntity import im.vector.matrix.android.internal.database.mapper.updateWith import im.vector.matrix.android.internal.database.model.ChunkEntity @@ -12,18 +13,21 @@ import im.vector.matrix.android.internal.session.room.timeline.PaginationDirecti import io.realm.Sort internal fun ChunkEntity.deleteOnCascade() { + assertIsManaged() this.events.deleteAllFromRealm() this.deleteFromRealm() } // By default if a chunk is empty we consider it unlinked internal fun ChunkEntity.isUnlinked(): Boolean { + assertIsManaged() return events.where().equalTo(EventEntityFields.IS_UNLINKED, false).findAll().isEmpty() } -internal fun ChunkEntity.merge(chunkToMerge: ChunkEntity, +internal fun ChunkEntity.merge(roomId: String, + chunkToMerge: ChunkEntity, direction: PaginationDirection) { - + assertIsManaged() val isChunkToMergeUnlinked = chunkToMerge.isUnlinked() val isCurrentChunkUnlinked = this.isUnlinked() val isUnlinked = isCurrentChunkUnlinked && isChunkToMergeUnlinked @@ -41,7 +45,7 @@ internal fun ChunkEntity.merge(chunkToMerge: ChunkEntity, eventsToMerge = chunkToMerge.events } eventsToMerge.forEach { - add(it, direction, isUnlinked = isUnlinked) + add(roomId, it.asDomain(), direction, isUnlinked = isUnlinked) } } @@ -50,7 +54,7 @@ internal fun ChunkEntity.addAll(roomId: String, direction: PaginationDirection, stateIndexOffset: Int = 0, isUnlinked: Boolean = false) { - + assertIsManaged() events.forEach { event -> add(roomId, event, direction, stateIndexOffset, isUnlinked) } @@ -66,22 +70,12 @@ internal fun ChunkEntity.add(roomId: String, stateIndexOffset: Int = 0, isUnlinked: Boolean = false) { - add(event.toEntity(roomId), direction, stateIndexOffset, isUnlinked) -} - -internal fun ChunkEntity.add(eventEntity: EventEntity, - direction: PaginationDirection, - stateIndexOffset: Int = 0, - isUnlinked: Boolean = false) { - if (!isManaged) { - throw IllegalStateException("Chunk entity should be managed to use fast contains") - } - - if (eventEntity.eventId.isEmpty() || events.fastContains(eventEntity.eventId)) { + assertIsManaged() + if (event.eventId.isNullOrEmpty() || events.fastContains(event.eventId)) { return } var currentStateIndex = lastStateIndex(direction, defaultValue = stateIndexOffset) - if (direction == PaginationDirection.FORWARDS && EventType.isStateEvent(eventEntity.type)) { + if (direction == PaginationDirection.FORWARDS && EventType.isStateEvent(event.type)) { currentStateIndex += 1 } else if (direction == PaginationDirection.BACKWARDS && events.isNotEmpty()) { val lastEventType = events.last()?.type ?: "" @@ -89,14 +83,21 @@ internal fun ChunkEntity.add(eventEntity: EventEntity, currentStateIndex -= 1 } } + val eventEntity = event.toEntity(roomId) eventEntity.updateWith(currentStateIndex, isUnlinked) val position = if (direction == PaginationDirection.FORWARDS) 0 else this.events.size events.add(position, eventEntity) } +private fun ChunkEntity.assertIsManaged() { + if (!isManaged) { + throw IllegalStateException("Chunk entity should be managed to use this function") + } +} + internal fun ChunkEntity.lastStateIndex(direction: PaginationDirection, defaultValue: Int = 0): Int { return when (direction) { - PaginationDirection.FORWARDS -> events.where().sort(EventEntityFields.STATE_INDEX, Sort.DESCENDING).findFirst()?.stateIndex - PaginationDirection.BACKWARDS -> events.where().sort(EventEntityFields.STATE_INDEX, Sort.ASCENDING).findFirst()?.stateIndex - } ?: defaultValue + PaginationDirection.FORWARDS -> events.where().sort(EventEntityFields.STATE_INDEX, Sort.DESCENDING).findFirst()?.stateIndex + PaginationDirection.BACKWARDS -> events.where().sort(EventEntityFields.STATE_INDEX, Sort.ASCENDING).findFirst()?.stateIndex + } ?: defaultValue } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEventPersistor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEventPersistor.kt index cd2b4868..68f26bcc 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEventPersistor.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEventPersistor.kt @@ -2,7 +2,12 @@ package im.vector.matrix.android.internal.session.room.timeline import arrow.core.Try import com.zhuinden.monarchy.Monarchy -import im.vector.matrix.android.internal.database.helper.* +import im.vector.matrix.android.internal.database.helper.addAll +import im.vector.matrix.android.internal.database.helper.addOrUpdate +import im.vector.matrix.android.internal.database.helper.addStateEvents +import im.vector.matrix.android.internal.database.helper.deleteOnCascade +import im.vector.matrix.android.internal.database.helper.isUnlinked +import im.vector.matrix.android.internal.database.helper.merge import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.query.create @@ -21,7 +26,7 @@ internal class TokenChunkEventPersistor(private val monarchy: Monarchy) { return monarchy .tryTransactionSync { realm -> val roomEntity = RoomEntity.where(realm, roomId).findFirst() - ?: throw IllegalStateException("You shouldn't use this method without a room") + ?: throw IllegalStateException("You shouldn't use this method without a room") val nextToken: String? val prevToken: String? @@ -41,10 +46,10 @@ internal class TokenChunkEventPersistor(private val monarchy: Monarchy) { var currentChunk = if (direction == PaginationDirection.FORWARDS) { prevChunk?.apply { this.nextToken = nextToken } - ?: ChunkEntity.create(realm, prevToken, nextToken) + ?: ChunkEntity.create(realm, prevToken, nextToken) } else { nextChunk?.apply { this.prevToken = prevToken } - ?: ChunkEntity.create(realm, prevToken, nextToken) + ?: ChunkEntity.create(realm, prevToken, nextToken) } currentChunk.addAll(roomId, receivedChunk.events, direction, isUnlinked = currentChunk.isUnlinked()) @@ -75,11 +80,11 @@ internal class TokenChunkEventPersistor(private val monarchy: Monarchy) { // We always merge the bottom chunk into top chunk, so we are always merging backwards return if (direction == PaginationDirection.BACKWARDS) { - currentChunk.merge(otherChunk, PaginationDirection.BACKWARDS) + currentChunk.merge(roomEntity.roomId, otherChunk, PaginationDirection.BACKWARDS) roomEntity.deleteOnCascade(otherChunk) currentChunk } else { - otherChunk.merge(currentChunk, PaginationDirection.BACKWARDS) + otherChunk.merge(roomEntity.roomId, currentChunk, PaginationDirection.BACKWARDS) roomEntity.deleteOnCascade(currentChunk) otherChunk } From 922609cb57f981756cbae189d7ff4cfe16f22985 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 10 Jan 2019 11:37:14 +0100 Subject: [PATCH 18/25] Timeline : try to get a better PagedList/Epoxy integration. Still need to be refined. --- app/build.gradle | 3 +- .../home/room/detail/RoomDetailFragment.kt | 3 +- .../home/room/detail/timeline/MessageItem.kt | 2 +- .../home/room/detail/timeline/TextItem.kt | 2 +- .../timeline/TimelineEventController.kt | 104 ++++------- .../paging/PagedListEpoxyController.kt | 125 +++++++++++++ .../timeline/paging/PagedListModelCache.kt | 171 ++++++++++++++++++ .../main/res/layout/item_event_message.xml | 4 +- .../database/query/ChunkEntityQueries.kt | 4 + .../room/members/RoomMemberExtractor.kt | 11 +- .../room/timeline/DefaultPaginationTask.kt | 5 +- .../room/timeline/DefaultTimelineService.kt | 4 +- .../room/timeline/TimelineBoundaryCallback.kt | 22 ++- .../room/timeline/TokenChunkEventPersistor.kt | 19 +- 14 files changed, 381 insertions(+), 98 deletions(-) create mode 100644 app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/paging/PagedListEpoxyController.kt create mode 100644 app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/paging/PagedListModelCache.kt diff --git a/app/build.gradle b/app/build.gradle index 1caa4065..f96635de 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -56,6 +56,8 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'com.android.support:appcompat-v7:28.0.0' implementation 'com.android.support.constraint:constraint-layout:1.1.3' + // Paging + implementation "android.arch.paging:runtime:1.0.1" implementation 'com.jakewharton.threetenabp:threetenabp:1.1.1' implementation 'com.jakewharton.timber:timber:4.7.1' @@ -66,7 +68,6 @@ dependencies { implementation("com.airbnb.android:epoxy:$epoxy_version") kapt "com.airbnb.android:epoxy-processor:$epoxy_version" - implementation "com.airbnb.android:epoxy-paging:$epoxy_version" implementation 'com.airbnb.android:mvrx:0.6.0' // FP diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt index fe1f3798..a03f21a5 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt @@ -73,9 +73,8 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback { val layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, true) scrollOnNewMessageCallback = ScrollOnNewMessageCallback(layoutManager) recyclerView.layoutManager = layoutManager - //timelineEventController.addModelBuildListener { it.dispatchTo(scrollOnNewMessageCallback) } recyclerView.setHasFixedSize(true) - recyclerView.setItemViewCacheSize(20) + //timelineEventController.addModelBuildListener { it.dispatchTo(scrollOnNewMessageCallback) } recyclerView.setController(timelineEventController) timelineEventController.callback = this } diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageItem.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageItem.kt index fb06ef9b..cdb5e35e 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageItem.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageItem.kt @@ -8,7 +8,7 @@ import im.vector.riotredesign.R import im.vector.riotredesign.core.epoxy.KotlinModel import im.vector.riotredesign.features.home.AvatarRenderer -data class MessageItem( +class MessageItem( val message: CharSequence? = null, val time: CharSequence? = null, val avatarUrl: String?, diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TextItem.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TextItem.kt index 8848b58b..4b827a5a 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TextItem.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TextItem.kt @@ -4,7 +4,7 @@ import android.widget.TextView import im.vector.riotredesign.R import im.vector.riotredesign.core.epoxy.KotlinModel -data class TextItem( +class TextItem( val text: CharSequence? = null ) : KotlinModel(R.layout.item_event_text) { diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt index 88649561..0cd38e50 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt @@ -1,106 +1,80 @@ package im.vector.riotredesign.features.home.room.detail.timeline -import android.arch.paging.PagedList import com.airbnb.epoxy.EpoxyAsyncUtil -import com.airbnb.epoxy.EpoxyController +import com.airbnb.epoxy.EpoxyModel import im.vector.matrix.android.api.session.events.model.EnrichedEvent import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.room.timeline.TimelineData import im.vector.riotredesign.core.extensions.localDateTime import im.vector.riotredesign.features.home.LoadingItemModel_ +import im.vector.riotredesign.features.home.room.detail.timeline.paging.PagedListEpoxyController class TimelineEventController(private val roomId: String, private val messageItemFactory: MessageItemFactory, private val textItemFactory: TextItemFactory, private val dateFormatter: TimelineDateFormatter -) : EpoxyController( +) : PagedListEpoxyController( EpoxyAsyncUtil.getAsyncBackgroundHandler(), EpoxyAsyncUtil.getAsyncBackgroundHandler() ) { - init { setFilterDuplicates(true) } - private val pagedListCallback = object : PagedList.Callback() { - override fun onChanged(position: Int, count: Int) { - buildSnapshotList() - } + private var isLoadingForward: Boolean = false + private var isLoadingBackward: Boolean = false - override fun onInserted(position: Int, count: Int) { - buildSnapshotList() - } - - override fun onRemoved(position: Int, count: Int) { - buildSnapshotList() - } - } - - private var snapshotList: List = emptyList() - private var timelineData: TimelineData? = null var callback: Callback? = null fun update(timelineData: TimelineData?) { - timelineData?.events?.removeWeakCallback(pagedListCallback) - this.timelineData = timelineData - timelineData?.events?.addWeakCallback(null, pagedListCallback) - buildSnapshotList() + isLoadingForward = timelineData?.isLoadingForward ?: false + isLoadingBackward = timelineData?.isLoadingBackward ?: false + submitList(timelineData?.events) + requestModelBuild() } - override fun buildModels() { - buildModelsWith( - snapshotList, - timelineData?.isLoadingForward ?: false, - timelineData?.isLoadingBackward ?: false - ) - } - private fun buildModelsWith(events: List, - isLoadingForward: Boolean, - isLoadingBackward: Boolean) { - if (events.isEmpty()) { - return + override fun buildItemModels(currentPosition: Int, items: List): List> { + if (items.isNullOrEmpty()) { + return emptyList() } + val epoxyModels = ArrayList>() + val event = items[currentPosition] ?: return emptyList() + val nextEvent = if (currentPosition + 1 < items.size) items[currentPosition + 1] else null + + val date = event.root.localDateTime() + val nextDate = nextEvent?.root?.localDateTime() + val addDaySeparator = date.toLocalDate() != nextDate?.toLocalDate() + + val item = when (event.root.type) { + EventType.MESSAGE -> messageItemFactory.create(event, nextEvent, addDaySeparator, date, callback) + else -> textItemFactory.create(event) + } + item?.also { + it.id(event.localId) + epoxyModels.add(it) + } + + if (addDaySeparator) { + val formattedDay = dateFormatter.formatMessageDay(date) + val daySeparatorItem = DaySeparatorItem(formattedDay).id(roomId + formattedDay) + epoxyModels.add(daySeparatorItem) + } + return epoxyModels + } + + override fun addModels(models: List>) { LoadingItemModel_() .id(roomId + "forward_loading_item") .addIf(isLoadingForward, this) - for (index in 0 until events.size) { - val event = events[index] ?: continue - val nextEvent = if (index + 1 < events.size) events[index + 1] else null - - val date = event.root.localDateTime() - val nextDate = nextEvent?.root?.localDateTime() - val addDaySeparator = date.toLocalDate() != nextDate?.toLocalDate() - - val item = when (event.root.type) { - EventType.MESSAGE -> messageItemFactory.create(event, nextEvent, addDaySeparator, date, callback) - else -> textItemFactory.create(event) - } - - item - ?.onBind { - timelineData?.events?.loadAround(index) - } - ?.id(event.localId) - ?.addTo(this) - - if (addDaySeparator) { - val formattedDay = dateFormatter.formatMessageDay(date) - DaySeparatorItem(formattedDay).id(roomId + formattedDay).addTo(this) - } - } + super.add(models) LoadingItemModel_() .id(roomId + "backward_loading_item") .addIf(isLoadingBackward, this) - } - private fun buildSnapshotList() { - snapshotList = timelineData?.events?.snapshot() ?: emptyList() - requestModelBuild() - } interface Callback { fun onUrlClicked(url: String) diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/paging/PagedListEpoxyController.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/paging/PagedListEpoxyController.kt new file mode 100644 index 00000000..a01c2904 --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/paging/PagedListEpoxyController.kt @@ -0,0 +1,125 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * 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.paging + +import android.arch.paging.PagedList +import android.os.Handler +import android.support.v7.util.DiffUtil +import com.airbnb.epoxy.EpoxyController +import com.airbnb.epoxy.EpoxyModel +import com.airbnb.epoxy.EpoxyViewHolder + +/** + * An [EpoxyController] that can work with a [PagedList]. + * + * Internally, it caches the model for each item in the [PagedList]. You should override + * [buildItemModel] method to build the model for the given item. Since [PagedList] might include + * `null` items if placeholders are enabled, this method needs to handle `null` values in the list. + * + * By default, the model for each item is added to the model list. To change this behavior (to + * filter items or inject extra items), you can override [addModels] function and manually add built + * models. + * + * @param T The type of the items in the [PagedList]. + */ +abstract class PagedListEpoxyController( + /** + * The handler to use for building models. By default this uses the main thread, but you can use + * [EpoxyAsyncUtil.getAsyncBackgroundHandler] to do model building in the background. + * + * The notify thread of your PagedList (from setNotifyExecutor in the PagedList Builder) must be + * the same as this thread. Otherwise Epoxy will crash. + */ + modelBuildingHandler: Handler = EpoxyController.defaultModelBuildingHandler, + /** + * The handler to use when calculating the diff between built model lists. + * By default this uses the main thread, but you can use + * [EpoxyAsyncUtil.getAsyncBackgroundHandler] to do diffing in the background. + */ + diffingHandler: Handler = EpoxyController.defaultDiffingHandler, + /** + * [PagedListEpoxyController] uses an [DiffUtil.ItemCallback] to detect changes between + * [PagedList]s. By default, it relies on simple object equality but you can provide a custom + * one if you don't use all fields in the object in your models. + */ + itemDiffCallback: DiffUtil.ItemCallback = DEFAULT_ITEM_DIFF_CALLBACK as DiffUtil.ItemCallback +) : EpoxyController(modelBuildingHandler, diffingHandler) { + // this is where we keep the already built models + protected val modelCache = PagedListModelCache( + modelBuilder = { pos, item -> + buildItemModels(pos, item) + }, + rebuildCallback = { + requestModelBuild() + }, + itemDiffCallback = itemDiffCallback, + modelBuildingHandler = modelBuildingHandler + ) + + final override fun buildModels() { + addModels(modelCache.getModels()) + } + + override fun onModelBound( + holder: EpoxyViewHolder, + boundModel: EpoxyModel<*>, + position: Int, + previouslyBoundModel: EpoxyModel<*>? + ) { + modelCache.loadAround(boundModel) + } + + /** + * This function adds all built models to the adapter. You can override this method to add extra + * items into the model list or remove some. + */ + open fun addModels(models: List>) { + super.add(models) + } + + /** + * Builds the model for a given item. This must return a single model for each item. If you want + * to inject headers etc, you can override [addModels] function. + * + * If the `item` is `null`, you should provide the placeholder. If your [PagedList] is configured + * without placeholders, you don't need to handle the `null` case. + */ + abstract fun buildItemModels(currentPosition: Int, items: List): List> + + /** + * Submit a new paged list. + * + * A diff will be calculated between this list and the previous list so you may still get calls + * to [buildItemModel] with items from the previous list. + */ + fun submitList(newList: PagedList?) { + modelCache.submitList(newList) + } + + companion object { + /** + * [PagedListEpoxyController] calculates a diff on top of the PagedList to check which + * models are invalidated. + * This is the default [DiffUtil.ItemCallback] which uses object equality. + */ + val DEFAULT_ITEM_DIFF_CALLBACK = object : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: Any, newItem: Any) = oldItem == newItem + + override fun areContentsTheSame(oldItem: Any, newItem: Any) = oldItem == newItem + } + } +} \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/paging/PagedListModelCache.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/paging/PagedListModelCache.kt new file mode 100644 index 00000000..1966ecee --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/paging/PagedListModelCache.kt @@ -0,0 +1,171 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * 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.paging + +import android.annotation.SuppressLint +import android.arch.paging.AsyncPagedListDiffer +import android.arch.paging.PagedList +import android.os.Handler +import android.support.v7.recyclerview.extensions.AsyncDifferConfig +import android.support.v7.util.DiffUtil +import android.support.v7.util.ListUpdateCallback +import android.util.Log +import com.airbnb.epoxy.EpoxyController +import com.airbnb.epoxy.EpoxyModel +import java.util.concurrent.Executor +import java.util.concurrent.atomic.AtomicBoolean + +/** + * A PagedList stream wrapper that caches models built for each item. It tracks changes in paged lists and caches + * models for each item when they are invalidated to avoid rebuilding models for the whole list when PagedList is + * updated. + */ +class PagedListModelCache( + private val modelBuilder: (itemIndex: Int, items: List) -> List>, + private val rebuildCallback: () -> Unit, + private val itemDiffCallback: DiffUtil.ItemCallback, + private val diffExecutor: Executor? = null, + private val modelBuildingHandler: Handler +) { + + + // Int is the index of the pagedList item + // We have to be able to find the pagedlist position coming from an epoxy model to trigger + // LoadAround with accuracy + private val modelCache = linkedMapOf, Int>() + private var isCacheStale = AtomicBoolean(true) + + /** + * Tracks the last accessed position so that we can report it back to the paged list when models are built. + */ + private var lastPosition: Int? = null + + /** + * Observer for the PagedList changes that invalidates the model cache when data is updated. + */ + private val updateCallback = object : ListUpdateCallback { + override fun onChanged(position: Int, count: Int, payload: Any?) { + invalidate() + rebuildCallback() + } + + override fun onMoved(fromPosition: Int, toPosition: Int) { + invalidate() + rebuildCallback() + } + + override fun onInserted(position: Int, count: Int) { + invalidate() + rebuildCallback() + } + + override fun onRemoved(position: Int, count: Int) { + invalidate() + rebuildCallback() + } + } + + private val asyncDiffer = @SuppressLint("RestrictedApi") + object : AsyncPagedListDiffer( + updateCallback, + AsyncDifferConfig.Builder( + itemDiffCallback + ).also { builder -> + if (diffExecutor != null) { + builder.setBackgroundThreadExecutor(diffExecutor) + } + // we have to reply on this private API, otherwise, paged list might be changed when models are being built, + // potentially creating concurrent modification problems. + builder.setMainThreadExecutor { runnable: Runnable -> + modelBuildingHandler.post(runnable) + } + }.build() + ) { + init { + if (modelBuildingHandler != EpoxyController.defaultModelBuildingHandler) { + try { + // looks like AsyncPagedListDiffer in 1.x ignores the config. + // Reflection to the rescue. + val mainThreadExecutorField = + AsyncPagedListDiffer::class.java.getDeclaredField("mMainThreadExecutor") + mainThreadExecutorField.isAccessible = true + mainThreadExecutorField.set(this, Executor { + modelBuildingHandler.post(it) + }) + } catch (t: Throwable) { + val msg = "Failed to hijack update handler in AsyncPagedListDiffer." + + "You can only build models on the main thread" + Log.e("PagedListModelCache", msg, t) + throw IllegalStateException(msg, t) + } + } + } + } + + fun submitList(pagedList: PagedList?) { + asyncDiffer.submitList(pagedList) + } + + fun getModels(): List> { + if (isCacheStale.compareAndSet(true, false)) { + asyncDiffer.currentList?.forEachIndexed { position, _ -> + buildModel(position) + } + } + lastPosition?.let { + triggerLoadAround(it) + } + return modelCache.keys.toList() + } + + fun loadAround(model: EpoxyModel<*>) { + modelCache[model]?.let { itemPosition -> + triggerLoadAround(itemPosition) + lastPosition = itemPosition + } + } + + // PRIVATE METHODS ***************************************************************************** + + private fun invalidate() { + modelCache.clear() + isCacheStale.set(true) + } + + private fun cacheModelsAtPosition(itemPosition: Int, epoxyModels: Set>) { + epoxyModels.forEach { + modelCache[it] = itemPosition + } + } + + private fun buildModel(pos: Int) { + if (pos >= asyncDiffer.currentList?.size ?: 0) { + return + } + modelBuilder(pos, asyncDiffer.currentList as List).also { + cacheModelsAtPosition(pos, it.toSet()) + } + } + + private fun triggerLoadAround(position: Int) { + asyncDiffer.currentList?.let { + if (it.size > 0) { + it.loadAround(Math.min(position, it.size - 1)) + } + } + } +} diff --git a/app/src/main/res/layout/item_event_message.xml b/app/src/main/res/layout/item_event_message.xml index 9ed6fe9b..c4b94cd4 100644 --- a/app/src/main/res/layout/item_event_message.xml +++ b/app/src/main/res/layout/item_event_message.xml @@ -28,7 +28,7 @@ android:ellipsize="end" android:maxLines="1" android:paddingBottom="8dp" - android:textSize="15sp" + android:textSize="16sp" app:layout_constraintBottom_toTopOf="@+id/toolbarSubtitleView" app:layout_constraintEnd_toStartOf="@+id/messageTimeView" app:layout_constraintHorizontal_bias="0.0" @@ -55,7 +55,7 @@ android:layout_marginStart="64dp" android:layout_marginBottom="8dp" android:textColor="@color/dark_grey" - android:textSize="14sp" + android:textSize="16sp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ChunkEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ChunkEntityQueries.kt index 2035af90..cf9cea43 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ChunkEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ChunkEntityQueries.kt @@ -37,6 +37,10 @@ internal fun ChunkEntity.Companion.findAllIncludingEvents(realm: Realm, eventIds .findAll() } +internal fun ChunkEntity.Companion.findIncludingEvent(realm: Realm, eventId: String): ChunkEntity? { + return findAllIncludingEvents(realm, listOf(eventId)).firstOrNull() +} + internal fun ChunkEntity.Companion.create(realm: Realm, prevToken: String?, nextToken: String?): ChunkEntity { return realm.createObject().apply { this.prevToken = prevToken diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/members/RoomMemberExtractor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/members/RoomMemberExtractor.kt index 88acac5e..e58f6f62 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/members/RoomMemberExtractor.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/members/RoomMemberExtractor.kt @@ -15,7 +15,12 @@ import io.realm.RealmQuery internal class RoomMemberExtractor(private val monarchy: Monarchy, private val roomId: String) { + private val cached = HashMap() + fun extractFrom(event: EventEntity): RoomMember? { + if (cached.containsKey(event.eventId)) { + return cached[event.eventId] + } val sender = event.sender ?: return null // If the event is unlinked we want to fetch unlinked state events val unlinked = event.isUnlinked @@ -23,11 +28,13 @@ internal class RoomMemberExtractor(private val monarchy: Monarchy, // If prevContent is null we fallback to the Int.MIN state events content() val content = if (event.stateIndex <= 0) { baseQuery(monarchy, roomId, sender, unlinked).next(from = event.stateIndex)?.prevContent - ?: baseQuery(monarchy, roomId, sender, unlinked).last(since = event.stateIndex)?.content + ?: baseQuery(monarchy, roomId, sender, unlinked).last(since = event.stateIndex)?.content } else { baseQuery(monarchy, roomId, sender, unlinked).last(since = event.stateIndex)?.content } - return ContentMapper.map(content).toModel() + val roomMember: RoomMember? = ContentMapper.map(content).toModel() + cached[event.eventId] = roomMember + return roomMember } private fun baseQuery(monarchy: Monarchy, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultPaginationTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultPaginationTask.kt index a9a549a3..647515a6 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultPaginationTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultPaginationTask.kt @@ -7,7 +7,7 @@ import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.util.FilterUtil -internal interface PaginationTask : Task { +internal interface PaginationTask : Task { data class Params( val roomId: String, @@ -22,14 +22,13 @@ internal class DefaultPaginationTask(private val roomAPI: RoomAPI, private val tokenChunkEventPersistor: TokenChunkEventPersistor ) : PaginationTask { - override fun execute(params: PaginationTask.Params): Try { + override fun execute(params: PaginationTask.Params): Try { val filter = FilterUtil.createRoomEventFilter(true)?.toJSONString() return executeRequest { apiCall = roomAPI.getRoomMessagesFrom(params.roomId, params.from, params.direction.value, params.limit, filter) }.flatMap { chunk -> tokenChunkEventPersistor .insertInDb(chunk, params.roomId, params.direction) - .map { chunk } } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt index 401da51d..f28f48bf 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt @@ -22,7 +22,8 @@ import im.vector.matrix.android.internal.util.tryTransactionAsync import io.realm.Realm import io.realm.RealmQuery -private const val PAGE_SIZE = 30 +private const val PAGE_SIZE = 50 +private const val PREFETCH_DISTANCE = 20 internal class DefaultTimelineService(private val roomId: String, private val monarchy: Monarchy, @@ -51,6 +52,7 @@ internal class DefaultTimelineService(private val roomId: String, val pagedListConfig = PagedList.Config.Builder() .setEnablePlaceholders(false) .setPageSize(PAGE_SIZE) + .setPrefetchDistance(PREFETCH_DISTANCE) .build() val livePagedListBuilder = LivePagedListBuilder(domainSourceFactory, pagedListConfig).setBoundaryCallback(boundaryCallback) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineBoundaryCallback.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineBoundaryCallback.kt index a9ff32d0..db9617e0 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineBoundaryCallback.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineBoundaryCallback.kt @@ -6,11 +6,11 @@ import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.session.events.model.EnrichedEvent import im.vector.matrix.android.internal.database.model.ChunkEntity -import im.vector.matrix.android.internal.database.query.findAllIncludingEvents +import im.vector.matrix.android.internal.database.query.findIncludingEvent import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.util.PagingRequestHelper -import java.util.* +import timber.log.Timber internal class TimelineBoundaryCallback(private val roomId: String, private val taskExecutor: TaskExecutor, @@ -43,8 +43,9 @@ internal class TimelineBoundaryCallback(private val roomId: String, } override fun onItemAtEndLoaded(itemAtEnd: EnrichedEvent) { + Timber.v("On item at end loaded") val token = itemAtEnd.root.eventId?.let { getToken(it, PaginationDirection.BACKWARDS) } - ?: return + ?: return helper.runIfNotRunning(PagingRequestHelper.RequestType.AFTER) { runPaginationRequest(it, token, PaginationDirection.BACKWARDS) @@ -52,8 +53,9 @@ internal class TimelineBoundaryCallback(private val roomId: String, } override fun onItemAtFrontLoaded(itemAtFront: EnrichedEvent) { + Timber.v("On item at front loaded") val token = itemAtFront.root.eventId?.let { getToken(it, PaginationDirection.FORWARDS) } - ?: return + ?: return helper.runIfNotRunning(PagingRequestHelper.RequestType.BEFORE) { runPaginationRequest(it, token, PaginationDirection.FORWARDS) @@ -63,7 +65,7 @@ internal class TimelineBoundaryCallback(private val roomId: String, private fun getToken(eventId: String, direction: PaginationDirection): String? { var token: String? = null monarchy.doWithRealm { realm -> - val chunkEntity = ChunkEntity.findAllIncludingEvents(realm, Collections.singletonList(eventId)).firstOrNull() + val chunkEntity = ChunkEntity.findIncludingEvent(realm, eventId) token = if (direction == PaginationDirection.FORWARDS) chunkEntity?.nextToken else chunkEntity?.prevToken } return token @@ -74,14 +76,14 @@ internal class TimelineBoundaryCallback(private val roomId: String, direction: PaginationDirection) { val params = PaginationTask.Params(roomId = roomId, - from = from, - direction = direction, - limit = limit) + from = from, + direction = direction, + limit = limit) paginationTask.configureWith(params) .enableRetry() - .dispatchTo(object : MatrixCallback { - override fun onSuccess(data: TokenChunkEvent) { + .dispatchTo(object : MatrixCallback { + override fun onSuccess(data: Boolean) { requestCallback.recordSuccess() } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEventPersistor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEventPersistor.kt index 68f26bcc..7e5943ab 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEventPersistor.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEventPersistor.kt @@ -2,12 +2,7 @@ package im.vector.matrix.android.internal.session.room.timeline import arrow.core.Try import com.zhuinden.monarchy.Monarchy -import im.vector.matrix.android.internal.database.helper.addAll -import im.vector.matrix.android.internal.database.helper.addOrUpdate -import im.vector.matrix.android.internal.database.helper.addStateEvents -import im.vector.matrix.android.internal.database.helper.deleteOnCascade -import im.vector.matrix.android.internal.database.helper.isUnlinked -import im.vector.matrix.android.internal.database.helper.merge +import im.vector.matrix.android.internal.database.helper.* import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.query.create @@ -21,12 +16,15 @@ internal class TokenChunkEventPersistor(private val monarchy: Monarchy) { fun insertInDb(receivedChunk: TokenChunkEvent, roomId: String, - direction: PaginationDirection): Try { + direction: PaginationDirection): Try { + if (receivedChunk.events.isEmpty() && receivedChunk.stateEvents.isEmpty()) { + return Try.just(false) + } return monarchy .tryTransactionSync { realm -> val roomEntity = RoomEntity.where(realm, roomId).findFirst() - ?: throw IllegalStateException("You shouldn't use this method without a room") + ?: throw IllegalStateException("You shouldn't use this method without a room") val nextToken: String? val prevToken: String? @@ -46,10 +44,10 @@ internal class TokenChunkEventPersistor(private val monarchy: Monarchy) { var currentChunk = if (direction == PaginationDirection.FORWARDS) { prevChunk?.apply { this.nextToken = nextToken } - ?: ChunkEntity.create(realm, prevToken, nextToken) + ?: ChunkEntity.create(realm, prevToken, nextToken) } else { nextChunk?.apply { this.prevToken = prevToken } - ?: ChunkEntity.create(realm, prevToken, nextToken) + ?: ChunkEntity.create(realm, prevToken, nextToken) } currentChunk.addAll(roomId, receivedChunk.events, direction, isUnlinked = currentChunk.isUnlinked()) @@ -71,6 +69,7 @@ internal class TokenChunkEventPersistor(private val monarchy: Monarchy) { roomEntity.addOrUpdate(currentChunk) roomEntity.addStateEvents(receivedChunk.stateEvents, isUnlinked = currentChunk.isUnlinked()) } + .map { true } } private fun handleMerge(roomEntity: RoomEntity, From 2b66b4c4b7346ddf2e038328945c36824610aa32 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 11 Jan 2019 12:07:38 +0100 Subject: [PATCH 19/25] Add a HomeActivityViewModel, VisibleRoomHandler and Event classes to handle some navigation. Not perfect but ok for now --- .../riotredesign/core/extensions/LiveData.kt | 19 ++++++ .../vector/riotredesign/core/utils/Event.kt | 40 ++++++++++++ .../features/home/HomeActivity.kt | 7 +++ .../features/home/HomeActivityViewModel.kt | 61 +++++++++++++++++++ .../riotredesign/features/home/HomeModule.kt | 6 ++ .../features/home/room/VisibleRoomHolder.kt | 19 ++++++ .../home/room/detail/RoomDetailActions.kt | 1 + .../home/room/detail/RoomDetailFragment.kt | 5 ++ .../home/room/detail/RoomDetailViewModel.kt | 11 +++- .../home/room/list/RoomListFragment.kt | 9 +-- .../home/room/list/RoomListViewModel.kt | 25 +++++--- 11 files changed, 184 insertions(+), 19 deletions(-) create mode 100644 app/src/main/java/im/vector/riotredesign/core/extensions/LiveData.kt create mode 100644 app/src/main/java/im/vector/riotredesign/core/utils/Event.kt create mode 100644 app/src/main/java/im/vector/riotredesign/features/home/HomeActivityViewModel.kt create mode 100644 app/src/main/java/im/vector/riotredesign/features/home/room/VisibleRoomHolder.kt diff --git a/app/src/main/java/im/vector/riotredesign/core/extensions/LiveData.kt b/app/src/main/java/im/vector/riotredesign/core/extensions/LiveData.kt new file mode 100644 index 00000000..bf33acda --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/core/extensions/LiveData.kt @@ -0,0 +1,19 @@ +package im.vector.riotredesign.core.extensions + +import android.arch.lifecycle.LifecycleOwner +import android.arch.lifecycle.LiveData +import android.arch.lifecycle.Observer +import im.vector.riotredesign.core.utils.Event +import im.vector.riotredesign.core.utils.EventObserver + +inline fun LiveData.observeK(owner: LifecycleOwner, crossinline observer: (T?) -> Unit) { + this.observe(owner, Observer { observer(it) }) +} + +inline fun LiveData.observeNotNull(owner: LifecycleOwner, crossinline observer: (T) -> Unit) { + this.observe(owner, Observer { it?.run(observer) }) +} + +inline fun LiveData>.observeEvent(owner: LifecycleOwner, crossinline observer: (T) -> Unit) { + this.observe(owner, EventObserver { it.run(observer) }) +} \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/core/utils/Event.kt b/app/src/main/java/im/vector/riotredesign/core/utils/Event.kt new file mode 100644 index 00000000..d67ada66 --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/core/utils/Event.kt @@ -0,0 +1,40 @@ +package im.vector.riotredesign.core.utils + +import android.arch.lifecycle.Observer + +open class Event(private val content: T) { + + var hasBeenHandled = false + private set // Allow external read but not write + + /** + * Returns the content and prevents its use again. + */ + fun getContentIfNotHandled(): T? { + return if (hasBeenHandled) { + null + } else { + hasBeenHandled = true + content + } + } + + /** + * Returns the content, even if it's already been handled. + */ + fun peekContent(): T = content +} + +/** + * An [Observer] for [Event]s, simplifying the pattern of checking if the [Event]'s content has + * already been handled. + * + * [onEventUnhandledContent] is *only* called if the [Event]'s contents has not been handled. + */ +class EventObserver(private val onEventUnhandledContent: (T) -> Unit) : Observer> { + override fun onChanged(event: Event?) { + event?.getContentIfNotHandled()?.let { value -> + onEventUnhandledContent(value) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt b/app/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt index 2fd2b653..da6a6731 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt @@ -9,7 +9,9 @@ import android.support.v7.app.ActionBarDrawerToggle import android.support.v7.widget.Toolbar import android.view.Gravity import android.view.MenuItem +import com.airbnb.mvrx.viewModel import im.vector.riotredesign.R +import im.vector.riotredesign.core.extensions.observeEvent import im.vector.riotredesign.core.extensions.replaceFragment import im.vector.riotredesign.core.platform.OnBackPressed import im.vector.riotredesign.core.platform.RiotActivity @@ -22,6 +24,8 @@ import org.koin.standalone.StandAloneContext.loadKoinModules class HomeActivity : RiotActivity(), ToolbarConfigurable { + + private val homeActivityViewModel: HomeActivityViewModel by viewModel() private val homeNavigator by inject() override fun onCreate(savedInstanceState: Bundle?) { @@ -35,6 +39,9 @@ class HomeActivity : RiotActivity(), ToolbarConfigurable { replaceFragment(loadingDetail, R.id.homeDetailFragmentContainer) replaceFragment(homeDrawerFragment, R.id.homeDrawerFragmentContainer) } + homeActivityViewModel.openRoomLiveData.observeEvent(this) { + homeNavigator.openRoomDetail(it, null) + } } override fun onDestroy() { diff --git a/app/src/main/java/im/vector/riotredesign/features/home/HomeActivityViewModel.kt b/app/src/main/java/im/vector/riotredesign/features/home/HomeActivityViewModel.kt new file mode 100644 index 00000000..108469da --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/features/home/HomeActivityViewModel.kt @@ -0,0 +1,61 @@ +package im.vector.riotredesign.features.home + +import android.arch.lifecycle.LiveData +import android.arch.lifecycle.MutableLiveData +import android.support.v4.app.FragmentActivity +import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MvRxViewModelFactory +import im.vector.matrix.android.api.Matrix +import im.vector.matrix.android.api.session.Session +import im.vector.matrix.rx.rx +import im.vector.riotredesign.core.platform.RiotViewModel +import im.vector.riotredesign.core.utils.Event +import im.vector.riotredesign.features.home.room.list.RoomSelectionRepository +import io.reactivex.rxkotlin.subscribeBy +import org.koin.android.ext.android.get + +class EmptyState : MvRxState + +class HomeActivityViewModel(state: EmptyState, + private val session: Session, + roomSelectionRepository: RoomSelectionRepository +) : RiotViewModel(state) { + + companion object : MvRxViewModelFactory { + + @JvmStatic + override fun create(activity: FragmentActivity, state: EmptyState): HomeActivityViewModel { + val session = Matrix.getInstance().currentSession + val roomSelectionRepository = activity.get() + return HomeActivityViewModel(state, session, roomSelectionRepository) + } + } + + private val _openRoomLiveData = MutableLiveData>() + val openRoomLiveData: LiveData> + get() = _openRoomLiveData + + init { + val lastSelectedRoom = roomSelectionRepository.lastSelectedRoom() + if (lastSelectedRoom == null) { + getTheFirstRoomWhenAvailable() + } else { + _openRoomLiveData.postValue(Event(lastSelectedRoom)) + } + } + + private fun getTheFirstRoomWhenAvailable() { + session.rx().liveRoomSummaries() + .filter { it.isNotEmpty() } + .first(emptyList()) + .subscribeBy { + val firstRoom = it.firstOrNull() + if (firstRoom != null) { + _openRoomLiveData.postValue(Event(firstRoom.roomId)) + } + } + .disposeOnClear() + } + + +} \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt b/app/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt index 86a414f1..1c30a4ca 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt @@ -1,6 +1,7 @@ package im.vector.riotredesign.features.home import im.vector.riotredesign.features.home.group.SelectedGroupHolder +import im.vector.riotredesign.features.home.room.VisibleRoomHolder import im.vector.riotredesign.features.home.room.detail.timeline.MessageItemFactory import im.vector.riotredesign.features.home.room.detail.timeline.TextItemFactory import im.vector.riotredesign.features.home.room.detail.timeline.TimelineDateFormatter @@ -35,9 +36,14 @@ class HomeModule(private val homeActivity: HomeActivity) { SelectedGroupHolder() } + single { + VisibleRoomHolder() + } + single { HomePermalinkHandler(get()) } + } } \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/VisibleRoomHolder.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/VisibleRoomHolder.kt new file mode 100644 index 00000000..c2888943 --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/VisibleRoomHolder.kt @@ -0,0 +1,19 @@ +package im.vector.riotredesign.features.home.room + +import io.reactivex.Observable +import io.reactivex.subjects.BehaviorSubject + +class VisibleRoomHolder { + + private val visibleRoomStream = BehaviorSubject.create() + + fun setVisibleRoom(roomId: String) { + visibleRoomStream.onNext(roomId) + } + + fun visibleRoom(): Observable { + return visibleRoomStream.hide() + } + + +} \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailActions.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailActions.kt index 032caa8a..89175598 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailActions.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailActions.kt @@ -3,5 +3,6 @@ package im.vector.riotredesign.features.home.room.detail sealed class RoomDetailActions { data class SendMessage(val text: String) : RoomDetailActions() + object IsDisplayed : RoomDetailActions() } \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt index a03f21a5..ee8ddbbf 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt @@ -62,6 +62,11 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback { roomDetailViewModel.subscribe { renderState(it) } } + override fun onResume() { + super.onResume() + roomDetailViewModel.accept(RoomDetailActions.IsDisplayed) + } + private fun setupToolbar() { val parentActivity = riotActivity if (parentActivity is ToolbarConfigurable) { diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt index 05a68813..d76b4cf4 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt @@ -8,9 +8,12 @@ import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.rx.rx import im.vector.riotredesign.core.platform.RiotViewModel +import im.vector.riotredesign.features.home.room.VisibleRoomHolder +import org.koin.android.ext.android.get class RoomDetailViewModel(initialState: RoomDetailViewState, - session: Session + private val session: Session, + private val visibleRoomHolder: VisibleRoomHolder ) : RiotViewModel(initialState) { private val room = session.getRoom(initialState.roomId)!! @@ -22,7 +25,8 @@ class RoomDetailViewModel(initialState: RoomDetailViewState, @JvmStatic override fun create(activity: FragmentActivity, state: RoomDetailViewState): RoomDetailViewModel { val currentSession = Matrix.getInstance().currentSession - return RoomDetailViewModel(state, currentSession) + val visibleRoomHolder = activity.get() + return RoomDetailViewModel(state, currentSession, visibleRoomHolder) } } @@ -35,6 +39,7 @@ class RoomDetailViewModel(initialState: RoomDetailViewState, fun accept(action: RoomDetailActions) { when (action) { is RoomDetailActions.SendMessage -> handleSendMessage(action) + is RoomDetailActions.IsDisplayed -> visibleRoomHolder.setVisibleRoom(roomId) } } @@ -54,7 +59,7 @@ class RoomDetailViewModel(initialState: RoomDetailViewState, private fun observeTimeline() { room.rx().timeline(eventId) .execute { timelineData -> - copy(asyncTimelineData= timelineData) + copy(asyncTimelineData = timelineData) } } diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt index 35b4d272..f66ee630 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt @@ -44,15 +44,12 @@ class RoomListFragment : RiotFragment(), RoomSummaryController.Callback { private fun renderState(state: RoomListViewState) { when (state.asyncRooms) { is Incomplete -> renderLoading() - is Success -> renderSuccess(state) - is Fail -> renderFailure(state.asyncRooms.error) + is Success -> renderSuccess(state) + is Fail -> renderFailure(state.asyncRooms.error) } } private fun renderSuccess(state: RoomListViewState) { - if (state.selectedRoomId != null) { - homeNavigator.openRoomDetail(state.selectedRoomId, null) - } if (state.asyncRooms().isNullOrEmpty()) { stateView.state = StateView.State.Empty(getString(R.string.room_list_empty)) } else { @@ -68,7 +65,7 @@ class RoomListFragment : RiotFragment(), RoomSummaryController.Callback { private fun renderFailure(error: Throwable) { val message = when (error) { is Failure.NetworkConnection -> getString(R.string.error_no_network) - else -> getString(R.string.error_common) + else -> getString(R.string.error_common) } stateView.state = StateView.State.Error(message) } diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewModel.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewModel.kt index 198e3fee..40a882f8 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewModel.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewModel.kt @@ -10,13 +10,16 @@ import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.rx.rx import im.vector.riotredesign.core.platform.RiotViewModel import im.vector.riotredesign.features.home.group.SelectedGroupHolder +import im.vector.riotredesign.features.home.room.VisibleRoomHolder import io.reactivex.Observable import io.reactivex.functions.BiFunction +import io.reactivex.rxkotlin.subscribeBy import org.koin.android.ext.android.get class RoomListViewModel(initialState: RoomListViewState, private val session: Session, private val selectedGroupHolder: SelectedGroupHolder, + private val visibleRoomHolder: VisibleRoomHolder, private val roomSelectionRepository: RoomSelectionRepository) : RiotViewModel(initialState) { @@ -27,12 +30,14 @@ class RoomListViewModel(initialState: RoomListViewState, val currentSession = Matrix.getInstance().currentSession val roomSelectionRepository = activity.get() val selectedGroupHolder = activity.get() - return RoomListViewModel(state, currentSession, selectedGroupHolder, roomSelectionRepository) + val visibleRoomHolder = activity.get() + return RoomListViewModel(state, currentSession, selectedGroupHolder, visibleRoomHolder, roomSelectionRepository) } } init { observeRoomSummaries() + observeVisibleRoom() } fun accept(action: RoomListActions) { @@ -46,10 +51,17 @@ class RoomListViewModel(initialState: RoomListViewState, private fun handleSelectRoom(action: RoomListActions.SelectRoom) = withState { state -> if (state.selectedRoomId != action.roomSummary.roomId) { roomSelectionRepository.saveLastSelectedRoom(action.roomSummary.roomId) - setState { copy(selectedRoomId = action.roomSummary.roomId) } } } + private fun observeVisibleRoom() { + visibleRoomHolder.visibleRoom() + .subscribeBy { + setState { copy(selectedRoomId = it) } + } + .disposeOnClear() + } + private fun observeRoomSummaries() { Observable.combineLatest, Option, RoomSummaries>( session.rx().liveRoomSummaries(), @@ -78,15 +90,8 @@ class RoomListViewModel(initialState: RoomListViewState, } ) .execute { async -> - val summaries = async() - val selectedRoomId = selectedRoomId - ?: roomSelectionRepository.lastSelectedRoom() - ?: summaries?.directRooms?.firstOrNull()?.roomId - ?: summaries?.groupRooms?.firstOrNull()?.roomId - copy( - asyncRooms = async, - selectedRoomId = selectedRoomId + asyncRooms = async ) } } From 02254d5d31a1684317b0f227596a7a1d964300fe Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 11 Jan 2019 15:17:15 +0100 Subject: [PATCH 20/25] Timeline : re-add hasReachedEnd condition to get a better display --- .../detail/timeline/TimelineEventController.kt | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt index 0cd38e50..8f5d70cb 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt @@ -23,14 +23,18 @@ class TimelineEventController(private val roomId: String, private var isLoadingForward: Boolean = false private var isLoadingBackward: Boolean = false + private var hasReachedEnd: Boolean = false var callback: Callback? = null fun update(timelineData: TimelineData?) { - isLoadingForward = timelineData?.isLoadingForward ?: false - isLoadingBackward = timelineData?.isLoadingBackward ?: false - submitList(timelineData?.events) - requestModelBuild() + timelineData?.let { + isLoadingForward = it.isLoadingForward + isLoadingBackward = it.isLoadingBackward + hasReachedEnd = it.events.lastOrNull()?.root?.type == EventType.STATE_ROOM_CREATE + submitList(it.events) + requestModelBuild() + } } @@ -48,7 +52,7 @@ class TimelineEventController(private val roomId: String, val item = when (event.root.type) { EventType.MESSAGE -> messageItemFactory.create(event, nextEvent, addDaySeparator, date, callback) - else -> textItemFactory.create(event) + else -> textItemFactory.create(event) } item?.also { it.id(event.localId) @@ -72,7 +76,7 @@ class TimelineEventController(private val roomId: String, LoadingItemModel_() .id(roomId + "backward_loading_item") - .addIf(isLoadingBackward, this) + .addIf(!hasReachedEnd, this) } From d7de986365be2fe57ee0e5222bf6ac8f2428f178 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 11 Jan 2019 16:35:49 +0100 Subject: [PATCH 21/25] Timeline : try using initialLoadKey to open PagedList around an event. Still need to scroll to the item. --- .../home/room/detail/RoomDetailFragment.kt | 18 ++++++---- .../room/timeline/DefaultTimelineService.kt | 35 +++++++++++-------- 2 files changed, 31 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt index ee8ddbbf..560e8824 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt @@ -52,13 +52,7 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback { super.onActivityCreated(savedInstanceState) setupRecyclerView() setupToolbar() - sendButton.setOnClickListener { - val textMessage = composerEditText.text.toString() - if (textMessage.isNotBlank()) { - composerEditText.text = null - roomDetailViewModel.accept(RoomDetailActions.SendMessage(textMessage)) - } - } + setupSendButton() roomDetailViewModel.subscribe { renderState(it) } } @@ -84,6 +78,16 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback { timelineEventController.callback = this } + private fun setupSendButton() { + sendButton.setOnClickListener { + val textMessage = composerEditText.text.toString() + if (textMessage.isNotBlank()) { + composerEditText.text = null + roomDetailViewModel.accept(RoomDetailActions.SendMessage(textMessage)) + } + } + } + private fun renderState(state: RoomDetailViewState) { renderRoomSummary(state) renderTimeline(state) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt index f28f48bf..5ce44682 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt @@ -22,8 +22,9 @@ import im.vector.matrix.android.internal.util.tryTransactionAsync import io.realm.Realm import io.realm.RealmQuery -private const val PAGE_SIZE = 50 -private const val PREFETCH_DISTANCE = 20 +private const val PAGE_SIZE = 120 +private const val PREFETCH_DISTANCE = 40 +private const val EVENT_NOT_FOUND_INDEX = -1 internal class DefaultTimelineService(private val roomId: String, private val monarchy: Monarchy, @@ -37,8 +38,15 @@ internal class DefaultTimelineService(private val roomId: String, override fun timeline(eventId: String?): LiveData { clearUnlinkedEvents() + var initialLoadKey = 0 if (eventId != null) { - fetchEventIfNeeded(eventId) + val indexOfEvent = indexOfEvent(eventId) + if (indexOfEvent == EVENT_NOT_FOUND_INDEX) { + val params = GetContextOfEventTask.Params(roomId, eventId) + contextOfEventTask.configureWith(params).executeBy(taskExecutor) + } else { + initialLoadKey = indexOfEvent + } } val realmDataSourceFactory = monarchy.createDataSourceFactory { buildDataSourceFactoryQuery(it, eventId) @@ -52,10 +60,14 @@ internal class DefaultTimelineService(private val roomId: String, val pagedListConfig = PagedList.Config.Builder() .setEnablePlaceholders(false) .setPageSize(PAGE_SIZE) + .setInitialLoadSizeHint(PAGE_SIZE) .setPrefetchDistance(PREFETCH_DISTANCE) .build() - val livePagedListBuilder = LivePagedListBuilder(domainSourceFactory, pagedListConfig).setBoundaryCallback(boundaryCallback) + val livePagedListBuilder = LivePagedListBuilder(domainSourceFactory, pagedListConfig) + .setBoundaryCallback(boundaryCallback) + .setInitialLoadKey(initialLoadKey) + val eventsLiveData = monarchy.findAllPagedWithChanges(realmDataSourceFactory, livePagedListBuilder) return LiveDataUtils.combine(eventsLiveData, boundaryCallback.status) { events, status -> @@ -76,19 +88,12 @@ internal class DefaultTimelineService(private val roomId: String, } } - private fun fetchEventIfNeeded(eventId: String) { - if (!isEventPersisted(eventId)) { - val params = GetContextOfEventTask.Params(roomId, eventId) - contextOfEventTask.configureWith(params).executeBy(taskExecutor) - } - } - - private fun isEventPersisted(eventId: String): Boolean { - var isEventPersisted = false + private fun indexOfEvent(eventId: String): Int { + var displayIndex = EVENT_NOT_FOUND_INDEX monarchy.doWithRealm { - isEventPersisted = EventEntity.where(it, eventId = eventId).findFirst() != null + displayIndex = EventEntity.where(it, eventId = eventId).findFirst()?.displayIndex ?: EVENT_NOT_FOUND_INDEX } - return isEventPersisted + return displayIndex } private fun buildDataSourceFactoryQuery(realm: Realm, eventId: String?): RealmQuery { From acf2301fc91920f0cb98a54a93895b18f7447c26 Mon Sep 17 00:00:00 2001 From: ganfra Date: Sat, 12 Jan 2019 10:20:46 +0100 Subject: [PATCH 22/25] Use RxRelay instead of Subjects --- app/build.gradle | 1 + .../riotredesign/features/home/group/SelectedGroupHolder.kt | 6 +++--- .../riotredesign/features/home/room/VisibleRoomHolder.kt | 5 +++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index f96635de..a79d1788 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -65,6 +65,7 @@ dependencies { // rx implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0' implementation 'io.reactivex.rxjava2:rxandroid:2.0.2' + implementation 'com.jakewharton.rxrelay2:rxrelay:2.1.0' implementation("com.airbnb.android:epoxy:$epoxy_version") kapt "com.airbnb.android:epoxy-processor:$epoxy_version" diff --git a/app/src/main/java/im/vector/riotredesign/features/home/group/SelectedGroupHolder.kt b/app/src/main/java/im/vector/riotredesign/features/home/group/SelectedGroupHolder.kt index 990e6fe8..5dc350d7 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/group/SelectedGroupHolder.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/group/SelectedGroupHolder.kt @@ -1,17 +1,17 @@ package im.vector.riotredesign.features.home.group import arrow.core.Option +import com.jakewharton.rxrelay2.BehaviorRelay import im.vector.matrix.android.api.session.group.model.GroupSummary import io.reactivex.Observable -import io.reactivex.subjects.BehaviorSubject class SelectedGroupHolder { - private val selectedGroupStream = BehaviorSubject.createDefault>(Option.empty()) + private val selectedGroupStream = BehaviorRelay.createDefault>(Option.empty()) fun setSelectedGroup(group: GroupSummary?) { val optionValue = Option.fromNullable(group) - selectedGroupStream.onNext(optionValue) + selectedGroupStream.accept(optionValue) } fun selectedGroup(): Observable> { diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/VisibleRoomHolder.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/VisibleRoomHolder.kt index c2888943..c17ca429 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/VisibleRoomHolder.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/VisibleRoomHolder.kt @@ -1,14 +1,15 @@ package im.vector.riotredesign.features.home.room +import com.jakewharton.rxrelay2.BehaviorRelay import io.reactivex.Observable import io.reactivex.subjects.BehaviorSubject class VisibleRoomHolder { - private val visibleRoomStream = BehaviorSubject.create() + private val visibleRoomStream = BehaviorRelay.create() fun setVisibleRoom(roomId: String) { - visibleRoomStream.onNext(roomId) + visibleRoomStream.accept(roomId) } fun visibleRoom(): Observable { From 34e08705ddc3cb8541000841b425b1c3b9f2755d Mon Sep 17 00:00:00 2001 From: ganfra Date: Sat, 12 Jan 2019 10:33:39 +0100 Subject: [PATCH 23/25] Timeline : still trying to adjust PagedList options --- .../session/room/timeline/DefaultTimelineService.kt | 6 +++--- .../room/timeline/TimelineBoundaryCallback.kt | 13 +++++-------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt index 5ce44682..6f5a80ad 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt @@ -22,8 +22,8 @@ import im.vector.matrix.android.internal.util.tryTransactionAsync import io.realm.Realm import io.realm.RealmQuery -private const val PAGE_SIZE = 120 -private const val PREFETCH_DISTANCE = 40 +private const val PAGE_SIZE = 100 +private const val PREFETCH_DISTANCE = 30 private const val EVENT_NOT_FOUND_INDEX = -1 internal class DefaultTimelineService(private val roomId: String, @@ -60,7 +60,7 @@ internal class DefaultTimelineService(private val roomId: String, val pagedListConfig = PagedList.Config.Builder() .setEnablePlaceholders(false) .setPageSize(PAGE_SIZE) - .setInitialLoadSizeHint(PAGE_SIZE) + .setInitialLoadSizeHint(2 * PAGE_SIZE) .setPrefetchDistance(PREFETCH_DISTANCE) .build() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineBoundaryCallback.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineBoundaryCallback.kt index db9617e0..2ce550f3 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineBoundaryCallback.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineBoundaryCallback.kt @@ -10,7 +10,6 @@ import im.vector.matrix.android.internal.database.query.findIncludingEvent import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.util.PagingRequestHelper -import timber.log.Timber internal class TimelineBoundaryCallback(private val roomId: String, private val taskExecutor: TaskExecutor, @@ -43,22 +42,20 @@ internal class TimelineBoundaryCallback(private val roomId: String, } override fun onItemAtEndLoaded(itemAtEnd: EnrichedEvent) { - Timber.v("On item at end loaded") val token = itemAtEnd.root.eventId?.let { getToken(it, PaginationDirection.BACKWARDS) } ?: return helper.runIfNotRunning(PagingRequestHelper.RequestType.AFTER) { - runPaginationRequest(it, token, PaginationDirection.BACKWARDS) + executePaginationTask(it, token, PaginationDirection.BACKWARDS) } } override fun onItemAtFrontLoaded(itemAtFront: EnrichedEvent) { - Timber.v("On item at front loaded") val token = itemAtFront.root.eventId?.let { getToken(it, PaginationDirection.FORWARDS) } ?: return helper.runIfNotRunning(PagingRequestHelper.RequestType.BEFORE) { - runPaginationRequest(it, token, PaginationDirection.FORWARDS) + executePaginationTask(it, token, PaginationDirection.FORWARDS) } } @@ -71,9 +68,9 @@ internal class TimelineBoundaryCallback(private val roomId: String, return token } - private fun runPaginationRequest(requestCallback: PagingRequestHelper.Request.Callback, - from: String, - direction: PaginationDirection) { + private fun executePaginationTask(requestCallback: PagingRequestHelper.Request.Callback, + from: String, + direction: PaginationDirection) { val params = PaginationTask.Params(roomId = roomId, from = from, From b2cdeb87f49ab3fcac4b2655b412a9d917c9f4ea Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 14 Jan 2019 16:18:39 +0100 Subject: [PATCH 24/25] Fix some timeline issues and rename EnrichedEvent to TimelineEvent as it's only used in this context. --- .../riotredesign/core/extensions/LiveData.kt | 4 +- .../riotredesign/core/utils/Constants.kt | 6 -- .../core/utils/FragmentArgumentDelegate.kt | 63 ------------------- .../core/utils/{Event.kt => LiveEvent.kt} | 10 +-- .../features/home/HomeActivityViewModel.kt | 10 +-- .../features/home/HomeNavigator.kt | 2 +- .../home/room/detail/RoomDetailFragment.kt | 11 +++- .../room/detail/ScrollOnNewMessageCallback.kt | 4 +- .../detail/timeline/MessageItemFactory.kt | 6 +- .../room/detail/timeline/TextItemFactory.kt | 4 +- .../timeline/TimelineEventController.kt | 6 +- .../paging/PagedListEpoxyController.kt | 4 ++ .../room/timeline/TimelineHolderTest.kt | 16 ++--- .../matrix/android/api/session/Session.kt | 8 +++ .../interceptor/EnrichedEventInterceptor.kt | 12 ---- .../interceptor/TimelineEventInterceptor.kt | 12 ++++ .../{EnrichedEvent.kt => TimelineEvent.kt} | 2 +- .../api/session/room/timeline/TimelineData.kt | 4 +- .../database/RealmLiveEntityObserver.kt | 11 ++-- .../database/query/EventEntityQueries.kt | 5 +- .../internal/session/DefaultSession.kt | 9 +++ .../internal/session/SessionListeners.kt | 17 +++++ .../android/internal/session/SessionModule.kt | 5 +- .../session/group/GetGroupDataWorker.kt | 7 +-- .../session/group/GroupSummaryUpdater.kt | 13 ++-- .../internal/session/room/DefaultRoom.kt | 6 +- .../session/room/RoomSummaryUpdater.kt | 10 +-- .../session/room/prune/EventsPruner.kt | 7 +-- .../session/room/prune/PruneEventWorker.kt | 12 ++-- .../room/timeline/DefaultTimelineService.kt | 55 ++++++++++------ .../room/timeline/TimelineBoundaryCallback.kt | 8 +-- 31 files changed, 170 insertions(+), 179 deletions(-) delete mode 100644 app/src/main/java/im/vector/riotredesign/core/utils/Constants.kt delete mode 100644 app/src/main/java/im/vector/riotredesign/core/utils/FragmentArgumentDelegate.kt rename app/src/main/java/im/vector/riotredesign/core/utils/{Event.kt => LiveEvent.kt} (68%) delete mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/interceptor/EnrichedEventInterceptor.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/interceptor/TimelineEventInterceptor.kt rename matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/{EnrichedEvent.kt => TimelineEvent.kt} (95%) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionListeners.kt diff --git a/app/src/main/java/im/vector/riotredesign/core/extensions/LiveData.kt b/app/src/main/java/im/vector/riotredesign/core/extensions/LiveData.kt index bf33acda..b012483a 100644 --- a/app/src/main/java/im/vector/riotredesign/core/extensions/LiveData.kt +++ b/app/src/main/java/im/vector/riotredesign/core/extensions/LiveData.kt @@ -3,7 +3,7 @@ package im.vector.riotredesign.core.extensions import android.arch.lifecycle.LifecycleOwner import android.arch.lifecycle.LiveData import android.arch.lifecycle.Observer -import im.vector.riotredesign.core.utils.Event +import im.vector.riotredesign.core.utils.LiveEvent import im.vector.riotredesign.core.utils.EventObserver inline fun LiveData.observeK(owner: LifecycleOwner, crossinline observer: (T?) -> Unit) { @@ -14,6 +14,6 @@ inline fun LiveData.observeNotNull(owner: LifecycleOwner, crossinline obs this.observe(owner, Observer { it?.run(observer) }) } -inline fun LiveData>.observeEvent(owner: LifecycleOwner, crossinline observer: (T) -> Unit) { +inline fun LiveData>.observeEvent(owner: LifecycleOwner, crossinline observer: (T) -> Unit) { this.observe(owner, EventObserver { it.run(observer) }) } \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/core/utils/Constants.kt b/app/src/main/java/im/vector/riotredesign/core/utils/Constants.kt deleted file mode 100644 index ade18b36..00000000 --- a/app/src/main/java/im/vector/riotredesign/core/utils/Constants.kt +++ /dev/null @@ -1,6 +0,0 @@ -package im.vector.riotredesign.core.utils - -object Constants { - - -} \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/core/utils/FragmentArgumentDelegate.kt b/app/src/main/java/im/vector/riotredesign/core/utils/FragmentArgumentDelegate.kt deleted file mode 100644 index 565c421c..00000000 --- a/app/src/main/java/im/vector/riotredesign/core/utils/FragmentArgumentDelegate.kt +++ /dev/null @@ -1,63 +0,0 @@ -package im.vector.riotredesign.core.utils - -import android.os.Binder -import android.os.Bundle -import android.support.v4.app.BundleCompat -import android.support.v4.app.Fragment -import kotlin.reflect.KProperty - -class FragmentArgumentDelegate : kotlin.properties.ReadWriteProperty { - - var value: T? = null - - override operator fun getValue(thisRef: android.support.v4.app.Fragment, property: kotlin.reflect.KProperty<*>): T? { - if (value == null) { - val args = thisRef.arguments - @Suppress("UNCHECKED_CAST") - value = args?.get(property.name) as T? - } - return value - } - - override operator fun setValue(thisRef: Fragment, property: KProperty<*>, value: T?) { - if (value == null) return - - if (thisRef.arguments == null) { - thisRef.arguments = Bundle() - } - val args = thisRef.arguments!! - val key = property.name - - when (value) { - is String -> args.putString(key, value) - is Int -> args.putInt(key, value) - is Short -> args.putShort(key, value) - is Long -> args.putLong(key, value) - is Byte -> args.putByte(key, value) - is ByteArray -> args.putByteArray(key, value) - is Char -> args.putChar(key, value) - is CharArray -> args.putCharArray(key, value) - is CharSequence -> args.putCharSequence(key, value) - is Float -> args.putFloat(key, value) - is Bundle -> args.putBundle(key, value) - is Binder -> BundleCompat.putBinder(args, key, value) - is android.os.Parcelable -> args.putParcelable(key, value) - is java.io.Serializable -> args.putSerializable(key, value) - else -> throw IllegalStateException("Type ${value.javaClass.name} of property ${property.name} is not supported") - } - } -} - -class UnsafeFragmentArgumentDelegate : kotlin.properties.ReadWriteProperty { - - private val innerDelegate = FragmentArgumentDelegate() - - override fun setValue(thisRef: Fragment, property: KProperty<*>, value: T) { - innerDelegate.setValue(thisRef, property, value) - } - - override fun getValue(thisRef: Fragment, property: KProperty<*>): T { - return innerDelegate.getValue(thisRef, property)!! - } - -} \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/core/utils/Event.kt b/app/src/main/java/im/vector/riotredesign/core/utils/LiveEvent.kt similarity index 68% rename from app/src/main/java/im/vector/riotredesign/core/utils/Event.kt rename to app/src/main/java/im/vector/riotredesign/core/utils/LiveEvent.kt index d67ada66..153e3c3f 100644 --- a/app/src/main/java/im/vector/riotredesign/core/utils/Event.kt +++ b/app/src/main/java/im/vector/riotredesign/core/utils/LiveEvent.kt @@ -2,7 +2,7 @@ package im.vector.riotredesign.core.utils import android.arch.lifecycle.Observer -open class Event(private val content: T) { +open class LiveEvent(private val content: T) { var hasBeenHandled = false private set // Allow external read but not write @@ -26,13 +26,13 @@ open class Event(private val content: T) { } /** - * An [Observer] for [Event]s, simplifying the pattern of checking if the [Event]'s content has + * An [Observer] for [LiveEvent]s, simplifying the pattern of checking if the [LiveEvent]'s content has * already been handled. * - * [onEventUnhandledContent] is *only* called if the [Event]'s contents has not been handled. + * [onEventUnhandledContent] is *only* called if the [LiveEvent]'s contents has not been handled. */ -class EventObserver(private val onEventUnhandledContent: (T) -> Unit) : Observer> { - override fun onChanged(event: Event?) { +class EventObserver(private val onEventUnhandledContent: (T) -> Unit) : Observer> { + override fun onChanged(event: LiveEvent?) { event?.getContentIfNotHandled()?.let { value -> onEventUnhandledContent(value) } diff --git a/app/src/main/java/im/vector/riotredesign/features/home/HomeActivityViewModel.kt b/app/src/main/java/im/vector/riotredesign/features/home/HomeActivityViewModel.kt index 108469da..06509e8a 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/HomeActivityViewModel.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/HomeActivityViewModel.kt @@ -9,7 +9,7 @@ import im.vector.matrix.android.api.Matrix import im.vector.matrix.android.api.session.Session import im.vector.matrix.rx.rx import im.vector.riotredesign.core.platform.RiotViewModel -import im.vector.riotredesign.core.utils.Event +import im.vector.riotredesign.core.utils.LiveEvent import im.vector.riotredesign.features.home.room.list.RoomSelectionRepository import io.reactivex.rxkotlin.subscribeBy import org.koin.android.ext.android.get @@ -31,8 +31,8 @@ class HomeActivityViewModel(state: EmptyState, } } - private val _openRoomLiveData = MutableLiveData>() - val openRoomLiveData: LiveData> + private val _openRoomLiveData = MutableLiveData>() + val openRoomLiveData: LiveData> get() = _openRoomLiveData init { @@ -40,7 +40,7 @@ class HomeActivityViewModel(state: EmptyState, if (lastSelectedRoom == null) { getTheFirstRoomWhenAvailable() } else { - _openRoomLiveData.postValue(Event(lastSelectedRoom)) + _openRoomLiveData.postValue(LiveEvent(lastSelectedRoom)) } } @@ -51,7 +51,7 @@ class HomeActivityViewModel(state: EmptyState, .subscribeBy { val firstRoom = it.firstOrNull() if (firstRoom != null) { - _openRoomLiveData.postValue(Event(firstRoom.roomId)) + _openRoomLiveData.postValue(LiveEvent(firstRoom.roomId)) } } .disposeOnClear() diff --git a/app/src/main/java/im/vector/riotredesign/features/home/HomeNavigator.kt b/app/src/main/java/im/vector/riotredesign/features/home/HomeNavigator.kt index 41f61688..29cb66bc 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/HomeNavigator.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/HomeNavigator.kt @@ -23,7 +23,6 @@ class HomeNavigator { if (!addToBackstack && isRoomOpened(roomId)) { return } - currentRoomId = roomId activity?.let { val args = RoomDetailArgs(roomId, eventId) val roomDetailFragment = RoomDetailFragment.newInstance(args) @@ -31,6 +30,7 @@ class HomeNavigator { if (addToBackstack) { it.addFragmentToBackstack(roomDetailFragment, R.id.homeDetailFragmentContainer, roomId) } else { + currentRoomId = roomId clearBackStack(it.supportFragmentManager) it.replaceFragment(roomDetailFragment, R.id.homeDetailFragmentContainer) } diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt index 560e8824..d3f47e3d 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt @@ -73,7 +73,7 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback { scrollOnNewMessageCallback = ScrollOnNewMessageCallback(layoutManager) recyclerView.layoutManager = layoutManager recyclerView.setHasFixedSize(true) - //timelineEventController.addModelBuildListener { it.dispatchTo(scrollOnNewMessageCallback) } + timelineEventController.addModelBuildListener { it.dispatchTo(scrollOnNewMessageCallback) } recyclerView.setController(timelineEventController) timelineEventController.callback = this } @@ -95,8 +95,15 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback { private fun renderTimeline(state: RoomDetailViewState) { when (state.asyncTimelineData) { - is Success -> timelineEventController.update(state.asyncTimelineData()) + is Success -> { + val timelineData = state.asyncTimelineData() + val lockAutoScroll = timelineData?.let { + it.events == timelineEventController.currentList && it.isLoadingForward + } ?: true + scrollOnNewMessageCallback.isLocked.set(lockAutoScroll) + timelineEventController.update(timelineData) + } } } diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/ScrollOnNewMessageCallback.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/ScrollOnNewMessageCallback.kt index aec98efe..1ffbb501 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/ScrollOnNewMessageCallback.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/ScrollOnNewMessageCallback.kt @@ -6,10 +6,10 @@ import java.util.concurrent.atomic.AtomicBoolean class ScrollOnNewMessageCallback(private val layoutManager: LinearLayoutManager) : DefaultListUpdateCallback { - val hasBeenUpdated = AtomicBoolean(false) + var isLocked = AtomicBoolean(true) override fun onInserted(position: Int, count: Int) { - if (hasBeenUpdated.compareAndSet(true, false) && position == 0 && layoutManager.findFirstVisibleItemPosition() == 0) { + if (isLocked.compareAndSet(false, true) && position == 0 && layoutManager.findFirstVisibleItemPosition() == 0) { layoutManager.scrollToPosition(0) } } diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageItemFactory.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageItemFactory.kt index 5e5d1b1e..2122e545 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageItemFactory.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageItemFactory.kt @@ -4,7 +4,7 @@ import android.text.SpannableStringBuilder import android.text.util.Linkify import im.vector.matrix.android.api.permalinks.MatrixLinkify import im.vector.matrix.android.api.permalinks.MatrixPermalinkSpan -import im.vector.matrix.android.api.session.events.model.EnrichedEvent +import im.vector.matrix.android.api.session.events.model.TimelineEvent import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.MessageContent import org.threeten.bp.LocalDateTime @@ -13,8 +13,8 @@ class MessageItemFactory(private val timelineDateFormatter: TimelineDateFormatte private val messagesDisplayedWithInformation = HashSet() - fun create(event: EnrichedEvent, - nextEvent: EnrichedEvent?, + fun create(event: TimelineEvent, + nextEvent: TimelineEvent?, addDaySeparator: Boolean, date: LocalDateTime, callback: TimelineEventController.Callback? diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TextItemFactory.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TextItemFactory.kt index e123d7f8..1ca81862 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TextItemFactory.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TextItemFactory.kt @@ -1,10 +1,10 @@ package im.vector.riotredesign.features.home.room.detail.timeline -import im.vector.matrix.android.api.session.events.model.EnrichedEvent +import im.vector.matrix.android.api.session.events.model.TimelineEvent class TextItemFactory { - fun create(event: EnrichedEvent): TextItem? { + fun create(event: TimelineEvent): TextItem? { val text = "${event.root.type} events are not yet handled" return TextItem(text = text) } diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt index 8f5d70cb..0c32025c 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt @@ -2,7 +2,7 @@ package im.vector.riotredesign.features.home.room.detail.timeline import com.airbnb.epoxy.EpoxyAsyncUtil import com.airbnb.epoxy.EpoxyModel -import im.vector.matrix.android.api.session.events.model.EnrichedEvent +import im.vector.matrix.android.api.session.events.model.TimelineEvent import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.room.timeline.TimelineData import im.vector.riotredesign.core.extensions.localDateTime @@ -13,7 +13,7 @@ class TimelineEventController(private val roomId: String, private val messageItemFactory: MessageItemFactory, private val textItemFactory: TextItemFactory, private val dateFormatter: TimelineDateFormatter -) : PagedListEpoxyController( +) : PagedListEpoxyController( EpoxyAsyncUtil.getAsyncBackgroundHandler(), EpoxyAsyncUtil.getAsyncBackgroundHandler() ) { @@ -38,7 +38,7 @@ class TimelineEventController(private val roomId: String, } - override fun buildItemModels(currentPosition: Int, items: List): List> { + override fun buildItemModels(currentPosition: Int, items: List): List> { if (items.isNullOrEmpty()) { return emptyList() } diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/paging/PagedListEpoxyController.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/paging/PagedListEpoxyController.kt index a01c2904..b0bdd5a9 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/paging/PagedListEpoxyController.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/paging/PagedListEpoxyController.kt @@ -70,6 +70,9 @@ abstract class PagedListEpoxyController( modelBuildingHandler = modelBuildingHandler ) + var currentList: PagedList? = null + private set + final override fun buildModels() { addModels(modelCache.getModels()) } @@ -107,6 +110,7 @@ abstract class PagedListEpoxyController( * to [buildItemModel] with items from the previous list. */ fun submitList(newList: PagedList?) { + currentList = newList modelCache.submitList(newList) } diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelineHolderTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelineHolderTest.kt index 38b465d8..11555dd2 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelineHolderTest.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelineHolderTest.kt @@ -7,7 +7,7 @@ import im.vector.matrix.android.InstrumentedTest import im.vector.matrix.android.LiveDataTestObserver import im.vector.matrix.android.api.thread.MainThreadExecutor import im.vector.matrix.android.internal.session.room.members.RoomMemberExtractor -import im.vector.matrix.android.internal.session.room.timeline.DefaultTimelineHolder +import im.vector.matrix.android.internal.session.room.timeline.DefaultTimelineService import im.vector.matrix.android.internal.session.room.timeline.TimelineBoundaryCallback import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEventPersistor import im.vector.matrix.android.internal.task.TaskExecutor @@ -44,17 +44,17 @@ internal class TimelineHolderTest : InstrumentedTest { val boundaryCallback = TimelineBoundaryCallback(roomId, taskExecutor, paginationTask, monarchy, PagingRequestHelper(MainThreadExecutor())) RoomDataHelper.fakeInitialSync(monarchy, roomId) - val timelineHolder = DefaultTimelineHolder(roomId, monarchy, taskExecutor, boundaryCallback, getContextOfEventTask, RoomMemberExtractor(monarchy, roomId)) + val timelineHolder = DefaultTimelineService(roomId, monarchy, taskExecutor, boundaryCallback, getContextOfEventTask, RoomMemberExtractor(monarchy, roomId)) val timelineObserver = LiveDataTestObserver.test(timelineHolder.timeline()) timelineObserver.awaitNextValue().assertHasValue() - var pagedList = timelineObserver.value() - pagedList.size shouldEqual 30 - (0 until pagedList.size).map { - pagedList.loadAround(it) + var timelineData = timelineObserver.value() + timelineData.events.size shouldEqual 30 + (0 until timelineData.events.size).map { + timelineData.events.loadAround(it) } timelineObserver.awaitNextValue().assertHasValue() - pagedList = timelineObserver.value() - pagedList.size shouldEqual 60 + timelineData = timelineObserver.value() + timelineData.events.size shouldEqual 60 } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt index cd4af401..d5652ef1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt @@ -15,4 +15,12 @@ interface Session : RoomService, GroupService { @MainThread fun close() + fun addListener(listener: Listener) + + fun removeListener(listener: Listener) + + // Not used at the moment + interface Listener + + } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/interceptor/EnrichedEventInterceptor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/interceptor/EnrichedEventInterceptor.kt deleted file mode 100644 index c781f29b..00000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/interceptor/EnrichedEventInterceptor.kt +++ /dev/null @@ -1,12 +0,0 @@ -package im.vector.matrix.android.api.session.events.interceptor - -import im.vector.matrix.android.api.session.events.model.EnrichedEvent - -interface EnrichedEventInterceptor { - - fun canEnrich(event: EnrichedEvent): Boolean - - fun enrich(event: EnrichedEvent) - -} - diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/interceptor/TimelineEventInterceptor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/interceptor/TimelineEventInterceptor.kt new file mode 100644 index 00000000..cf7df317 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/interceptor/TimelineEventInterceptor.kt @@ -0,0 +1,12 @@ +package im.vector.matrix.android.api.session.events.interceptor + +import im.vector.matrix.android.api.session.events.model.TimelineEvent + +interface TimelineEventInterceptor { + + fun canEnrich(event: TimelineEvent): Boolean + + fun enrich(event: TimelineEvent) + +} + diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EnrichedEvent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/TimelineEvent.kt similarity index 95% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EnrichedEvent.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/TimelineEvent.kt index 5cf64db9..97a5eced 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EnrichedEvent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/TimelineEvent.kt @@ -2,7 +2,7 @@ package im.vector.matrix.android.api.session.events.model import im.vector.matrix.android.api.session.room.model.RoomMember -data class EnrichedEvent( +data class TimelineEvent( val root: Event, val localId: String, val roomMember: RoomMember? diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineData.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineData.kt index 556c95f9..01727cd2 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineData.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineData.kt @@ -1,10 +1,10 @@ package im.vector.matrix.android.api.session.room.timeline import android.arch.paging.PagedList -import im.vector.matrix.android.api.session.events.model.EnrichedEvent +import im.vector.matrix.android.api.session.events.model.TimelineEvent data class TimelineData( - val events: PagedList, + val events: PagedList, val isLoadingForward: Boolean = false, val isLoadingBackward: Boolean = false ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/RealmLiveEntityObserver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/RealmLiveEntityObserver.kt index bc81a709..5f01f433 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/RealmLiveEntityObserver.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/RealmLiveEntityObserver.kt @@ -4,7 +4,6 @@ import android.arch.lifecycle.LiveData import android.arch.lifecycle.Observer import com.zhuinden.monarchy.Monarchy import io.realm.RealmObject -import io.realm.RealmResults import java.util.concurrent.atomic.AtomicBoolean internal interface LiveEntityObserver { @@ -39,11 +38,15 @@ internal abstract class RealmLiveEntityObserver(protected val m if (changeSet == null) { return } - val updateIndexes = changeSet.orderedCollectionChangeSet.changes + changeSet.orderedCollectionChangeSet.insertions + val insertionIndexes = changeSet.orderedCollectionChangeSet.insertions + val updateIndexes = changeSet.orderedCollectionChangeSet.changes val deletionIndexes = changeSet.orderedCollectionChangeSet.deletions - process(changeSet.realmResults, updateIndexes, deletionIndexes) + val inserted = changeSet.realmResults.filterIndexed { index, _ -> insertionIndexes.contains(index) } + val updated = changeSet.realmResults.filterIndexed { index, _ -> updateIndexes.contains(index) } + val deleted = changeSet.realmResults.filterIndexed { index, _ -> deletionIndexes.contains(index) } + process(inserted, updated, deleted) } - abstract fun process(results: RealmResults, updateIndexes: IntArray, deletionIndexes: IntArray) + abstract fun process(inserted: List, updated: List, deleted: List) } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt index 2d846d8b..2a723512 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt @@ -26,12 +26,13 @@ internal fun EventEntity.Companion.where(realm: Realm, query.equalTo(EventEntityFields.TYPE, type) } return when (linkFilterMode) { - LINKED_ONLY -> query.equalTo(EventEntityFields.IS_UNLINKED, false) + LINKED_ONLY -> query.equalTo(EventEntityFields.IS_UNLINKED, false) UNLINKED_ONLY -> query.equalTo(EventEntityFields.IS_UNLINKED, true) - BOTH -> query + BOTH -> query } } + internal fun RealmQuery.next(from: Int? = null, strict: Boolean = true): EventEntity? { if (from != null) { if (strict) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt index 2841f089..0c9356ed 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt @@ -31,6 +31,7 @@ internal class DefaultSession(override val sessionParams: SessionParams) : Sessi private lateinit var scope: Scope private val liveEntityUpdaters by inject>() + private val sessionListeners by inject() private val roomService by inject() private val groupService by inject() private val syncThread by inject() @@ -62,6 +63,14 @@ internal class DefaultSession(override val sessionParams: SessionParams) : Sessi isOpen = false } + override fun addListener(listener: Session.Listener) { + sessionListeners.addListener(listener) + } + + override fun removeListener(listener: Session.Listener) { + sessionListeners.removeListener(listener) + } + // ROOM SERVICE override fun getRoom(roomId: String): Room? { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionListeners.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionListeners.kt new file mode 100644 index 00000000..92c87ee4 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionListeners.kt @@ -0,0 +1,17 @@ +package im.vector.matrix.android.internal.session + +import im.vector.matrix.android.api.session.Session + +internal class SessionListeners { + + private val listeners = ArrayList() + + fun addListener(listener: Session.Listener) { + listeners.add(listener) + } + + fun removeListener(listener: Session.Listener) { + listeners.remove(listener) + } + +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt index 41644ad8..d36cbcbf 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt @@ -6,7 +6,6 @@ import im.vector.matrix.android.api.auth.data.SessionParams import im.vector.matrix.android.api.session.group.GroupService import im.vector.matrix.android.api.session.room.RoomService import im.vector.matrix.android.internal.database.LiveEntityObserver -import im.vector.matrix.android.internal.session.room.prune.EventsPruner import im.vector.matrix.android.internal.session.group.DefaultGroupService import im.vector.matrix.android.internal.session.group.GroupSummaryUpdater import im.vector.matrix.android.internal.session.room.DefaultRoomService @@ -14,6 +13,7 @@ import im.vector.matrix.android.internal.session.room.RoomAvatarResolver import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater import im.vector.matrix.android.internal.session.room.members.RoomDisplayNameResolver import im.vector.matrix.android.internal.session.room.members.RoomMemberDisplayNameResolver +import im.vector.matrix.android.internal.session.room.prune.EventsPruner import im.vector.matrix.android.internal.util.md5 import io.realm.RealmConfiguration import org.koin.dsl.module.module @@ -75,7 +75,10 @@ internal class SessionModule(private val sessionParams: SessionParams) { } scope(DefaultSession.SCOPE) { + SessionListeners() + } + scope(DefaultSession.SCOPE) { val roomSummaryUpdater = RoomSummaryUpdater(get(), get(), get(), get(), sessionParams.credentials) val groupSummaryUpdater = GroupSummaryUpdater(get()) val eventsPruner = EventsPruner(get()) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GetGroupDataWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GetGroupDataWorker.kt index fc5c0d1e..861e9640 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GetGroupDataWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GetGroupDataWorker.kt @@ -15,9 +15,7 @@ internal class GetGroupDataWorker(context: Context, @JsonClass(generateAdapter = true) internal data class Params( - val groupIds: List, - val updateIndexes: List, - val deletionIndexes: List + val groupIds: List ) private val getGroupDataTask by inject() @@ -26,8 +24,7 @@ internal class GetGroupDataWorker(context: Context, val params = WorkerParamsFactory.fromData(inputData) ?: return Result.failure() - val results = params.updateIndexes.map { index -> - val groupId = params.groupIds[index] + val results = params.groupIds.map { groupId -> fetchGroupData(groupId) } val isSuccessful = results.none { it.isFailure() } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupSummaryUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupSummaryUpdater.kt index be70a160..7d5d4f3d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupSummaryUpdater.kt @@ -1,12 +1,15 @@ package im.vector.matrix.android.internal.session.group -import androidx.work.* +import androidx.work.Constraints +import androidx.work.ExistingWorkPolicy +import androidx.work.NetworkType +import androidx.work.OneTimeWorkRequestBuilder +import androidx.work.WorkManager import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.internal.database.RealmLiveEntityObserver import im.vector.matrix.android.internal.database.model.GroupEntity import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.util.WorkerParamsFactory -import io.realm.RealmResults private const val GET_GROUP_DATA_WORKER = "GET_GROUP_DATA_WORKER" @@ -19,9 +22,9 @@ internal class GroupSummaryUpdater(monarchy: Monarchy .setRequiredNetworkType(NetworkType.CONNECTED) .build() - override fun process(results: RealmResults, updateIndexes: IntArray, deletionIndexes: IntArray) { - val groupIds = results.map { it.groupId } - val getGroupDataWorkerParams = GetGroupDataWorker.Params(groupIds, updateIndexes.toList(), deletionIndexes.toList()) + override fun process(inserted: List, updated: List, deleted: List) { + val newGroupIds = inserted.map { it.groupId } + val getGroupDataWorkerParams = GetGroupDataWorker.Params(newGroupIds) val workData = WorkerParamsFactory.toData(getGroupDataWorkerParams) val sendWork = OneTimeWorkRequestBuilder() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt index 4cf645a5..2359c301 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt @@ -2,24 +2,23 @@ package im.vector.matrix.android.internal.session.room import android.arch.lifecycle.LiveData import android.arch.lifecycle.Transformations -import android.arch.paging.PagedList import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.MatrixCallback -import im.vector.matrix.android.api.session.events.model.EnrichedEvent import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.room.Room import im.vector.matrix.android.api.session.room.SendService -import im.vector.matrix.android.api.session.room.timeline.TimelineService import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.MyMembership import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.timeline.TimelineData +import im.vector.matrix.android.api.session.room.timeline.TimelineService import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.di.MatrixKoinComponent +import im.vector.matrix.android.internal.session.SessionListeners import im.vector.matrix.android.internal.session.room.members.LoadRoomMembersTask import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith @@ -62,4 +61,5 @@ internal data class DefaultRoom( return sendService.sendTextMessage(text, callback) } + } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt index 37fe93c1..c0e9863c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt @@ -17,7 +17,6 @@ import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.session.room.members.RoomDisplayNameResolver import im.vector.matrix.android.internal.session.room.members.RoomMembers import io.realm.Realm -import io.realm.RealmResults import io.realm.kotlin.createObject internal class RoomSummaryUpdater(monarchy: Monarchy, @@ -29,13 +28,10 @@ internal class RoomSummaryUpdater(monarchy: Monarchy, override val query = Monarchy.Query { RoomEntity.where(it) } - override fun process(results: RealmResults, updateIndexes: IntArray, deletionIndexes: IntArray) { - val rooms = results.map { it.asDomain() } + override fun process(inserted: List, updated: List, deleted: List) { + val rooms = (inserted + updated).map { it.asDomain() } monarchy.writeAsync { realm -> - updateIndexes.forEach { index -> - val data = rooms[index] - updateRoom(realm, data) - } + rooms.forEach { updateRoom(realm, it) } } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/EventsPruner.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/EventsPruner.kt index a80b7ccc..ea5d8d64 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/EventsPruner.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/EventsPruner.kt @@ -10,7 +10,6 @@ import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.util.WorkerParamsFactory -import io.realm.RealmResults private const val PRUNE_EVENT_WORKER = "PRUNE_EVENT_WORKER" @@ -19,9 +18,9 @@ internal class EventsPruner(monarchy: Monarchy) : override val query = Monarchy.Query { EventEntity.where(it, type = EventType.REDACTION) } - override fun process(results: RealmResults, updateIndexes: IntArray, deletionIndexes: IntArray) { - val redactionEvents = results.map { it.asDomain() } - val pruneEventWorkerParams = PruneEventWorker.Params(redactionEvents, updateIndexes.toList(), deletionIndexes.toList()) + override fun process(inserted: List, updated: List, deleted: List) { + val redactionEvents = inserted.map { it.asDomain() } + val pruneEventWorkerParams = PruneEventWorker.Params(redactionEvents) val workData = WorkerParamsFactory.toData(pruneEventWorkerParams) val sendWork = OneTimeWorkRequestBuilder() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/PruneEventWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/PruneEventWorker.kt index 6562fd14..90e5cdc4 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/PruneEventWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/PruneEventWorker.kt @@ -13,6 +13,7 @@ import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.di.MatrixKoinComponent import im.vector.matrix.android.internal.util.WorkerParamsFactory import im.vector.matrix.android.internal.util.tryTransactionAsync +import im.vector.matrix.android.internal.util.tryTransactionSync import io.realm.Realm import org.koin.standalone.inject @@ -22,9 +23,7 @@ internal class PruneEventWorker(context: Context, @JsonClass(generateAdapter = true) internal data class Params( - val redactionEvents: List, - val updateIndexes: List, - val deletionIndexes: List + val redactionEvents: List ) private val monarchy by inject() @@ -33,10 +32,9 @@ internal class PruneEventWorker(context: Context, val params = WorkerParamsFactory.fromData(inputData) ?: return Result.failure() - val result = monarchy.tryTransactionAsync { realm -> - params.updateIndexes.forEach { index -> - val data = params.redactionEvents[index] - pruneEvent(realm, data) + val result = monarchy.tryTransactionSync { realm -> + params.redactionEvents.forEach { event -> + pruneEvent(realm, event) } } return result.fold({ Result.retry() }, { Result.success() }) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt index 6f5a80ad..2a06dbb4 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt @@ -4,8 +4,8 @@ import android.arch.lifecycle.LiveData import android.arch.paging.LivePagedListBuilder import android.arch.paging.PagedList import com.zhuinden.monarchy.Monarchy -import im.vector.matrix.android.api.session.events.interceptor.EnrichedEventInterceptor -import im.vector.matrix.android.api.session.events.model.EnrichedEvent +import im.vector.matrix.android.api.session.events.interceptor.TimelineEventInterceptor +import im.vector.matrix.android.api.session.events.model.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineData import im.vector.matrix.android.api.session.room.timeline.TimelineService import im.vector.matrix.android.internal.database.mapper.asDomain @@ -34,35 +34,21 @@ internal class DefaultTimelineService(private val roomId: String, private val roomMemberExtractor: RoomMemberExtractor ) : TimelineService { - private val eventInterceptors = ArrayList() + private val eventInterceptors = ArrayList() override fun timeline(eventId: String?): LiveData { clearUnlinkedEvents() - var initialLoadKey = 0 - if (eventId != null) { - val indexOfEvent = indexOfEvent(eventId) - if (indexOfEvent == EVENT_NOT_FOUND_INDEX) { - val params = GetContextOfEventTask.Params(roomId, eventId) - contextOfEventTask.configureWith(params).executeBy(taskExecutor) - } else { - initialLoadKey = indexOfEvent - } - } + val initialLoadKey = getInitialLoadKey(eventId) val realmDataSourceFactory = monarchy.createDataSourceFactory { buildDataSourceFactoryQuery(it, eventId) } val domainSourceFactory = realmDataSourceFactory .map { eventEntity -> val roomMember = roomMemberExtractor.extractFrom(eventEntity) - EnrichedEvent(eventEntity.asDomain(), eventEntity.localId, roomMember) + TimelineEvent(eventEntity.asDomain(), eventEntity.localId, roomMember) } - val pagedListConfig = PagedList.Config.Builder() - .setEnablePlaceholders(false) - .setPageSize(PAGE_SIZE) - .setInitialLoadSizeHint(2 * PAGE_SIZE) - .setPrefetchDistance(PREFETCH_DISTANCE) - .build() + val pagedListConfig = buildPagedListConfig() val livePagedListBuilder = LivePagedListBuilder(domainSourceFactory, pagedListConfig) .setBoundaryCallback(boundaryCallback) @@ -77,6 +63,35 @@ internal class DefaultTimelineService(private val roomId: String, } } + // PRIVATE FUNCTIONS *************************************************************************** + + private fun getInitialLoadKey(eventId: String?): Int { + var initialLoadKey = 0 + if (eventId != null) { + val indexOfEvent = indexOfEvent(eventId) + if (indexOfEvent == EVENT_NOT_FOUND_INDEX) { + fetchEvent(eventId) + } else { + initialLoadKey = indexOfEvent + } + } + return initialLoadKey + } + + + private fun fetchEvent(eventId: String) { + val params = GetContextOfEventTask.Params(roomId, eventId) + contextOfEventTask.configureWith(params).executeBy(taskExecutor) + } + + private fun buildPagedListConfig(): PagedList.Config { + return PagedList.Config.Builder() + .setEnablePlaceholders(false) + .setPageSize(PAGE_SIZE) + .setInitialLoadSizeHint(2 * PAGE_SIZE) + .setPrefetchDistance(PREFETCH_DISTANCE) + .build() + } private fun clearUnlinkedEvents() { monarchy.tryTransactionAsync { realm -> diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineBoundaryCallback.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineBoundaryCallback.kt index 2ce550f3..88282e85 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineBoundaryCallback.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineBoundaryCallback.kt @@ -4,7 +4,7 @@ import android.arch.lifecycle.LiveData import android.arch.paging.PagedList import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.MatrixCallback -import im.vector.matrix.android.api.session.events.model.EnrichedEvent +import im.vector.matrix.android.api.session.events.model.TimelineEvent import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.query.findIncludingEvent import im.vector.matrix.android.internal.task.TaskExecutor @@ -16,7 +16,7 @@ internal class TimelineBoundaryCallback(private val roomId: String, private val paginationTask: PaginationTask, private val monarchy: Monarchy, private val helper: PagingRequestHelper -) : PagedList.BoundaryCallback() { +) : PagedList.BoundaryCallback() { var limit = 30 @@ -41,7 +41,7 @@ internal class TimelineBoundaryCallback(private val roomId: String, // actually, it's not possible } - override fun onItemAtEndLoaded(itemAtEnd: EnrichedEvent) { + override fun onItemAtEndLoaded(itemAtEnd: TimelineEvent) { val token = itemAtEnd.root.eventId?.let { getToken(it, PaginationDirection.BACKWARDS) } ?: return @@ -50,7 +50,7 @@ internal class TimelineBoundaryCallback(private val roomId: String, } } - override fun onItemAtFrontLoaded(itemAtFront: EnrichedEvent) { + override fun onItemAtFrontLoaded(itemAtFront: TimelineEvent) { val token = itemAtFront.root.eventId?.let { getToken(it, PaginationDirection.FORWARDS) } ?: return From 0b539043b9e53dae44e113084a6ade52d0e55dfa Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 14 Jan 2019 16:46:16 +0100 Subject: [PATCH 25/25] Timeline : adjust message text size --- app/src/main/res/layout/item_event_message.xml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/layout/item_event_message.xml b/app/src/main/res/layout/item_event_message.xml index c4b94cd4..5b2cde0a 100644 --- a/app/src/main/res/layout/item_event_message.xml +++ b/app/src/main/res/layout/item_event_message.xml @@ -27,8 +27,7 @@ android:layout_marginEnd="8dp" android:ellipsize="end" android:maxLines="1" - android:paddingBottom="8dp" - android:textSize="16sp" + android:textSize="15sp" app:layout_constraintBottom_toTopOf="@+id/toolbarSubtitleView" app:layout_constraintEnd_toStartOf="@+id/messageTimeView" app:layout_constraintHorizontal_bias="0.0" @@ -55,7 +54,7 @@ android:layout_marginStart="64dp" android:layout_marginBottom="8dp" android:textColor="@color/dark_grey" - android:textSize="16sp" + android:textSize="14sp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent"