Scope and error manager

This commit is contained in:
Benoit Marty 2019-05-24 22:27:26 +02:00
parent cd5e808bb6
commit 128dea2677
12 changed files with 198 additions and 66 deletions

View file

@ -30,6 +30,7 @@ import im.vector.riotredesign.core.di.AppModule
import im.vector.riotredesign.features.home.HomeModule import im.vector.riotredesign.features.home.HomeModule
import im.vector.riotredesign.features.rageshake.VectorFileLogger import im.vector.riotredesign.features.rageshake.VectorFileLogger
import im.vector.riotredesign.features.rageshake.VectorUncaughtExceptionHandler import im.vector.riotredesign.features.rageshake.VectorUncaughtExceptionHandler
import im.vector.riotredesign.features.roomdirectory.RoomDirectoryModule
import org.koin.log.EmptyLogger import org.koin.log.EmptyLogger
import org.koin.standalone.StandAloneContext.startKoin import org.koin.standalone.StandAloneContext.startKoin
import timber.log.Timber import timber.log.Timber
@ -56,7 +57,8 @@ class VectorApplication : Application() {
EpoxyController.defaultModelBuildingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler() EpoxyController.defaultModelBuildingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler()
val appModule = AppModule(applicationContext).definition val appModule = AppModule(applicationContext).definition
val homeModule = HomeModule().definition val homeModule = HomeModule().definition
startKoin(listOf(appModule, homeModule), logger = EmptyLogger()) val roomDirectoryModule = RoomDirectoryModule().definition
startKoin(listOf(appModule, homeModule, roomDirectoryModule), logger = EmptyLogger())
Matrix.getInstance().setApplicationFlavor(BuildConfig.FLAVOR_DESCRIPTION) Matrix.getInstance().setApplicationFlavor(BuildConfig.FLAVOR_DESCRIPTION)
} }

View file

@ -19,6 +19,7 @@ package im.vector.riotredesign.core.di
import android.content.Context import android.content.Context
import android.content.Context.MODE_PRIVATE import android.content.Context.MODE_PRIVATE
import im.vector.matrix.android.api.Matrix import im.vector.matrix.android.api.Matrix
import im.vector.riotredesign.core.error.ErrorFormatter
import im.vector.riotredesign.core.resources.LocaleProvider import im.vector.riotredesign.core.resources.LocaleProvider
import im.vector.riotredesign.core.resources.StringArrayProvider import im.vector.riotredesign.core.resources.StringArrayProvider
import im.vector.riotredesign.core.resources.StringProvider import im.vector.riotredesign.core.resources.StringProvider
@ -65,6 +66,10 @@ class AppModule(private val context: Context) {
RoomSummaryComparator() RoomSummaryComparator()
} }
single {
ErrorFormatter(get())
}
single { single {
NotificationDrawerManager(context) NotificationDrawerManager(context)
} }

View file

@ -0,0 +1,39 @@
/*
* 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.error
import im.vector.matrix.android.api.failure.Failure
import im.vector.riotredesign.R
import im.vector.riotredesign.core.resources.StringProvider
class ErrorFormatter(val stringProvider: StringProvider) {
fun toHumanReadable(failure: Failure): String {
// Default
return failure.localizedMessage
}
fun toHumanReadable(throwable: Throwable): String {
return when (throwable) {
is Failure.NetworkConnection -> stringProvider.getString(R.string.error_no_network)
else -> throwable.localizedMessage
}
}
}

View file

@ -27,7 +27,6 @@ import kotlinx.android.synthetic.main.view_state.view.*
class StateView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) class StateView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0)
: FrameLayout(context, attrs, defStyle) { : FrameLayout(context, attrs, defStyle) {
// TODO Use Async from MvRx?
sealed class State { sealed class State {
object Content : State() object Content : State()
object Loading : State() object Loading : State()

View file

@ -26,9 +26,11 @@ import im.vector.riotredesign.R
import im.vector.riotredesign.core.epoxy.errorWithRetryItem import im.vector.riotredesign.core.epoxy.errorWithRetryItem
import im.vector.riotredesign.core.epoxy.loadingItem import im.vector.riotredesign.core.epoxy.loadingItem
import im.vector.riotredesign.core.epoxy.noResultItem import im.vector.riotredesign.core.epoxy.noResultItem
import im.vector.riotredesign.core.error.ErrorFormatter
import im.vector.riotredesign.core.resources.StringProvider import im.vector.riotredesign.core.resources.StringProvider
class PublicRoomsController(private val stringProvider: StringProvider) : TypedEpoxyController<PublicRoomsViewState>() { class PublicRoomsController(private val stringProvider: StringProvider,
private val errorFormatter: ErrorFormatter) : TypedEpoxyController<PublicRoomsViewState>() {
var callback: Callback? = null var callback: Callback? = null
@ -68,7 +70,7 @@ class PublicRoomsController(private val stringProvider: StringProvider) : TypedE
if (viewState.asyncPublicRoomsRequest is Fail) { if (viewState.asyncPublicRoomsRequest is Fail) {
errorWithRetryItem { errorWithRetryItem {
id("error") id("error")
text(viewState.asyncPublicRoomsRequest.error.localizedMessage) text(errorFormatter.toHumanReadable(viewState.asyncPublicRoomsRequest.error))
listener { callback?.loadMore() } listener { callback?.loadMore() }
} }
} }

View file

@ -27,12 +27,15 @@ import com.google.android.material.snackbar.Snackbar
import com.jakewharton.rxbinding2.widget.RxTextView import com.jakewharton.rxbinding2.widget.RxTextView
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.error.ErrorFormatter
import im.vector.riotredesign.core.extensions.addFragmentToBackstack import im.vector.riotredesign.core.extensions.addFragmentToBackstack
import im.vector.riotredesign.core.platform.VectorBaseFragment import im.vector.riotredesign.core.platform.VectorBaseFragment
import im.vector.riotredesign.features.roomdirectory.picker.RoomDirectoryPickerFragment import im.vector.riotredesign.features.roomdirectory.picker.RoomDirectoryPickerFragment
import io.reactivex.rxkotlin.subscribeBy import io.reactivex.rxkotlin.subscribeBy
import kotlinx.android.synthetic.main.fragment_public_rooms.* import kotlinx.android.synthetic.main.fragment_public_rooms.*
import org.koin.android.ext.android.get import org.koin.android.ext.android.inject
import org.koin.android.scope.ext.android.bindScope
import org.koin.android.scope.ext.android.getOrCreateScope
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -52,8 +55,8 @@ import java.util.concurrent.TimeUnit
class PublicRoomsFragment : VectorBaseFragment(), PublicRoomsController.Callback { class PublicRoomsFragment : VectorBaseFragment(), PublicRoomsController.Callback {
private val viewModel: RoomDirectoryViewModel by activityViewModel() private val viewModel: RoomDirectoryViewModel by activityViewModel()
private val publicRoomsController: PublicRoomsController by inject()
private val publicRoomsController = PublicRoomsController(get()) private val errorFormatter: ErrorFormatter by inject()
override fun getLayoutResId() = R.layout.fragment_public_rooms override fun getLayoutResId() = R.layout.fragment_public_rooms
@ -84,7 +87,7 @@ class PublicRoomsFragment : VectorBaseFragment(), PublicRoomsController.Callback
viewModel.joinRoomErrorLiveData.observe(this, Observer { viewModel.joinRoomErrorLiveData.observe(this, Observer {
it.getContentIfNotHandled()?.let { throwable -> it.getContentIfNotHandled()?.let { throwable ->
Snackbar.make(publicRoomsCoordinator, throwable.localizedMessage, Snackbar.LENGTH_SHORT) Snackbar.make(publicRoomsCoordinator, errorFormatter.toHumanReadable(throwable), Snackbar.LENGTH_SHORT)
.show() .show()
} }
}) })
@ -92,6 +95,7 @@ class PublicRoomsFragment : VectorBaseFragment(), PublicRoomsController.Callback
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)
bindScope(getOrCreateScope(RoomDirectoryModule.ROOM_DIRECTORY_SCOPE))
setupRecyclerView() setupRecyclerView()
} }

View file

@ -16,15 +16,24 @@
package im.vector.riotredesign.features.roomdirectory package im.vector.riotredesign.features.roomdirectory
import android.os.Bundle
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.extensions.addFragment import im.vector.riotredesign.core.extensions.addFragment
import im.vector.riotredesign.core.platform.VectorBaseActivity import im.vector.riotredesign.core.platform.VectorBaseActivity
import org.koin.android.scope.ext.android.bindScope
import org.koin.android.scope.ext.android.getOrCreateScope
class RoomDirectoryActivity : VectorBaseActivity() { class RoomDirectoryActivity : VectorBaseActivity() {
override fun getLayoutRes() = R.layout.activity_simple override fun getLayoutRes() = R.layout.activity_simple
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
bindScope(getOrCreateScope(RoomDirectoryModule.ROOM_DIRECTORY_SCOPE))
}
override fun initUiAndData() { override fun initUiAndData() {
if (isFirstCreation()) { if (isFirstCreation()) {
addFragment(PublicRoomsFragment(), R.id.simpleFragmentContainer) addFragment(PublicRoomsFragment(), R.id.simpleFragmentContainer)

View file

@ -0,0 +1,45 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotredesign.features.roomdirectory
import im.vector.matrix.android.api.session.Session
import im.vector.riotredesign.features.roomdirectory.picker.RoomDirectoryListCreator
import im.vector.riotredesign.features.roomdirectory.picker.RoomDirectoryPickerController
import org.koin.dsl.module.module
// TODO Ganfra: When do we create a new module?
class RoomDirectoryModule {
companion object {
const val ROOM_DIRECTORY_SCOPE = "ROOM_DIRECTORY_SCOPE"
}
val definition = module(override = true) {
scope(ROOM_DIRECTORY_SCOPE) {
RoomDirectoryPickerController(get(), get(), get())
}
scope(ROOM_DIRECTORY_SCOPE) {
RoomDirectoryListCreator(get(), get<Session>().sessionParams.credentials)
}
scope(ROOM_DIRECTORY_SCOPE) {
PublicRoomsController(get(), get())
}
}
}

View file

@ -0,0 +1,70 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotredesign.features.roomdirectory.picker
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.room.model.thirdparty.RoomDirectoryData
import im.vector.matrix.android.api.session.room.model.thirdparty.ThirdPartyProtocol
import im.vector.riotredesign.R
import im.vector.riotredesign.core.resources.StringArrayProvider
class RoomDirectoryListCreator(private val stringArrayProvider: StringArrayProvider,
private val credentials: Credentials) {
fun computeDirectories(thirdPartyProtocolData: Map<String, ThirdPartyProtocol>): List<RoomDirectoryData> {
val result = ArrayList<RoomDirectoryData>()
// Add user homeserver name
val userHsName = credentials.userId.substring(credentials.userId.indexOf(":") + 1)
result.add(RoomDirectoryData(
displayName = userHsName,
includeAllNetworks = true
))
// Add user's HS but for Matrix public rooms only
result.add(RoomDirectoryData())
// Add custom directory servers
val hsNamesList = stringArrayProvider.getStringArray(R.array.room_directory_servers)
hsNamesList.forEach {
if (it != userHsName) {
// Use the server name as a default display name
result.add(RoomDirectoryData(
displayName = it,
includeAllNetworks = true
))
}
}
// Add result of the request
thirdPartyProtocolData.forEach {
it.value.instances?.forEach { thirdPartyProtocolInstance ->
result.add(RoomDirectoryData(
homeServer = null,
displayName = thirdPartyProtocolInstance.desc ?: "",
thirdPartyInstanceId = thirdPartyProtocolInstance.instanceId,
includeAllNetworks = false,
// Default to protocol icon
avatarUrl = thirdPartyProtocolInstance.icon ?: it.value.icon
))
}
}
return result
}
}

View file

@ -20,18 +20,16 @@ import com.airbnb.epoxy.TypedEpoxyController
import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Incomplete import com.airbnb.mvrx.Incomplete
import com.airbnb.mvrx.Success import com.airbnb.mvrx.Success
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.room.model.thirdparty.RoomDirectoryData import im.vector.matrix.android.api.session.room.model.thirdparty.RoomDirectoryData
import im.vector.matrix.android.api.session.room.model.thirdparty.ThirdPartyProtocol
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.epoxy.errorWithRetryItem import im.vector.riotredesign.core.epoxy.errorWithRetryItem
import im.vector.riotredesign.core.epoxy.loadingItem import im.vector.riotredesign.core.epoxy.loadingItem
import im.vector.riotredesign.core.resources.StringArrayProvider import im.vector.riotredesign.core.error.ErrorFormatter
import im.vector.riotredesign.core.resources.StringProvider import im.vector.riotredesign.core.resources.StringProvider
class RoomDirectoryPickerController(private val stringProvider: StringProvider, class RoomDirectoryPickerController(private val stringProvider: StringProvider,
private val stringArrayProvider: StringArrayProvider, private val errorFormatter: ErrorFormatter,
private val credentials: Credentials private val roomDirectoryListCreator: RoomDirectoryListCreator
) : TypedEpoxyController<RoomDirectoryPickerViewState>() { ) : TypedEpoxyController<RoomDirectoryPickerViewState>() {
var callback: Callback? = null var callback: Callback? = null
@ -43,7 +41,7 @@ class RoomDirectoryPickerController(private val stringProvider: StringProvider,
when (asyncThirdPartyProtocol) { when (asyncThirdPartyProtocol) {
is Success -> { is Success -> {
val directories = computeDirectories(asyncThirdPartyProtocol.invoke()) val directories = roomDirectoryListCreator.computeDirectories(asyncThirdPartyProtocol.invoke())
directories.forEach { directories.forEach {
buildDirectory(it) buildDirectory(it)
@ -57,59 +55,14 @@ class RoomDirectoryPickerController(private val stringProvider: StringProvider,
is Fail -> { is Fail -> {
errorWithRetryItem { errorWithRetryItem {
id("error") id("error")
text(asyncThirdPartyProtocol.error.localizedMessage) text(errorFormatter.toHumanReadable(asyncThirdPartyProtocol.error))
listener { callback?.retry() } listener { callback?.retry() }
} }
} }
} }
} }
private fun computeDirectories(thirdPartyProtocolData: Map<String, ThirdPartyProtocol>): List<RoomDirectoryData> {
val result = ArrayList<RoomDirectoryData>()
// Add user homeserver name
val userHsName = credentials.userId.substring(credentials.userId.indexOf(":") + 1)
result.add(RoomDirectoryData(
displayName = userHsName,
includeAllNetworks = true
))
// Add user's HS but for Matrix public rooms only
result.add(RoomDirectoryData())
// Add custom directory servers
val hsNamesList = stringArrayProvider.getStringArray(R.array.room_directory_servers)
hsNamesList.forEach {
if (it != userHsName) {
// Use the server name as a default display name
result.add(RoomDirectoryData(
displayName = it,
includeAllNetworks = true
))
}
}
// Add result of the request
thirdPartyProtocolData.forEach {
it.value.instances?.forEach { thirdPartyProtocolInstance ->
result.add(RoomDirectoryData(
homeServer = null,
displayName = thirdPartyProtocolInstance.desc ?: "",
thirdPartyInstanceId = thirdPartyProtocolInstance.instanceId,
includeAllNetworks = false,
// Default to protocol icon
avatarUrl = thirdPartyProtocolInstance.icon ?: it.value.icon
))
}
}
return result
}
private fun buildDirectory(roomDirectoryData: RoomDirectoryData) { private fun buildDirectory(roomDirectoryData: RoomDirectoryData) {
// TODO
roomDirectoryItem { roomDirectoryItem {
id(index++) id(index++)

View file

@ -23,24 +23,25 @@ import androidx.recyclerview.widget.LinearLayoutManager
import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.room.model.thirdparty.RoomDirectoryData import im.vector.matrix.android.api.session.room.model.thirdparty.RoomDirectoryData
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.platform.VectorBaseFragment import im.vector.riotredesign.core.platform.VectorBaseFragment
import im.vector.riotredesign.features.roomdirectory.RoomDirectoryModule
import im.vector.riotredesign.features.roomdirectory.RoomDirectoryViewModel import im.vector.riotredesign.features.roomdirectory.RoomDirectoryViewModel
import kotlinx.android.synthetic.main.fragment_public_rooms.toolbar import kotlinx.android.synthetic.main.fragment_public_rooms.toolbar
import kotlinx.android.synthetic.main.fragment_room_directory_picker.* import kotlinx.android.synthetic.main.fragment_room_directory_picker.*
import org.koin.android.ext.android.get import org.koin.android.ext.android.inject
import org.koin.android.scope.ext.android.bindScope
import org.koin.android.scope.ext.android.getOrCreateScope
import timber.log.Timber import timber.log.Timber
// TODO Set title to R.string.select_room_directory // TODO Set title to R.string.select_room_directory
// TODO Menu to add custom room directory (not done in RiotWeb so far...) // TODO Menu to add custom room directory (not done in RiotWeb so far...)
class RoomDirectoryPickerFragment : VectorBaseFragment(), RoomDirectoryPickerController.Callback { class RoomDirectoryPickerFragment : VectorBaseFragment(), RoomDirectoryPickerController.Callback {
private val viewModel: RoomDirectoryViewModel by activityViewModel() private val viewModel: RoomDirectoryViewModel by activityViewModel()
private val pickerViewModel: RoomDirectoryPickerViewModel by fragmentViewModel() private val pickerViewModel: RoomDirectoryPickerViewModel by fragmentViewModel()
private val roomDirectoryPickerController: RoomDirectoryPickerController by inject()
private val roomDirectoryPickerController = RoomDirectoryPickerController(get(), get(), get<Session>().sessionParams.credentials)
override fun getLayoutResId() = R.layout.fragment_room_directory_picker override fun getLayoutResId() = R.layout.fragment_room_directory_picker
@ -69,6 +70,7 @@ class RoomDirectoryPickerFragment : VectorBaseFragment(), RoomDirectoryPickerCon
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)
bindScope(getOrCreateScope(RoomDirectoryModule.ROOM_DIRECTORY_SCOPE))
setupRecyclerView() setupRecyclerView()
} }
@ -87,7 +89,7 @@ class RoomDirectoryPickerFragment : VectorBaseFragment(), RoomDirectoryPickerCon
Timber.v("onRoomDirectoryClicked: $roomDirectoryData") Timber.v("onRoomDirectoryClicked: $roomDirectoryData")
viewModel.setRoomDirectoryData(roomDirectoryData) viewModel.setRoomDirectoryData(roomDirectoryData)
// TODO Not the bast way to manage Fragment Backstack... // TODO Not the best way to manage Fragment Backstack...
vectorBaseActivity.onBackPressed() vectorBaseActivity.onBackPressed()
} }

View file

@ -20,4 +20,6 @@
<string name="create_new_room">Create New Room</string> <string name="create_new_room">Create New Room</string>
<string name="error_no_network">No network. Please check your Internet connection.</string>
</resources> </resources>