diff --git a/.idea/dictionaries/ganfra.xml b/.idea/dictionaries/ganfra.xml
index 7e1fdcddbb..728c63d678 100644
--- a/.idea/dictionaries/ganfra.xml
+++ b/.idea/dictionaries/ganfra.xml
@@ -3,9 +3,14 @@
connectable
coroutine
+ linkify
+ markon
+ markwon
merlins
moshi
persistor
+ restorable
+ restorables
synchronizer
untimelined
diff --git a/app/src/main/java/im/vector/riotredesign/Riot.kt b/app/src/main/java/im/vector/riotredesign/Riot.kt
index 8385443ce8..9a82a20fec 100644
--- a/app/src/main/java/im/vector/riotredesign/Riot.kt
+++ b/app/src/main/java/im/vector/riotredesign/Riot.kt
@@ -23,6 +23,7 @@ import com.facebook.stetho.Stetho
import com.jakewharton.threetenabp.AndroidThreeTen
import im.vector.matrix.android.BuildConfig
import im.vector.riotredesign.core.di.AppModule
+import im.vector.riotredesign.features.home.HomeModule
import org.koin.log.EmptyLogger
import org.koin.standalone.StandAloneContext.startKoin
import timber.log.Timber
@@ -37,7 +38,9 @@ class Riot : Application() {
Stetho.initializeWithDefaults(this)
}
AndroidThreeTen.init(this)
- startKoin(listOf(AppModule(this).definition), logger = EmptyLogger())
+ val appModule = AppModule(applicationContext).definition
+ val homeModule = HomeModule().definition
+ startKoin(listOf(appModule, homeModule), logger = EmptyLogger())
}
override fun attachBaseContext(base: Context) {
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 bbc973152c..f67c9c11d8 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
@@ -18,10 +18,14 @@ package im.vector.riotredesign.core.di
import android.content.Context
import android.content.Context.MODE_PRIVATE
+import im.vector.matrix.android.api.Matrix
import im.vector.riotredesign.core.resources.ColorProvider
import im.vector.riotredesign.core.resources.LocaleProvider
import im.vector.riotredesign.core.resources.StringProvider
+import im.vector.riotredesign.features.home.group.SelectedGroupStore
+import im.vector.riotredesign.features.home.room.VisibleRoomStore
import im.vector.riotredesign.features.home.room.list.RoomSelectionRepository
+import im.vector.riotredesign.features.home.room.list.RoomSummaryComparator
import org.koin.dsl.module.module
class AppModule(private val context: Context) {
@@ -48,5 +52,22 @@ class AppModule(private val context: Context) {
RoomSelectionRepository(get())
}
+ single {
+ SelectedGroupStore()
+ }
+
+ single {
+ VisibleRoomStore()
+ }
+
+ single {
+ RoomSummaryComparator()
+ }
+
+ factory {
+ Matrix.getInstance().currentSession
+ }
+
+
}
}
\ No newline at end of file
diff --git a/app/src/main/java/im/vector/riotredesign/core/epoxy/LayoutManagerStateRestorer.kt b/app/src/main/java/im/vector/riotredesign/core/epoxy/LayoutManagerStateRestorer.kt
new file mode 100644
index 0000000000..b4b9f181af
--- /dev/null
+++ b/app/src/main/java/im/vector/riotredesign/core/epoxy/LayoutManagerStateRestorer.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2019 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.riotredesign.core.epoxy
+
+import android.os.Bundle
+import android.os.Parcelable
+import androidx.recyclerview.widget.RecyclerView
+import im.vector.riotredesign.core.platform.DefaultListUpdateCallback
+import im.vector.riotredesign.core.platform.Restorable
+import java.util.concurrent.atomic.AtomicReference
+
+private const val LAYOUT_MANAGER_STATE = "LAYOUT_MANAGER_STATE"
+
+class LayoutManagerStateRestorer(private val layoutManager: RecyclerView.LayoutManager) : Restorable, DefaultListUpdateCallback {
+
+ private var layoutManagerState = AtomicReference()
+
+ override fun onSaveInstanceState(outState: Bundle) {
+ val layoutManagerState = layoutManager.onSaveInstanceState()
+ outState.putParcelable(LAYOUT_MANAGER_STATE, layoutManagerState)
+ }
+
+ override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
+ val parcelable = savedInstanceState?.getParcelable(LAYOUT_MANAGER_STATE)
+ layoutManagerState.set(parcelable)
+ }
+
+ override fun onInserted(position: Int, count: Int) {
+ layoutManagerState.getAndSet(null)?.also {
+ layoutManager.onRestoreInstanceState(it)
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/im/vector/riotredesign/core/platform/Restorable.kt b/app/src/main/java/im/vector/riotredesign/core/platform/Restorable.kt
new file mode 100644
index 0000000000..683fed732f
--- /dev/null
+++ b/app/src/main/java/im/vector/riotredesign/core/platform/Restorable.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2019 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.riotredesign.core.platform
+
+import android.os.Bundle
+
+interface Restorable {
+
+ fun onSaveInstanceState(outState: Bundle)
+
+ fun onRestoreInstanceState(savedInstanceState: Bundle?)
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/im/vector/riotredesign/core/platform/RiotActivity.kt b/app/src/main/java/im/vector/riotredesign/core/platform/RiotActivity.kt
index 2465399d2f..db1aa476fb 100644
--- a/app/src/main/java/im/vector/riotredesign/core/platform/RiotActivity.kt
+++ b/app/src/main/java/im/vector/riotredesign/core/platform/RiotActivity.kt
@@ -16,13 +16,34 @@
package im.vector.riotredesign.core.platform
+import android.os.Bundle
+import androidx.annotation.MainThread
import com.airbnb.mvrx.BaseMvRxActivity
+import com.bumptech.glide.util.Util
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable
abstract class RiotActivity : BaseMvRxActivity() {
private val uiDisposables = CompositeDisposable()
+ private val restorables = ArrayList()
+
+ override fun onSaveInstanceState(outState: Bundle) {
+ super.onSaveInstanceState(outState)
+ restorables.forEach { it.onSaveInstanceState(outState) }
+ }
+
+ override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
+ restorables.forEach { it.onRestoreInstanceState(savedInstanceState) }
+ super.onRestoreInstanceState(savedInstanceState)
+ }
+
+ @MainThread
+ protected fun T.register(): T {
+ Util.assertMainThread()
+ restorables.add(this)
+ return this
+ }
protected fun Disposable.disposeOnDestroy(): Disposable {
uiDisposables.add(this)
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 72a7f4b291..0b7996fa29 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
@@ -18,8 +18,10 @@ package im.vector.riotredesign.core.platform
import android.os.Bundle
import android.os.Parcelable
+import androidx.annotation.MainThread
import com.airbnb.mvrx.BaseMvRxFragment
import com.airbnb.mvrx.MvRx
+import com.bumptech.glide.util.Util.assertMainThread
abstract class RiotFragment : BaseMvRxFragment(), OnBackPressed {
@@ -27,6 +29,18 @@ abstract class RiotFragment : BaseMvRxFragment(), OnBackPressed {
activity as RiotActivity
}
+ private val restorables = ArrayList()
+
+ override fun onSaveInstanceState(outState: Bundle) {
+ super.onSaveInstanceState(outState)
+ restorables.forEach { it.onSaveInstanceState(outState) }
+ }
+
+ override fun onViewStateRestored(savedInstanceState: Bundle?) {
+ restorables.forEach { it.onRestoreInstanceState(savedInstanceState) }
+ super.onViewStateRestored(savedInstanceState)
+ }
+
override fun onBackPressed(): Boolean {
return false
}
@@ -39,4 +53,11 @@ abstract class RiotFragment : BaseMvRxFragment(), OnBackPressed {
arguments = args?.let { Bundle().apply { putParcelable(MvRx.KEY_ARG, it) } }
}
+ @MainThread
+ protected fun T.register(): T {
+ assertMainThread()
+ restorables.add(this)
+ return this
+ }
+
}
\ No newline at end of file
diff --git a/app/src/main/java/im/vector/riotredesign/features/home/AvatarRenderer.kt b/app/src/main/java/im/vector/riotredesign/features/home/AvatarRenderer.kt
index 3227dc6502..275d76136e 100644
--- a/app/src/main/java/im/vector/riotredesign/features/home/AvatarRenderer.kt
+++ b/app/src/main/java/im/vector/riotredesign/features/home/AvatarRenderer.kt
@@ -19,74 +19,98 @@ package im.vector.riotredesign.features.home
import android.content.Context
import android.graphics.drawable.Drawable
import android.widget.ImageView
+import androidx.annotation.UiThread
+import androidx.annotation.WorkerThread
import androidx.core.content.ContextCompat
import com.amulyakhare.textdrawable.TextDrawable
+import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.RequestOptions
+import com.bumptech.glide.request.target.DrawableImageViewTarget
+import com.bumptech.glide.request.target.Target
import im.vector.matrix.android.api.Matrix
import im.vector.matrix.android.api.MatrixPatterns
+import im.vector.matrix.android.api.session.content.ContentUrlResolver
import im.vector.matrix.android.api.session.room.model.RoomMember
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.riotredesign.R
import im.vector.riotredesign.core.glide.GlideApp
import im.vector.riotredesign.core.glide.GlideRequest
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.launch
+import im.vector.riotredesign.core.glide.GlideRequests
+/**
+ * This helper centralise ways to retrieve avatar into ImageView or even generic Target
+ */
object AvatarRenderer {
+ @UiThread
fun render(roomMember: RoomMember, imageView: ImageView) {
render(roomMember.avatarUrl, roomMember.displayName, imageView)
}
+ @UiThread
fun render(roomSummary: RoomSummary, imageView: ImageView) {
render(roomSummary.avatarUrl, roomSummary.displayName, imageView)
}
+ @UiThread
fun render(avatarUrl: String?, name: String?, imageView: ImageView) {
+ render(imageView.context, GlideApp.with(imageView), avatarUrl, name, imageView.height, DrawableImageViewTarget(imageView))
+ }
+
+ @UiThread
+ fun render(context: Context,
+ glideRequest: GlideRequests,
+ avatarUrl: String?,
+ name: String?,
+ size: Int,
+ target: Target) {
if (name.isNullOrEmpty()) {
return
}
- val placeholder = buildPlaceholderDrawable(imageView.context, name)
- buildGlideRequest(imageView.context, avatarUrl)
+ val placeholder = buildPlaceholderDrawable(context, name)
+ buildGlideRequest(glideRequest, avatarUrl, size)
.placeholder(placeholder)
- .into(imageView)
+ .into(target)
}
- fun load(context: Context, avatarUrl: String?, name: String?, size: Int, callback: Callback) {
- if (name.isNullOrEmpty()) {
- return
- }
- val request = buildGlideRequest(context, avatarUrl)
- GlobalScope.launch {
- val placeholder = buildPlaceholderDrawable(context, name)
- callback.onDrawableUpdated(placeholder)
- try {
- val drawable = request.submit(size, size).get()
- callback.onDrawableUpdated(drawable)
- } catch (exception: Exception) {
- callback.onDrawableUpdated(placeholder)
- }
+ @WorkerThread
+ fun getCachedOrPlaceholder(context: Context,
+ glideRequest: GlideRequests,
+ avatarUrl: String?,
+ text: String,
+ size: Int): Drawable {
+ val future = buildGlideRequest(glideRequest, avatarUrl, size).onlyRetrieveFromCache(true).submit()
+ return try {
+ future.get()
+ } catch (exception: Exception) {
+ buildPlaceholderDrawable(context, text)
}
}
- private fun buildGlideRequest(context: Context, avatarUrl: String?): GlideRequest {
- val resolvedUrl = Matrix.getInstance().currentSession.contentUrlResolver().resolveFullSize(avatarUrl)
- return GlideApp
- .with(context)
+ // PRIVATE API *********************************************************************************
+
+ private fun buildGlideRequest(glideRequest: GlideRequests, avatarUrl: String?, size: Int): GlideRequest {
+ val resolvedUrl = Matrix.getInstance().currentSession
+ .contentUrlResolver()
+ .resolveThumbnail(avatarUrl, size, size, ContentUrlResolver.ThumbnailMethod.SCALE)
+
+ return glideRequest
.load(resolvedUrl)
.apply(RequestOptions.circleCropTransform())
+ .diskCacheStrategy(DiskCacheStrategy.DATA)
}
- private fun buildPlaceholderDrawable(context: Context, name: String): Drawable {
+ private fun buildPlaceholderDrawable(context: Context, text: String): Drawable {
val avatarColor = ContextCompat.getColor(context, R.color.pale_teal)
- val isNameUserId = MatrixPatterns.isUserId(name)
- val firstLetterIndex = if (isNameUserId) 1 else 0
- val firstLetter = name[firstLetterIndex].toString().toUpperCase()
- return TextDrawable.builder().buildRound(firstLetter, avatarColor)
- }
+ return if (text.isEmpty()) {
+ TextDrawable.builder().buildRound("", avatarColor)
+ } else {
+ val isUserId = MatrixPatterns.isUserId(text)
+ val firstLetterIndex = if (isUserId) 1 else 0
+ val firstLetter = text[firstLetterIndex].toString().toUpperCase()
+ TextDrawable.builder().buildRound(firstLetter, avatarColor)
+ }
- interface Callback {
- fun onDrawableUpdated(drawable: Drawable?)
}
}
\ 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 12842034f8..db37dfc03f 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
@@ -37,12 +37,12 @@ import im.vector.riotredesign.core.platform.ToolbarConfigurable
import im.vector.riotredesign.features.home.room.detail.LoadingRoomDetailFragment
import kotlinx.android.synthetic.main.activity_home.*
import org.koin.android.ext.android.inject
-import org.koin.standalone.StandAloneContext.loadKoinModules
+import org.koin.android.scope.ext.android.bindScope
+import org.koin.android.scope.ext.android.getOrCreateScope
class HomeActivity : RiotActivity(), ToolbarConfigurable {
-
private val homeActivityViewModel: HomeActivityViewModel by viewModel()
private val homeNavigator by inject()
@@ -53,10 +53,10 @@ class HomeActivity : RiotActivity(), ToolbarConfigurable {
}
override fun onCreate(savedInstanceState: Bundle?) {
- loadKoinModules(listOf(HomeModule(this).definition))
- homeNavigator.activity = this
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_home)
+ bindScope(getOrCreateScope(HomeModule.HOME_SCOPE))
+ homeNavigator.activity = this
drawerLayout.addDrawerListener(drawerListener)
if (savedInstanceState == null) {
val homeDrawerFragment = HomeDrawerFragment.newInstance()
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 2862765708..04a93d666b 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
@@ -16,9 +16,9 @@
package im.vector.riotredesign.features.home
-import im.vector.matrix.android.api.Matrix
-import im.vector.riotredesign.features.home.group.SelectedGroupStore
-import im.vector.riotredesign.features.home.room.VisibleRoomStore
+import androidx.fragment.app.Fragment
+import im.vector.riotredesign.core.glide.GlideApp
+import im.vector.riotredesign.features.home.group.GroupSummaryController
import im.vector.riotredesign.features.home.room.detail.timeline.CallItemFactory
import im.vector.riotredesign.features.home.room.detail.timeline.DefaultItemFactory
import im.vector.riotredesign.features.home.room.detail.timeline.MessageItemFactory
@@ -30,89 +30,56 @@ import im.vector.riotredesign.features.home.room.detail.timeline.TimelineDateFor
import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController
import im.vector.riotredesign.features.home.room.detail.timeline.TimelineItemFactory
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
-import im.vector.riotredesign.features.home.room.list.RoomSummaryComparator
import im.vector.riotredesign.features.home.room.list.RoomSummaryController
import im.vector.riotredesign.features.html.EventHtmlRenderer
import org.koin.dsl.module.module
-class HomeModule(homeActivity: HomeActivity) {
+class HomeModule {
- val definition = module(override = true) {
+ companion object {
+ const val HOME_SCOPE = "HOME_SCOPE"
+ const val ROOM_DETAIL_SCOPE = "ROOM_DETAIL_SCOPE"
+ const val ROOM_LIST_SCOPE = "ROOM_LIST_SCOPE"
+ const val GROUP_LIST_SCOPE = "GROUP_LIST_SCOPE"
+ }
- single {
- Matrix.getInstance().currentSession
- }
+ val definition = module {
- single {
- TimelineDateFormatter(get())
- }
+ // Activity scope
- single {
- EventHtmlRenderer(homeActivity, get())
- }
-
- single {
- MessageItemFactory(get(), get(), get(), get())
- }
-
- single {
- RoomNameItemFactory(get())
- }
-
- single {
- RoomTopicItemFactory(get())
- }
-
- single {
- RoomMemberItemFactory(get())
- }
-
- single {
- CallItemFactory(get())
- }
-
- single {
- RoomHistoryVisibilityItemFactory(get())
- }
-
- single {
- DefaultItemFactory()
- }
-
- single {
- TimelineItemFactory(get(), get(), get(), get(), get(), get(), get())
- }
-
- single {
+ scope(HOME_SCOPE) {
HomeNavigator()
}
- factory {
- RoomSummaryController(get())
- }
-
- factory { (roomId: String) ->
- TimelineEventController(roomId, get(), get(), get())
- }
-
- single {
- TimelineMediaSizeProvider()
- }
-
- single {
- SelectedGroupStore()
- }
-
- single {
- VisibleRoomStore()
- }
-
- single {
+ scope(HOME_SCOPE) {
HomePermalinkHandler(get())
}
- single {
- RoomSummaryComparator()
+ // Fragment scopes
+
+ scope(ROOM_DETAIL_SCOPE) { (fragment: Fragment) ->
+ val eventHtmlRenderer = EventHtmlRenderer(GlideApp.with(fragment), fragment.requireContext(), get())
+ val timelineDateFormatter = TimelineDateFormatter(get())
+ val timelineMediaSizeProvider = TimelineMediaSizeProvider()
+ val messageItemFactory = MessageItemFactory(get(), timelineMediaSizeProvider, timelineDateFormatter, eventHtmlRenderer)
+
+ val timelineItemFactory = TimelineItemFactory(messageItemFactory = messageItemFactory,
+ roomNameItemFactory = RoomNameItemFactory(get()),
+ roomTopicItemFactory = RoomTopicItemFactory(get()),
+ roomMemberItemFactory = RoomMemberItemFactory(get()),
+ roomHistoryVisibilityItemFactory = RoomHistoryVisibilityItemFactory(get()),
+ callItemFactory = CallItemFactory(get()),
+ defaultItemFactory = DefaultItemFactory()
+ )
+ TimelineEventController(timelineDateFormatter, timelineItemFactory, timelineMediaSizeProvider)
+ }
+
+ scope(ROOM_LIST_SCOPE) {
+ RoomSummaryController(get())
+ }
+
+ scope(GROUP_LIST_SCOPE) {
+ GroupSummaryController()
}
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 4fb84ea10d..f6583b97a4 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
@@ -36,9 +36,6 @@ class HomeNavigator {
eventId: String?,
addToBackstack: Boolean = false) {
Timber.v("Open room detail $roomId - $eventId - $addToBackstack")
- if (!addToBackstack && isRoot(roomId)) {
- return
- }
activity?.let {
val args = RoomDetailArgs(roomId, eventId)
val roomDetailFragment = RoomDetailFragment.newInstance(args)
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 30b6a6d08a..fb99e839ed 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
@@ -27,7 +27,11 @@ 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.HomeModule
import kotlinx.android.synthetic.main.fragment_group_list.*
+import org.koin.android.ext.android.inject
+import org.koin.android.scope.ext.android.bindScope
+import org.koin.android.scope.ext.android.getOrCreateScope
class GroupListFragment : RiotFragment(), GroupSummaryController.Callback {
@@ -38,8 +42,7 @@ class GroupListFragment : RiotFragment(), GroupSummaryController.Callback {
}
private val viewModel: GroupListViewModel by fragmentViewModel()
-
- private lateinit var groupController: GroupSummaryController
+ private val groupController by inject()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_group_list, container, false)
@@ -47,7 +50,8 @@ class GroupListFragment : RiotFragment(), GroupSummaryController.Callback {
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
- groupController = GroupSummaryController(this)
+ bindScope(getOrCreateScope(HomeModule.GROUP_LIST_SCOPE))
+ groupController.callback = this
stateView.contentView = epoxyRecyclerView
epoxyRecyclerView.setController(groupController)
viewModel.subscribe { renderState(it) }
@@ -56,7 +60,7 @@ class GroupListFragment : RiotFragment(), GroupSummaryController.Callback {
private fun renderState(state: GroupListViewState) {
when (state.asyncGroups) {
is Incomplete -> renderLoading()
- is Success -> renderSuccess(state)
+ is Success -> renderSuccess(state)
}
}
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
index f35c46fc10..1ab01369df 100644
--- 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
@@ -19,7 +19,6 @@ package im.vector.riotredesign.features.home.group
import arrow.core.Option
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext
-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
@@ -34,7 +33,7 @@ class GroupListViewModel(initialState: GroupListViewState,
@JvmStatic
override fun create(viewModelContext: ViewModelContext, state: GroupListViewState): GroupListViewModel? {
- val currentSession = Matrix.getInstance().currentSession
+ val currentSession = viewModelContext.activity.get()
val selectedGroupHolder = viewModelContext.activity.get()
return GroupListViewModel(state, selectedGroupHolder, currentSession)
}
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 3f24f9e0f4..0fc0c9d4d5 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
@@ -19,8 +19,9 @@ package im.vector.riotredesign.features.home.group
import com.airbnb.epoxy.TypedEpoxyController
import im.vector.matrix.android.api.session.group.model.GroupSummary
-class GroupSummaryController(private val callback: Callback? = null
-) : TypedEpoxyController() {
+class GroupSummaryController : TypedEpoxyController() {
+
+ var callback: Callback? = null
override fun buildModels(viewState: GroupListViewState) {
buildGroupModels(viewState.asyncGroups(), viewState.selectedGroup)
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 76f91c7b28..faf792d706 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
@@ -25,18 +25,21 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.airbnb.epoxy.EpoxyVisibilityTracker
import com.airbnb.mvrx.Success
-import com.airbnb.mvrx.args
import com.airbnb.mvrx.fragmentViewModel
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.riotredesign.R
+import im.vector.riotredesign.core.epoxy.LayoutManagerStateRestorer
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.HomeModule
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.android.scope.ext.android.bindScope
+import org.koin.android.scope.ext.android.getOrCreateScope
import org.koin.core.parameter.parametersOf
@Parcelize
@@ -45,6 +48,7 @@ data class RoomDetailArgs(
val eventId: String? = null
) : Parcelable
+
class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback {
companion object {
@@ -57,10 +61,9 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback {
}
private val roomDetailViewModel: RoomDetailViewModel by fragmentViewModel()
- private val roomDetailArgs: RoomDetailArgs by args()
+ private val timelineEventController: TimelineEventController by inject { parametersOf(this) }
+ private val homePermalinkHandler: HomePermalinkHandler by inject()
- 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? {
@@ -69,6 +72,7 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback {
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
+ bindScope(getOrCreateScope(HomeModule.ROOM_DETAIL_SCOPE))
setupRecyclerView()
setupToolbar()
setupSendButton()
@@ -80,6 +84,8 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback {
roomDetailViewModel.process(RoomDetailActions.IsDisplayed)
}
+ // PRIVATE METHODS *****************************************************************************
+
private fun setupToolbar() {
val parentActivity = riotActivity
if (parentActivity is ToolbarConfigurable) {
@@ -91,10 +97,14 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback {
val epoxyVisibilityTracker = EpoxyVisibilityTracker()
epoxyVisibilityTracker.attach(recyclerView)
val layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, true)
+ val stateRestorer = LayoutManagerStateRestorer(layoutManager).register()
scrollOnNewMessageCallback = ScrollOnNewMessageCallback(layoutManager)
recyclerView.layoutManager = layoutManager
recyclerView.setHasFixedSize(true)
- timelineEventController.addModelBuildListener { it.dispatchTo(scrollOnNewMessageCallback) }
+ timelineEventController.addModelBuildListener {
+ it.dispatchTo(stateRestorer)
+ it.dispatchTo(scrollOnNewMessageCallback)
+ }
recyclerView.setController(timelineEventController)
timelineEventController.callback = this
}
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 3ce30c0e59..4ca21b94ec 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
@@ -19,7 +19,6 @@ package im.vector.riotredesign.features.home.room.detail
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext
import com.jakewharton.rxrelay2.BehaviorRelay
-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
@@ -46,7 +45,7 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
@JvmStatic
override fun create(viewModelContext: ViewModelContext, state: RoomDetailViewState): RoomDetailViewModel? {
- val currentSession = Matrix.getInstance().currentSession
+ val currentSession = viewModelContext.activity.get()
val visibleRoomHolder = viewModelContext.activity.get()
return RoomDetailViewModel(state, currentSession, visibleRoomHolder)
}
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 d27475f77b..b31ccf940d 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
@@ -16,13 +16,18 @@
package im.vector.riotredesign.features.home.room.detail.timeline
+import android.text.Spannable
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.EventType
import im.vector.matrix.android.api.session.events.model.toModel
-import im.vector.matrix.android.api.session.room.model.message.*
+import im.vector.matrix.android.api.session.room.model.message.MessageContent
+import im.vector.matrix.android.api.session.room.model.message.MessageEmoteContent
+import im.vector.matrix.android.api.session.room.model.message.MessageImageContent
+import im.vector.matrix.android.api.session.room.model.message.MessageNoticeContent
+import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.riotredesign.R
import im.vector.riotredesign.core.epoxy.RiotEpoxyModel
@@ -146,7 +151,7 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
.informationData(informationData)
}
- private fun linkifyBody(body: CharSequence, callback: TimelineEventController.Callback?): CharSequence {
+ private fun linkifyBody(body: CharSequence, callback: TimelineEventController.Callback?): Spannable {
val spannable = SpannableStringBuilder(body)
MatrixLinkify.addLinks(spannable, object : MatrixPermalinkSpan.Callback {
override fun onUrlClicked(url: String) {
diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageTextItem.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageTextItem.kt
index 8265313dc7..9a0bc8ba6f 100644
--- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageTextItem.kt
+++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageTextItem.kt
@@ -16,30 +16,54 @@
package im.vector.riotredesign.features.home.room.detail.timeline
+import android.text.Spannable
import android.widget.ImageView
import android.widget.TextView
+import androidx.appcompat.widget.AppCompatTextView
+import androidx.core.text.PrecomputedTextCompat
+import androidx.core.widget.TextViewCompat
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.matrix.android.api.permalinks.MatrixLinkify
import im.vector.riotredesign.R
+import im.vector.riotredesign.features.html.PillImageSpan
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
@EpoxyModelClass(layout = R.layout.item_timeline_event_text_message)
abstract class MessageTextItem : AbsMessageItem() {
- @EpoxyAttribute var message: CharSequence? = null
+ @EpoxyAttribute var message: Spannable? = null
@EpoxyAttribute override lateinit var informationData: MessageInformationData
override fun bind(holder: Holder) {
super.bind(holder)
- holder.messageView.text = message
MatrixLinkify.addLinkMovementMethod(holder.messageView)
+ val textFuture = PrecomputedTextCompat.getTextFuture(message ?: "",
+ TextViewCompat.getTextMetricsParams(holder.messageView),
+ null)
+ holder.messageView.setTextFuture(textFuture)
+ findPillsAndProcess { it.bind(holder.messageView) }
+ }
+
+ private fun findPillsAndProcess(processBlock: (span: PillImageSpan) -> Unit) {
+ GlobalScope.launch(Dispatchers.Main) {
+ val pillImageSpans: Array? = withContext(Dispatchers.IO) {
+ message?.let { spannable ->
+ spannable.getSpans(0, spannable.length, PillImageSpan::class.java)
+ }
+ }
+ pillImageSpans?.forEach { processBlock(it) }
+ }
}
class Holder : AbsMessageItem.Holder() {
override val avatarImageView by bind(R.id.messageAvatarImageView)
override val memberNameView by bind(R.id.messageMemberNameView)
override val timeView by bind(R.id.messageTimeView)
- val messageView by bind(R.id.messageTextView)
+ val messageView by bind(R.id.messageTextView)
}
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 ec865e4b8b..883476de78 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
@@ -29,8 +29,7 @@ import im.vector.riotredesign.core.extensions.localDateTime
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
import im.vector.riotredesign.features.home.room.detail.timeline.paging.PagedListEpoxyController
-class TimelineEventController(private val roomId: String,
- private val dateFormatter: TimelineDateFormatter,
+class TimelineEventController(private val dateFormatter: TimelineDateFormatter,
private val timelineItemFactory: TimelineItemFactory,
private val timelineMediaSizeProvider: TimelineMediaSizeProvider
) : PagedListEpoxyController(
@@ -82,7 +81,7 @@ class TimelineEventController(private val roomId: String,
}
if (addDaySeparator) {
val formattedDay = dateFormatter.formatMessageDay(date)
- val daySeparatorItem = DaySeparatorItem_().formattedDay(formattedDay).id(roomId + formattedDay)
+ val daySeparatorItem = DaySeparatorItem_().formattedDay(formattedDay).id(formattedDay)
epoxyModels.add(daySeparatorItem)
}
return epoxyModels
@@ -90,13 +89,13 @@ class TimelineEventController(private val roomId: String,
override fun addModels(models: List>) {
LoadingItemModel_()
- .id(roomId + "forward_loading_item")
+ .id("forward_loading_item")
.addIf(isLoadingForward, this)
super.add(models)
LoadingItemModel_()
- .id(roomId + "backward_loading_item")
+ .id("backward_loading_item")
.addIf(!hasReachedEnd, this)
}
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
index efc8b6641f..dc9c393b37 100644
--- 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
@@ -22,8 +22,8 @@ sealed class RoomListActions {
data class SelectRoom(val roomSummary: RoomSummary) : RoomListActions()
- object RoomDisplayed : RoomListActions()
-
data class FilterRooms(val roomName: CharSequence? = null) : RoomListActions()
+ data class ToggleCategory(val category: RoomCategory) : 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 904cf66ca1..deeba711b6 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
@@ -22,19 +22,25 @@ import android.text.TextWatcher
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import androidx.recyclerview.widget.LinearLayoutManager
import com.airbnb.mvrx.Fail
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.failure.Failure
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.riotredesign.R
+import im.vector.riotredesign.core.epoxy.LayoutManagerStateRestorer
+import im.vector.riotredesign.core.extensions.observeEvent
import im.vector.riotredesign.core.extensions.setupAsSearch
import im.vector.riotredesign.core.platform.RiotFragment
import im.vector.riotredesign.core.platform.StateView
+import im.vector.riotredesign.features.home.HomeModule
import im.vector.riotredesign.features.home.HomeNavigator
import kotlinx.android.synthetic.main.fragment_room_list.*
import org.koin.android.ext.android.inject
+import org.koin.android.scope.ext.android.bindScope
+import org.koin.android.scope.ext.android.getOrCreateScope
class RoomListFragment : RiotFragment(), RoomSummaryController.Callback {
@@ -44,9 +50,9 @@ class RoomListFragment : RiotFragment(), RoomSummaryController.Callback {
}
}
- private val homeNavigator by inject()
private val roomController by inject()
- private val homeViewModel: RoomListViewModel by activityViewModel()
+ private val homeNavigator by inject()
+ private val roomListViewModel: RoomListViewModel by fragmentViewModel()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_room_list, container, false)
@@ -54,11 +60,36 @@ class RoomListFragment : RiotFragment(), RoomSummaryController.Callback {
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
+ bindScope(getOrCreateScope(HomeModule.ROOM_LIST_SCOPE))
+ setupRecyclerView()
+ setupFilterView()
+ roomListViewModel.subscribe { renderState(it) }
+ roomListViewModel.openRoomLiveData.observeEvent(this) {
+ homeNavigator.openRoomDetail(it, null)
+ }
+ }
+
+ private fun setupRecyclerView() {
+ val layoutManager = LinearLayoutManager(context)
+ val stateRestorer = LayoutManagerStateRestorer(layoutManager).register()
+ epoxyRecyclerView.layoutManager = layoutManager
roomController.callback = this
+ roomController.addModelBuildListener { it.dispatchTo(stateRestorer) }
stateView.contentView = epoxyRecyclerView
epoxyRecyclerView.setController(roomController)
- setupFilterView()
- homeViewModel.subscribe { renderState(it) }
+ }
+
+ private fun setupFilterView() {
+ filterRoomView.setupAsSearch()
+ filterRoomView.addTextChangedListener(object : TextWatcher {
+ override fun afterTextChanged(s: Editable?) = Unit
+
+ override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit
+
+ override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
+ roomListViewModel.accept(RoomListActions.FilterRooms(s))
+ }
+ })
}
private fun renderState(state: RoomListViewState) {
@@ -90,24 +121,13 @@ class RoomListFragment : RiotFragment(), RoomSummaryController.Callback {
stateView.state = StateView.State.Error(message)
}
- private fun setupFilterView() {
- filterRoomView.setupAsSearch()
- filterRoomView.addTextChangedListener(object : TextWatcher {
- override fun afterTextChanged(s: Editable?) = Unit
-
- override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit
-
- override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
- homeViewModel.accept(RoomListActions.FilterRooms(s))
- }
- })
- }
-
// RoomSummaryController.Callback **************************************************************
override fun onRoomSelected(room: RoomSummary) {
- homeViewModel.accept(RoomListActions.SelectRoom(room))
- homeNavigator.openRoomDetail(room.roomId, null)
+ roomListViewModel.accept(RoomListActions.SelectRoom(room))
}
+ override fun onToggleRoomCategory(roomCategory: RoomCategory) {
+ roomListViewModel.accept(RoomListActions.ToggleCategory(roomCategory))
+ }
}
\ 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
index 0612016c06..f52134323d 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
@@ -16,22 +16,23 @@
package im.vector.riotredesign.features.home.room.list
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
import arrow.core.Option
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext
import com.jakewharton.rxrelay2.BehaviorRelay
-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.android.api.session.room.model.tag.RoomTag
import im.vector.matrix.rx.rx
import im.vector.riotredesign.core.platform.RiotViewModel
+import im.vector.riotredesign.core.utils.LiveEvent
import im.vector.riotredesign.features.home.group.SelectedGroupStore
import im.vector.riotredesign.features.home.room.VisibleRoomStore
import io.reactivex.Observable
import io.reactivex.functions.Function3
-import io.reactivex.rxkotlin.subscribeBy
import org.koin.android.ext.android.get
import java.util.concurrent.TimeUnit
@@ -49,7 +50,7 @@ class RoomListViewModel(initialState: RoomListViewState,
@JvmStatic
override fun create(viewModelContext: ViewModelContext, state: RoomListViewState): RoomListViewModel? {
- val currentSession = Matrix.getInstance().currentSession
+ val currentSession = viewModelContext.activity.get()
val roomSelectionRepository = viewModelContext.activity.get()
val selectedGroupHolder = viewModelContext.activity.get()
val visibleRoomHolder = viewModelContext.activity.get()
@@ -61,6 +62,10 @@ class RoomListViewModel(initialState: RoomListViewState,
private val roomListFilter = BehaviorRelay.createDefault