From 6890f83810dba3783d4971feb1cdb112b40e37c2 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 24 Sep 2019 10:47:29 +0200 Subject: [PATCH 01/22] Cleanup dead code --- .../riotx/features/home/HomeActivity.kt | 15 ----------- .../features/home/HomeActivityViewModel.kt | 25 +------------------ 2 files changed, 1 insertion(+), 39 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeActivity.kt index 2a43ca705a..c3636cde7a 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/HomeActivity.kt @@ -16,7 +16,6 @@ package im.vector.riotx.features.home -import android.app.ProgressDialog import android.content.Context import android.content.Intent import android.os.Bundle @@ -66,8 +65,6 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable { @Inject lateinit var pushManager: PushersManager @Inject lateinit var notificationDrawerManager: NotificationDrawerManager - private var progress: ProgressDialog? = null - private val drawerListener = object : DrawerLayout.SimpleDrawerListener() { override fun onDrawerStateChanged(newState: Int) { hideKeyboard() @@ -93,18 +90,6 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable { replaceFragment(homeDrawerFragment, R.id.homeDrawerFragmentContainer) } - homeActivityViewModel.isLoading.observe(this, Observer { - // TODO better UI - if (it) { - progress?.dismiss() - progress = ProgressDialog(this) - progress?.setMessage(getString(R.string.room_recents_create_room)) - progress?.show() - } else { - progress?.dismiss() - } - }) - navigationViewModel.navigateTo.observeEvent(this) { navigation -> when (navigation) { is Navigation.OpenDrawer -> drawerLayout.openDrawer(GravityCompat.START) diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeActivityViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeActivityViewModel.kt index 36d5725561..f8c1eca19e 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/HomeActivityViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/HomeActivityViewModel.kt @@ -16,8 +16,6 @@ package im.vector.riotx.features.home -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData import arrow.core.Option import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.MvRxState @@ -25,11 +23,9 @@ import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.ViewModelContext import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject -import im.vector.matrix.android.api.MatrixCallback 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.create.CreateRoomParams import im.vector.matrix.rx.rx import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.features.home.group.ALL_COMMUNITIES_GROUP_ID @@ -61,10 +57,6 @@ class HomeActivityViewModel @AssistedInject constructor(@Assisted initialState: } - private val _isLoading = MutableLiveData() - val isLoading: LiveData - get() = _isLoading - init { session.addListener(this) observeRoomAndGroup() @@ -93,7 +85,7 @@ class HomeActivityViewModel @AssistedInject constructor(@Assisted initialState: .filter { !it.isDirect } .filter { selectedGroup?.groupId == ALL_COMMUNITIES_GROUP_ID - || selectedGroup?.roomIds?.contains(it.roomId) ?: true + || selectedGroup?.roomIds?.contains(it.roomId) ?: true } filteredDirectRooms + filteredGroupRooms } @@ -104,21 +96,6 @@ class HomeActivityViewModel @AssistedInject constructor(@Assisted initialState: .disposeOnClear() } - fun createRoom(createRoomParams: CreateRoomParams = CreateRoomParams()) { - _isLoading.value = true - - session.createRoom(createRoomParams, object : MatrixCallback { - override fun onSuccess(data: String) { - _isLoading.value = false - } - - override fun onFailure(failure: Throwable) { - _isLoading.value = false - super.onFailure(failure) - } - }) - } - override fun onCleared() { super.onCleared() From af433266c88a2e5ec85c812a2d818021a78d7a3a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 24 Sep 2019 11:24:03 +0200 Subject: [PATCH 02/22] Move currentDisplayMode to the ViewState --- .../riotx/features/home/HomeDetailFragment.kt | 20 +++++-------------- .../features/home/HomeDetailViewModel.kt | 12 ++++++++++- .../features/home/HomeDetailViewState.kt | 2 ++ 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt index acfac104d4..a236bec747 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt @@ -51,8 +51,6 @@ data class HomeDetailParams( ) : Parcelable -private const val CURRENT_DISPLAY_MODE = "CURRENT_DISPLAY_MODE" - private const val INDEX_CATCHUP = 0 private const val INDEX_PEOPLE = 1 private const val INDEX_ROOMS = 2 @@ -61,7 +59,6 @@ class HomeDetailFragment : VectorBaseFragment(), KeysBackupBanner.Delegate { private val params: HomeDetailParams by args() private val unreadCounterBadgeViews = arrayListOf() - private lateinit var currentDisplayMode: RoomListFragment.DisplayMode private val viewModel: HomeDetailViewModel by fragmentViewModel() private lateinit var navigationViewModel: HomeNavigationViewModel @@ -80,15 +77,16 @@ class HomeDetailFragment : VectorBaseFragment(), KeysBackupBanner.Delegate { override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) - currentDisplayMode = savedInstanceState?.getSerializable(CURRENT_DISPLAY_MODE) as? RoomListFragment.DisplayMode - ?: RoomListFragment.DisplayMode.HOME navigationViewModel = ViewModelProviders.of(requireActivity()).get(HomeNavigationViewModel::class.java) - switchDisplayMode(currentDisplayMode) setupBottomNavigationView() setupToolbar() setupKeysBackupBanner() + + viewModel.selectSubscribe(this, HomeDetailViewState::displayMode) { displayMode -> + switchDisplayMode(displayMode) + } } private fun setupKeysBackupBanner() { @@ -126,11 +124,6 @@ class HomeDetailFragment : VectorBaseFragment(), KeysBackupBanner.Delegate { } - override fun onSaveInstanceState(outState: Bundle) { - outState.putSerializable(CURRENT_DISPLAY_MODE, currentDisplayMode) - super.onSaveInstanceState(outState) - } - private fun setupToolbar() { val parentActivity = vectorBaseActivity if (parentActivity is ToolbarConfigurable) { @@ -156,10 +149,7 @@ class HomeDetailFragment : VectorBaseFragment(), KeysBackupBanner.Delegate { R.id.bottom_action_rooms -> RoomListFragment.DisplayMode.ROOMS else -> RoomListFragment.DisplayMode.HOME } - if (currentDisplayMode != displayMode) { - currentDisplayMode = displayMode - switchDisplayMode(displayMode) - } + viewModel.switchDisplayMode(displayMode) true } diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeDetailViewModel.kt index 917cafe149..be8648da35 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/HomeDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/HomeDetailViewModel.kt @@ -24,10 +24,12 @@ import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.session.Session import im.vector.matrix.rx.rx import im.vector.riotx.core.platform.VectorViewModel +import im.vector.riotx.features.home.room.list.RoomListFragment import io.reactivex.schedulers.Schedulers /** - * View model used to update the home bottom bar notification counts + * View model used to update the home bottom bar notification counts, observe the sync state and + * change the selected room list view */ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: HomeDetailViewState, private val session: Session, @@ -53,6 +55,14 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho observeRoomSummaries() } + fun switchDisplayMode(displayMode: RoomListFragment.DisplayMode) = withState { state -> + if (state.displayMode != displayMode) { + setState { + copy(displayMode = displayMode) + } + } + } + // PRIVATE METHODS ***************************************************************************** private fun observeSyncState() { diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeDetailViewState.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeDetailViewState.kt index 970951bfba..a8f89cc566 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/HomeDetailViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/HomeDetailViewState.kt @@ -18,8 +18,10 @@ package im.vector.riotx.features.home import com.airbnb.mvrx.MvRxState import im.vector.matrix.android.api.session.sync.SyncState +import im.vector.riotx.features.home.room.list.RoomListFragment data class HomeDetailViewState( + val displayMode: RoomListFragment.DisplayMode = RoomListFragment.DisplayMode.HOME, val notificationCountCatchup: Int = 0, val notificationHighlightCatchup: Boolean = false, val notificationCountPeople: Int = 0, From 9e1ded941fdf340c8a4811e4e1cb44c128da3bd0 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 24 Sep 2019 12:29:37 +0200 Subject: [PATCH 03/22] Persist active tab between sessions (#503) --- CHANGES.md | 2 +- .../vector/riotx/core/di/ScreenComponent.kt | 3 + .../vector/riotx/core/di/VectorComponent.kt | 3 + .../riotx/features/home/HomeDetailFragment.kt | 6 ++ .../features/home/HomeDetailViewModel.kt | 12 ++++ .../riotx/features/ui/UiStateRepository.kt | 56 +++++++++++++++++++ 6 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 vector/src/main/java/im/vector/riotx/features/ui/UiStateRepository.kt diff --git a/CHANGES.md b/CHANGES.md index 07eab082d0..ee803b43cf 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,7 +5,7 @@ Features: - Improvements: - - + - Persist active tab between sessions (#503) Other changes: - diff --git a/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt b/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt index 01db4b4a01..3b18d3042e 100644 --- a/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt +++ b/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt @@ -65,6 +65,7 @@ import im.vector.riotx.features.roomdirectory.picker.RoomDirectoryPickerFragment import im.vector.riotx.features.roomdirectory.roompreview.RoomPreviewNoPreviewFragment import im.vector.riotx.features.settings.* import im.vector.riotx.features.settings.push.PushGatewaysFragment +import im.vector.riotx.features.ui.UiStateRepository @Component(dependencies = [VectorComponent::class], modules = [AssistedInjectModule::class, ViewModelModule::class, HomeModule::class]) @ScreenScope @@ -80,6 +81,8 @@ interface ScreenComponent { fun navigator(): Navigator + fun uiStateRepository(): UiStateRepository + fun inject(activity: HomeActivity) fun inject(roomDetailFragment: RoomDetailFragment) diff --git a/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt b/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt index a76091fb36..61c461c655 100644 --- a/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt +++ b/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt @@ -42,6 +42,7 @@ import im.vector.riotx.features.rageshake.BugReporter import im.vector.riotx.features.rageshake.VectorFileLogger import im.vector.riotx.features.rageshake.VectorUncaughtExceptionHandler import im.vector.riotx.features.settings.VectorPreferences +import im.vector.riotx.features.ui.UiStateRepository import javax.inject.Singleton @Component(modules = [VectorModule::class]) @@ -106,6 +107,8 @@ interface VectorComponent { fun vectorFileLogger(): VectorFileLogger + fun uiStateRepository(): UiStateRepository + @Component.Factory interface Factory { fun create(@BindsInstance context: Context): VectorComponent diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt index a236bec747..112fae1b24 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt @@ -166,6 +166,12 @@ class HomeDetailFragment : VectorBaseFragment(), KeysBackupBanner.Delegate { private fun switchDisplayMode(displayMode: RoomListFragment.DisplayMode) { groupToolbarTitleView.setText(displayMode.titleRes) updateSelectedFragment(displayMode) + // Update the navigation view (for when we restore the tabs) + bottomNavigationView.selectedItemId = when (displayMode) { + RoomListFragment.DisplayMode.PEOPLE -> R.id.bottom_action_people + RoomListFragment.DisplayMode.ROOMS -> R.id.bottom_action_rooms + else -> R.id.bottom_action_home + } } private fun updateSelectedFragment(displayMode: RoomListFragment.DisplayMode) { diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeDetailViewModel.kt index be8648da35..688d2b6b7b 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/HomeDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/HomeDetailViewModel.kt @@ -23,8 +23,10 @@ import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.session.Session import im.vector.matrix.rx.rx +import im.vector.riotx.core.di.HasScreenInjector import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.features.home.room.list.RoomListFragment +import im.vector.riotx.features.ui.UiStateRepository import io.reactivex.schedulers.Schedulers /** @@ -33,6 +35,7 @@ import io.reactivex.schedulers.Schedulers */ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: HomeDetailViewState, private val session: Session, + private val uiStateRepository: UiStateRepository, private val homeRoomListStore: HomeRoomListObservableStore) : VectorViewModel(initialState) { @@ -43,6 +46,13 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho companion object : MvRxViewModelFactory { + override fun initialState(viewModelContext: ViewModelContext): HomeDetailViewState? { + val uiStateRepository = (viewModelContext.activity as HasScreenInjector).injector().uiStateRepository() + return HomeDetailViewState( + displayMode = uiStateRepository.getDisplayMode() + ) + } + @JvmStatic override fun create(viewModelContext: ViewModelContext, state: HomeDetailViewState): HomeDetailViewModel? { val fragment: HomeDetailFragment = (viewModelContext as FragmentViewModelContext).fragment() @@ -60,6 +70,8 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho setState { copy(displayMode = displayMode) } + + uiStateRepository.storeDisplayMode(displayMode) } } diff --git a/vector/src/main/java/im/vector/riotx/features/ui/UiStateRepository.kt b/vector/src/main/java/im/vector/riotx/features/ui/UiStateRepository.kt new file mode 100644 index 0000000000..be8e624100 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/ui/UiStateRepository.kt @@ -0,0 +1,56 @@ +/* + * 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.riotx.features.ui + +import android.content.SharedPreferences +import androidx.core.content.edit +import im.vector.riotx.features.home.room.list.RoomListFragment +import javax.inject.Inject + + +/** + * This class is used to persist UI state across application restart + */ +class UiStateRepository @Inject constructor(private val sharedPreferences: SharedPreferences) { + + fun getDisplayMode(): RoomListFragment.DisplayMode { + return when (sharedPreferences.getInt(KEY_DISPLAY_MODE, VALUE_DISPLAY_MODE_CATCHUP)) { + VALUE_DISPLAY_MODE_PEOPLE -> RoomListFragment.DisplayMode.PEOPLE + VALUE_DISPLAY_MODE_ROOMS -> RoomListFragment.DisplayMode.ROOMS + else -> RoomListFragment.DisplayMode.HOME + } + } + + fun storeDisplayMode(displayMode: RoomListFragment.DisplayMode) { + sharedPreferences.edit { + putInt(KEY_DISPLAY_MODE, + when (displayMode) { + RoomListFragment.DisplayMode.PEOPLE -> VALUE_DISPLAY_MODE_PEOPLE + RoomListFragment.DisplayMode.ROOMS -> VALUE_DISPLAY_MODE_ROOMS + else -> VALUE_DISPLAY_MODE_CATCHUP + }) + } + } + + + companion object { + private const val KEY_DISPLAY_MODE = "UI_STATE_DISPLAY_MODE" + private const val VALUE_DISPLAY_MODE_CATCHUP = 0 + private const val VALUE_DISPLAY_MODE_PEOPLE = 1 + private const val VALUE_DISPLAY_MODE_ROOMS = 2 + } +} \ No newline at end of file From 1c9cf7a81072fa374457d0706af64a2d6918ec40 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 24 Sep 2019 13:40:03 +0200 Subject: [PATCH 04/22] Dagger code cleanup --- .../android/internal/crypto/CryptoModule.kt | 18 +++++++++--------- .../android/internal/session/SessionModule.kt | 2 +- .../internal/session/filter/FilterModule.kt | 2 +- .../internal/session/pushers/GetPushersTask.kt | 4 ++-- .../internal/session/pushers/PushersModule.kt | 2 +- .../internal/session/room/RoomModule.kt | 2 +- .../im/vector/riotx/core/di/VectorComponent.kt | 2 +- .../im/vector/riotx/core/di/VectorModule.kt | 2 +- 8 files changed, 17 insertions(+), 17 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt index 742e3ff21a..26647457a1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt @@ -117,13 +117,13 @@ internal abstract class CryptoModule { abstract fun bindGetDevicesTask(getDevicesTask: DefaultGetDevicesTask): GetDevicesTask @Binds - abstract fun bindSetDeviceNameTask(getDevicesTask: DefaultSetDeviceNameTask): SetDeviceNameTask + abstract fun bindSetDeviceNameTask(setDeviceNameTask: DefaultSetDeviceNameTask): SetDeviceNameTask @Binds - abstract fun bindUploadKeysTask(getDevicesTask: DefaultUploadKeysTask): UploadKeysTask + abstract fun bindUploadKeysTask(uploadKeysTask: DefaultUploadKeysTask): UploadKeysTask @Binds - abstract fun bindDownloadKeysForUsersTask(downloadKeysForUsers: DefaultDownloadKeysForUsers): DownloadKeysForUsersTask + abstract fun bindDownloadKeysForUsersTask(downloadKeysForUsersTask: DefaultDownloadKeysForUsers): DownloadKeysForUsersTask @Binds abstract fun bindCreateKeysBackupVersionTask(createKeysBackupVersionTask: DefaultCreateKeysBackupVersionTask): CreateKeysBackupVersionTask @@ -135,10 +135,10 @@ internal abstract class CryptoModule { abstract fun bindDeleteRoomSessionDataTask(deleteRoomSessionDataTask: DefaultDeleteRoomSessionDataTask): DeleteRoomSessionDataTask @Binds - abstract fun bindDeleteRoomSessionsDataTask(deleteRoomSessionDataTask: DefaultDeleteRoomSessionsDataTask): DeleteRoomSessionsDataTask + abstract fun bindDeleteRoomSessionsDataTask(deleteRoomSessionsDataTask: DefaultDeleteRoomSessionsDataTask): DeleteRoomSessionsDataTask @Binds - abstract fun bindDeleteSessionsDataTask(deleteRoomSessionDataTask: DefaultDeleteSessionsDataTask): DeleteSessionsDataTask + abstract fun bindDeleteSessionsDataTask(deleteSessionsDataTask: DefaultDeleteSessionsDataTask): DeleteSessionsDataTask @Binds abstract fun bindGetKeysBackupLastVersionTask(getKeysBackupLastVersionTask: DefaultGetKeysBackupLastVersionTask): GetKeysBackupLastVersionTask @@ -150,19 +150,19 @@ internal abstract class CryptoModule { abstract fun bindGetRoomSessionDataTask(getRoomSessionDataTask: DefaultGetRoomSessionDataTask): GetRoomSessionDataTask @Binds - abstract fun bindGetRoomSessionsDataTask(getRoomSessionDataTask: DefaultGetRoomSessionsDataTask): GetRoomSessionsDataTask + abstract fun bindGetRoomSessionsDataTask(getRoomSessionsDataTask: DefaultGetRoomSessionsDataTask): GetRoomSessionsDataTask @Binds - abstract fun bindGetSessionsDataTask(getRoomSessionDataTask: DefaultGetSessionsDataTask): GetSessionsDataTask + abstract fun bindGetSessionsDataTask(getSessionsDataTask: DefaultGetSessionsDataTask): GetSessionsDataTask @Binds abstract fun bindStoreRoomSessionDataTask(storeRoomSessionDataTask: DefaultStoreRoomSessionDataTask): StoreRoomSessionDataTask @Binds - abstract fun bindStoreRoomSessionsDataTask(storeRoomSessionDataTask: DefaultStoreRoomSessionsDataTask): StoreRoomSessionsDataTask + abstract fun bindStoreRoomSessionsDataTask(storeRoomSessionsDataTask: DefaultStoreRoomSessionsDataTask): StoreRoomSessionsDataTask @Binds - abstract fun bindStoreSessionsDataTask(storeRoomSessionDataTask: DefaultStoreSessionsDataTask): StoreSessionsDataTask + abstract fun bindStoreSessionsDataTask(storeSessionsDataTask: DefaultStoreSessionsDataTask): StoreSessionsDataTask @Binds abstract fun bindUpdateKeysBackupVersionTask(updateKeysBackupVersionTask: DefaultUpdateKeysBackupVersionTask): UpdateKeysBackupVersionTask 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 a08c7e4ab7..db4997ca89 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 @@ -162,7 +162,7 @@ internal abstract class SessionModule { @Binds @IntoSet - abstract fun bindEventRelationsAggregationUpdater(groupSummaryUpdater: EventRelationsAggregationUpdater): LiveEntityObserver + abstract fun bindEventRelationsAggregationUpdater(eventRelationsAggregationUpdater: EventRelationsAggregationUpdater): LiveEntityObserver @Binds @IntoSet diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterModule.kt index 745a92b3f9..0fc125a172 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterModule.kt @@ -43,7 +43,7 @@ internal abstract class FilterModule { abstract fun bindFilterService(filterService: DefaultFilterService): FilterService @Binds - abstract fun bindSaveFilterTask(saveFilterTask_Factory: DefaultSaveFilterTask): SaveFilterTask + abstract fun bindSaveFilterTask(saveFilterTask: DefaultSaveFilterTask): SaveFilterTask } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushersTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushersTask.kt index 8fd1a5b3be..ffaedad652 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushersTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushersTask.kt @@ -26,8 +26,8 @@ import javax.inject.Inject internal interface GetPushersTask : Task -internal class DefaultGetPusherTask @Inject constructor(private val pushersAPI: PushersAPI, - private val monarchy: Monarchy) : GetPushersTask { +internal class DefaultGetPushersTask @Inject constructor(private val pushersAPI: PushersAPI, + private val monarchy: Monarchy) : GetPushersTask { override suspend fun execute(params: Unit) { val response = executeRequest { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/PushersModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/PushersModule.kt index 784a140b19..ad4e3eee7a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/PushersModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/PushersModule.kt @@ -54,7 +54,7 @@ internal abstract class PushersModule { abstract fun bindConditionResolver(conditionResolver: DefaultConditionResolver): ConditionResolver @Binds - abstract fun bindGetPushersTask(getPusherTask: DefaultGetPusherTask): GetPushersTask + abstract fun bindGetPushersTask(getPushersTask: DefaultGetPushersTask): GetPushersTask @Binds abstract fun bindGetPushRulesTask(getPushRulesTask: DefaultGetPushRulesTask): GetPushRulesTask 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 572e03d0d6..95edf3dbaa 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 @@ -127,5 +127,5 @@ internal abstract class RoomModule { abstract fun bindFileService(fileService: DefaultFileService): FileService @Binds - abstract fun bindFetchEditHistoryTask(editHistoryTask: DefaultFetchEditHistoryTask): FetchEditHistoryTask + abstract fun bindFetchEditHistoryTask(fetchEditHistoryTask: DefaultFetchEditHistoryTask): FetchEditHistoryTask } diff --git a/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt b/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt index 61c461c655..040decbcc2 100644 --- a/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt +++ b/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt @@ -49,7 +49,7 @@ import javax.inject.Singleton @Singleton interface VectorComponent { - fun inject(vectorApplication: NotificationBroadcastReceiver) + fun inject(notificationBroadcastReceiver: NotificationBroadcastReceiver) fun inject(vectorApplication: VectorApplication) diff --git a/vector/src/main/java/im/vector/riotx/core/di/VectorModule.kt b/vector/src/main/java/im/vector/riotx/core/di/VectorModule.kt index 251fc0b1e9..f70771ef3e 100644 --- a/vector/src/main/java/im/vector/riotx/core/di/VectorModule.kt +++ b/vector/src/main/java/im/vector/riotx/core/di/VectorModule.kt @@ -62,7 +62,7 @@ abstract class VectorModule { @Provides @JvmStatic - fun providesAuthenticator(matrix: Matrix): Authenticator{ + fun providesAuthenticator(matrix: Matrix): Authenticator { return matrix.authenticator() } } From 0d807505077a8bdc63921c2427b3e0bc7be80da4 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 24 Sep 2019 13:43:50 +0200 Subject: [PATCH 05/22] Create interface for UiStateRepository and an implementation with SharedPrefs --- .../im/vector/riotx/core/di/VectorModule.kt | 4 ++ .../ui/SharedPreferencesUiStateRepository.kt | 56 +++++++++++++++++++ .../riotx/features/ui/UiStateRepository.kt | 34 ++--------- 3 files changed, 64 insertions(+), 30 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotx/features/ui/SharedPreferencesUiStateRepository.kt diff --git a/vector/src/main/java/im/vector/riotx/core/di/VectorModule.kt b/vector/src/main/java/im/vector/riotx/core/di/VectorModule.kt index f70771ef3e..e4532bada0 100644 --- a/vector/src/main/java/im/vector/riotx/core/di/VectorModule.kt +++ b/vector/src/main/java/im/vector/riotx/core/di/VectorModule.kt @@ -28,6 +28,8 @@ import im.vector.matrix.android.api.auth.Authenticator import im.vector.matrix.android.api.session.Session import im.vector.riotx.features.navigation.DefaultNavigator import im.vector.riotx.features.navigation.Navigator +import im.vector.riotx.features.ui.SharedPreferencesUiStateRepository +import im.vector.riotx.features.ui.UiStateRepository @Module abstract class VectorModule { @@ -70,5 +72,7 @@ abstract class VectorModule { @Binds abstract fun bindNavigator(navigator: DefaultNavigator): Navigator + @Binds + abstract fun bindUiStateRepository(uiStateRepository: SharedPreferencesUiStateRepository): UiStateRepository } \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotx/features/ui/SharedPreferencesUiStateRepository.kt b/vector/src/main/java/im/vector/riotx/features/ui/SharedPreferencesUiStateRepository.kt new file mode 100644 index 0000000000..3ca01c21c2 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/ui/SharedPreferencesUiStateRepository.kt @@ -0,0 +1,56 @@ +/* + * 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.riotx.features.ui + +import android.content.SharedPreferences +import androidx.core.content.edit +import im.vector.riotx.features.home.room.list.RoomListFragment +import javax.inject.Inject + + +/** + * This class is used to persist UI state across application restart + */ +class SharedPreferencesUiStateRepository @Inject constructor(private val sharedPreferences: SharedPreferences) : UiStateRepository { + + override fun getDisplayMode(): RoomListFragment.DisplayMode { + return when (sharedPreferences.getInt(KEY_DISPLAY_MODE, VALUE_DISPLAY_MODE_CATCHUP)) { + VALUE_DISPLAY_MODE_PEOPLE -> RoomListFragment.DisplayMode.PEOPLE + VALUE_DISPLAY_MODE_ROOMS -> RoomListFragment.DisplayMode.ROOMS + else -> RoomListFragment.DisplayMode.HOME + } + } + + override fun storeDisplayMode(displayMode: RoomListFragment.DisplayMode) { + sharedPreferences.edit { + putInt(KEY_DISPLAY_MODE, + when (displayMode) { + RoomListFragment.DisplayMode.PEOPLE -> VALUE_DISPLAY_MODE_PEOPLE + RoomListFragment.DisplayMode.ROOMS -> VALUE_DISPLAY_MODE_ROOMS + else -> VALUE_DISPLAY_MODE_CATCHUP + }) + } + } + + + companion object { + private const val KEY_DISPLAY_MODE = "UI_STATE_DISPLAY_MODE" + private const val VALUE_DISPLAY_MODE_CATCHUP = 0 + private const val VALUE_DISPLAY_MODE_PEOPLE = 1 + private const val VALUE_DISPLAY_MODE_ROOMS = 2 + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotx/features/ui/UiStateRepository.kt b/vector/src/main/java/im/vector/riotx/features/ui/UiStateRepository.kt index be8e624100..15f3f3efa2 100644 --- a/vector/src/main/java/im/vector/riotx/features/ui/UiStateRepository.kt +++ b/vector/src/main/java/im/vector/riotx/features/ui/UiStateRepository.kt @@ -16,41 +16,15 @@ package im.vector.riotx.features.ui -import android.content.SharedPreferences -import androidx.core.content.edit import im.vector.riotx.features.home.room.list.RoomListFragment -import javax.inject.Inject /** - * This class is used to persist UI state across application restart + * This interface is used to persist UI state across application restart */ -class UiStateRepository @Inject constructor(private val sharedPreferences: SharedPreferences) { +interface UiStateRepository { - fun getDisplayMode(): RoomListFragment.DisplayMode { - return when (sharedPreferences.getInt(KEY_DISPLAY_MODE, VALUE_DISPLAY_MODE_CATCHUP)) { - VALUE_DISPLAY_MODE_PEOPLE -> RoomListFragment.DisplayMode.PEOPLE - VALUE_DISPLAY_MODE_ROOMS -> RoomListFragment.DisplayMode.ROOMS - else -> RoomListFragment.DisplayMode.HOME - } - } + fun getDisplayMode(): RoomListFragment.DisplayMode - fun storeDisplayMode(displayMode: RoomListFragment.DisplayMode) { - sharedPreferences.edit { - putInt(KEY_DISPLAY_MODE, - when (displayMode) { - RoomListFragment.DisplayMode.PEOPLE -> VALUE_DISPLAY_MODE_PEOPLE - RoomListFragment.DisplayMode.ROOMS -> VALUE_DISPLAY_MODE_ROOMS - else -> VALUE_DISPLAY_MODE_CATCHUP - }) - } - } - - - companion object { - private const val KEY_DISPLAY_MODE = "UI_STATE_DISPLAY_MODE" - private const val VALUE_DISPLAY_MODE_CATCHUP = 0 - private const val VALUE_DISPLAY_MODE_PEOPLE = 1 - private const val VALUE_DISPLAY_MODE_ROOMS = 2 - } + fun storeDisplayMode(displayMode: RoomListFragment.DisplayMode) } \ No newline at end of file From 1e963bc0dc745f5b74eebdfb42f0d99a8db78e7b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 24 Sep 2019 14:23:13 +0200 Subject: [PATCH 06/22] Fix crash: MergedHeaderItem was missing dimensionConverter --- CHANGES.md | 6 ++++++ vector/build.gradle | 2 +- .../home/room/detail/timeline/TimelineEventController.kt | 9 ++++----- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 8c2372a3b1..22701ff529 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,9 @@ +Changes in RiotX 0.6.1 (2019-09-24) +=================================================== + +Bugfix: + - Fix crash: MergedHeaderItem was missing dimensionConverter + Changes in RiotX 0.6.0 (2019-09-24) =================================================== diff --git a/vector/build.gradle b/vector/build.gradle index 697d0f36d0..1364093ddb 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -16,7 +16,7 @@ androidExtensions { ext.versionMajor = 0 ext.versionMinor = 6 -ext.versionPatch = 0 +ext.versionPatch = 1 static def getGitTimestamp() { def cmd = 'git show -s --format=%ct' diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineEventController.kt index 3c212d6129..1a0e8d180b 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineEventController.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineEventController.kt @@ -31,14 +31,11 @@ import im.vector.riotx.core.date.VectorDateFormatter import im.vector.riotx.core.epoxy.LoadingItem_ import im.vector.riotx.core.extensions.localDateTime import im.vector.riotx.core.resources.UserPreferencesProvider +import im.vector.riotx.core.utils.DimensionConverter import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.room.detail.timeline.factory.TimelineItemFactory import im.vector.riotx.features.home.room.detail.timeline.helper.* -import im.vector.riotx.features.home.room.detail.timeline.item.DaySeparatorItem -import im.vector.riotx.features.home.room.detail.timeline.item.DaySeparatorItem_ -import im.vector.riotx.features.home.room.detail.timeline.item.MergedHeaderItem -import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData -import im.vector.riotx.features.home.room.detail.timeline.item.ReadReceiptData +import im.vector.riotx.features.home.room.detail.timeline.item.* import im.vector.riotx.features.media.ImageContentRenderer import im.vector.riotx.features.media.VideoContentRenderer import org.threeten.bp.LocalDateTime @@ -48,6 +45,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec private val timelineItemFactory: TimelineItemFactory, private val timelineMediaSizeProvider: TimelineMediaSizeProvider, private val avatarRenderer: AvatarRenderer, + private val dimensionConverter: DimensionConverter, @TimelineEventControllerHandler private val backgroundHandler: Handler, userPreferencesProvider: UserPreferencesProvider @@ -312,6 +310,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec mergeItemCollapseStates[event.localId] = it requestModelBuild() }.also { + it.dimensionConverter = dimensionConverter it.setOnVisibilityStateChanged(MergedTimelineEventVisibilityStateChangedListener(callback, mergedEvents)) } } From f6373221de6fd03b807e2e26f2b3f91d087164c9 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 24 Sep 2019 16:05:08 +0200 Subject: [PATCH 07/22] Dagger cleanup --- vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt b/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt index 040decbcc2..10b3730022 100644 --- a/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt +++ b/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt @@ -65,7 +65,7 @@ interface VectorComponent { fun resources(): Resources - fun dimensionUtils(): DimensionConverter + fun dimensionConverter(): DimensionConverter fun vectorConfiguration(): VectorConfiguration From b24a37226263ecf10d0450b13814483ab69966eb Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 25 Sep 2019 10:50:13 +0200 Subject: [PATCH 08/22] Show "Clear message queue" option (in debug mode) --- .../home/room/detail/RoomDetailViewModel.kt | 20 ++++++++----------- vector/src/main/res/menu/menu_timeline.xml | 2 +- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index 80f333a76e..ac56114319 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -48,6 +48,7 @@ import im.vector.matrix.android.api.session.room.timeline.getTextEditableContent import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent import im.vector.matrix.rx.rx +import im.vector.riotx.BuildConfig import im.vector.riotx.R import im.vector.riotx.core.extensions.postLiveEvent import im.vector.riotx.core.intent.getFilenameFromUri @@ -232,18 +233,13 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro get() = _downloadedFileEvent - fun isMenuItemVisible(@IdRes itemId: Int): Boolean { - if (itemId == R.id.clear_message_queue) { - //For now always disable, woker cancellation is not working properly - return false//timeline.pendingEventCount() > 0 - } - if (itemId == R.id.resend_all) { - return timeline.failedToDeliverEventCount() > 0 - } - if (itemId == R.id.clear_all) { - return timeline.failedToDeliverEventCount() > 0 - } - return false + fun isMenuItemVisible(@IdRes itemId: Int) = when (itemId) { + R.id.clear_message_queue -> + /* For now always disable on production, worker cancellation is not working properly */ + timeline.pendingEventCount() > 0 && BuildConfig.DEBUG + R.id.resend_all -> timeline.failedToDeliverEventCount() > 0 + R.id.clear_all -> timeline.failedToDeliverEventCount() > 0 + else -> false } // PRIVATE METHODS ***************************************************************************** diff --git a/vector/src/main/res/menu/menu_timeline.xml b/vector/src/main/res/menu/menu_timeline.xml index 824735406f..7eea0e2582 100644 --- a/vector/src/main/res/menu/menu_timeline.xml +++ b/vector/src/main/res/menu/menu_timeline.xml @@ -22,7 +22,7 @@ From 9b91b6ea87effe986e7539592661b18948514848 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 25 Sep 2019 10:56:18 +0200 Subject: [PATCH 09/22] Create Extension to convert a Response to a Failure --- .../matrix/android/internal/network/Request.kt | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/Request.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/Request.kt index ede9e823bf..3420717199 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/Request.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/Request.kt @@ -23,9 +23,9 @@ import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.failure.MatrixError import im.vector.matrix.android.internal.di.MoshiProvider import kotlinx.coroutines.CancellationException -import okhttp3.ResponseBody import org.greenrobot.eventbus.EventBus import retrofit2.Call +import retrofit2.Response import timber.log.Timber import java.io.IOException @@ -43,7 +43,7 @@ internal class Request { response.body() ?: throw IllegalStateException("The request returned a null body") } else { - throw manageFailure(response.errorBody(), response.code()) + throw response.toFailure() } } catch (exception: Throwable) { throw when (exception) { @@ -56,10 +56,8 @@ internal class Request { } } - private fun manageFailure(errorBody: ResponseBody?, httpCode: Int): Throwable { - if (errorBody == null) { - return RuntimeException("Error body should not be null") - } + private fun Response.toFailure(): Failure { + val errorBody = errorBody() ?: return Failure.Unknown(RuntimeException("errorBody() should not be null")) val errorBodyStr = errorBody.string() @@ -74,13 +72,13 @@ internal class Request { EventBus.getDefault().post(ConsentNotGivenError(matrixError.consentUri)) } - return Failure.ServerError(matrixError, httpCode) + return Failure.ServerError(matrixError, code()) } } catch (ex: JsonDataException) { // This is not a MatrixError Timber.w("The error returned by the server is not a MatrixError") } - return Failure.OtherServerError(errorBodyStr, httpCode) + return Failure.OtherServerError(errorBodyStr, code()) } } \ No newline at end of file From ae8bceacba1f1b6076e3fc7fb1cd7e0da7ece3b1 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 25 Sep 2019 10:58:55 +0200 Subject: [PATCH 10/22] Create Extension to convert a Response to a Failure -> expose to other object --- .../android/internal/network/Request.kt | 35 ------------ .../internal/network/RetrofitExtensions.kt | 54 ++++++++++++++++++- 2 files changed, 52 insertions(+), 37 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/Request.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/Request.kt index 3420717199..a333a02c67 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/Request.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/Request.kt @@ -16,24 +16,15 @@ package im.vector.matrix.android.internal.network -import com.squareup.moshi.JsonDataException -import com.squareup.moshi.Moshi -import im.vector.matrix.android.api.failure.ConsentNotGivenError import im.vector.matrix.android.api.failure.Failure -import im.vector.matrix.android.api.failure.MatrixError -import im.vector.matrix.android.internal.di.MoshiProvider import kotlinx.coroutines.CancellationException -import org.greenrobot.eventbus.EventBus import retrofit2.Call -import retrofit2.Response -import timber.log.Timber import java.io.IOException internal suspend inline fun executeRequest(block: Request.() -> Unit) = Request().apply(block).execute() internal class Request { - private val moshi: Moshi = MoshiProvider.providesMoshi() lateinit var apiCall: Call suspend fun execute(): DATA { @@ -55,30 +46,4 @@ internal class Request { } } } - - private fun Response.toFailure(): Failure { - val errorBody = errorBody() ?: return Failure.Unknown(RuntimeException("errorBody() should not be null")) - - val errorBodyStr = errorBody.string() - - val matrixErrorAdapter = moshi.adapter(MatrixError::class.java) - - try { - val matrixError = matrixErrorAdapter.fromJson(errorBodyStr) - - if (matrixError != null) { - if (matrixError.code == MatrixError.M_CONSENT_NOT_GIVEN && !matrixError.consentUri.isNullOrBlank()) { - // Also send this error to the bus, for a global management - EventBus.getDefault().post(ConsentNotGivenError(matrixError.consentUri)) - } - - return Failure.ServerError(matrixError, code()) - } - } catch (ex: JsonDataException) { - // This is not a MatrixError - Timber.w("The error returned by the server is not a MatrixError") - } - - return Failure.OtherServerError(errorBodyStr, code()) - } } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/RetrofitExtensions.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/RetrofitExtensions.kt index 824d74b30e..64fcb08bac 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/RetrofitExtensions.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/RetrofitExtensions.kt @@ -18,14 +18,22 @@ package im.vector.matrix.android.internal.network +import com.squareup.moshi.JsonDataException +import im.vector.matrix.android.api.failure.ConsentNotGivenError +import im.vector.matrix.android.api.failure.Failure +import im.vector.matrix.android.api.failure.MatrixError +import im.vector.matrix.android.internal.di.MoshiProvider import kotlinx.coroutines.suspendCancellableCoroutine +import okhttp3.ResponseBody +import org.greenrobot.eventbus.EventBus import retrofit2.Call import retrofit2.Callback import retrofit2.Response +import timber.log.Timber import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException -suspend fun Call.awaitResponse(): Response { +internal suspend fun Call.awaitResponse(): Response { return suspendCancellableCoroutine { continuation -> continuation.invokeOnCancellation { cancel() @@ -40,4 +48,46 @@ suspend fun Call.awaitResponse(): Response { } }) } -} \ No newline at end of file +} + +/** + * Convert a retrofit Response to a Failure, and eventually parse errorBody to convert it to a MatrixError + */ +internal fun Response.toFailure(): Failure { + return toFailure(errorBody(), code()) +} + +/** + * Convert a okhttp3 Response to a Failure, and eventually parse errorBody to convert it to a MatrixError + */ +internal fun okhttp3.Response.toFailure(): Failure { + return toFailure(body(), code()) +} + +private fun toFailure(errorBody: ResponseBody?, httpCode: Int): Failure { + if (errorBody == null) { + return Failure.Unknown(RuntimeException("errorBody should not be null")) + } + + val errorBodyStr = errorBody.string() + + val matrixErrorAdapter = MoshiProvider.providesMoshi().adapter(MatrixError::class.java) + + try { + val matrixError = matrixErrorAdapter.fromJson(errorBodyStr) + + if (matrixError != null) { + if (matrixError.code == MatrixError.M_CONSENT_NOT_GIVEN && !matrixError.consentUri.isNullOrBlank()) { + // Also send this error to the bus, for a global management + EventBus.getDefault().post(ConsentNotGivenError(matrixError.consentUri)) + } + + return Failure.ServerError(matrixError, httpCode) + } + } catch (ex: JsonDataException) { + // This is not a MatrixError + Timber.w("The error returned by the server is not a MatrixError") + } + + return Failure.OtherServerError(errorBodyStr, httpCode) +} From 4c04014e4d998429812289bbc6ecf5f4bf37bc92 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 25 Sep 2019 11:26:49 +0200 Subject: [PATCH 11/22] Do not log big data request (ex: file upload) --- .../interceptors/CurlLoggingInterceptor.kt | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/matrix-sdk-android/src/debug/java/im/vector/matrix/android/internal/network/interceptors/CurlLoggingInterceptor.kt b/matrix-sdk-android/src/debug/java/im/vector/matrix/android/internal/network/interceptors/CurlLoggingInterceptor.kt index 3d499be3c1..5863edd154 100644 --- a/matrix-sdk-android/src/debug/java/im/vector/matrix/android/internal/network/interceptors/CurlLoggingInterceptor.kt +++ b/matrix-sdk-android/src/debug/java/im/vector/matrix/android/internal/network/interceptors/CurlLoggingInterceptor.kt @@ -22,6 +22,7 @@ import okhttp3.Interceptor import okhttp3.Response import okhttp3.logging.HttpLoggingInterceptor import okio.Buffer +import timber.log.Timber import java.io.IOException import java.nio.charset.Charset import javax.inject.Inject @@ -58,15 +59,21 @@ internal class CurlLoggingInterceptor @Inject constructor(private val logger: Ht val requestBody = request.body() if (requestBody != null) { - val buffer = Buffer() - requestBody.writeTo(buffer) - var charset: Charset? = UTF8 - val contentType = requestBody.contentType() - if (contentType != null) { - charset = contentType.charset(UTF8) + if (requestBody.contentLength() > 100_000) { + Timber.w("Unable to log curl command data, size is too big (${requestBody.contentLength()})") + // Ensure the curl command will failed + curlCmd += "DATA IS TOO BIG" + } else { + val buffer = Buffer() + requestBody.writeTo(buffer) + var charset: Charset? = UTF8 + val contentType = requestBody.contentType() + if (contentType != null) { + charset = contentType.charset(UTF8) + } + // try to keep to a single line and use a subshell to preserve any line breaks + curlCmd += " --data $'" + buffer.readString(charset!!).replace("\n", "\\n") + "'" } - // try to keep to a single line and use a subshell to preserve any line breaks - curlCmd += " --data $'" + buffer.readString(charset!!).replace("\n", "\\n") + "'" } val headers = request.headers() From f3039601bf135c27b5b74016a3bdd4252a53bb5e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 25 Sep 2019 11:27:23 +0200 Subject: [PATCH 12/22] throw Failure instead of meaning less IOException --- .../matrix/android/internal/session/content/FileUploader.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt index 2ec17248d1..15d75ceeb6 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt @@ -22,6 +22,7 @@ import com.squareup.moshi.Moshi import im.vector.matrix.android.api.auth.data.SessionParams import im.vector.matrix.android.internal.di.Authenticated import im.vector.matrix.android.internal.network.ProgressRequestBody +import im.vector.matrix.android.internal.network.toFailure import okhttp3.* import java.io.File import java.io.IOException @@ -74,7 +75,7 @@ internal class FileUploader @Inject constructor(@Authenticated return Try { okHttpClient.newCall(request).execute().use { response -> if (!response.isSuccessful) { - throw IOException() + throw response.toFailure() } else { response.body()?.source()?.let { responseAdapter.fromJson(it) From f077cc846763689dc3b61331b954589be639928e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 25 Sep 2019 14:09:26 +0200 Subject: [PATCH 13/22] Stop sending media in an infinite loop in case of error (part of #587) Not sure how this commit fix it, but the issue is not observed anymore with it --- .../attachments/MXEncryptedAttachments.kt | 19 +---- .../internal/network/RetrofitExtensions.kt | 19 +++++ .../internal/session/content/FileUploader.kt | 43 +++++----- .../session/content/UploadContentWorker.kt | 79 +++++++++---------- 4 files changed, 77 insertions(+), 83 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/attachments/MXEncryptedAttachments.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/attachments/MXEncryptedAttachments.kt index c699325562..95ff11d595 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/attachments/MXEncryptedAttachments.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/attachments/MXEncryptedAttachments.kt @@ -17,7 +17,6 @@ package im.vector.matrix.android.internal.crypto.attachments import android.util.Base64 -import arrow.core.Try import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileInfo import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileKey import timber.log.Timber @@ -50,7 +49,7 @@ object MXEncryptedAttachments { * @param mimetype the mime type * @return the encryption file info */ - fun encryptAttachment(attachmentStream: InputStream, mimetype: String): Try { + fun encryptAttachment(attachmentStream: InputStream, mimetype: String): EncryptionResult { val t0 = System.currentTimeMillis() val secureRandom = SecureRandom() @@ -70,7 +69,7 @@ object MXEncryptedAttachments { val outStream = ByteArrayOutputStream() - try { + outStream.use { val encryptCipher = Cipher.getInstance(CIPHER_ALGORITHM) val secretKeySpec = SecretKeySpec(key, SECRET_KEY_SPEC_ALGORITHM) val ivParameterSpec = IvParameterSpec(initVectorBytes) @@ -114,19 +113,7 @@ object MXEncryptedAttachments { ) Timber.v("Encrypt in ${System.currentTimeMillis() - t0} ms") - return Try.just(result) - } catch (oom: OutOfMemoryError) { - Timber.e(oom, "## encryptAttachment failed") - return Try.Failure(oom) - } catch (e: Exception) { - Timber.e(e, "## encryptAttachment failed") - return Try.Failure(e) - } finally { - try { - outStream.close() - } catch (e: Exception) { - Timber.e(e, "## encryptAttachment() : fail to close outStream") - } + return result } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/RetrofitExtensions.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/RetrofitExtensions.kt index 64fcb08bac..2bdcd9a2fb 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/RetrofitExtensions.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/RetrofitExtensions.kt @@ -30,6 +30,7 @@ import retrofit2.Call import retrofit2.Callback import retrofit2.Response import timber.log.Timber +import java.io.IOException import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException @@ -50,6 +51,24 @@ internal suspend fun Call.awaitResponse(): Response { } } +internal suspend fun okhttp3.Call.awaitResponse(): okhttp3.Response { + return suspendCancellableCoroutine { continuation -> + continuation.invokeOnCancellation { + cancel() + } + + enqueue(object : okhttp3.Callback { + override fun onResponse(call: okhttp3.Call, response: okhttp3.Response) { + continuation.resume(response) + } + + override fun onFailure(call: okhttp3.Call, e: IOException) { + continuation.resumeWithException(e) + } + }) + } +} + /** * Convert a retrofit Response to a Failure, and eventually parse errorBody to convert it to a MatrixError */ diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt index 15d75ceeb6..2f99199736 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt @@ -16,12 +16,11 @@ package im.vector.matrix.android.internal.session.content -import arrow.core.Try -import arrow.core.Try.Companion.raise import com.squareup.moshi.Moshi import im.vector.matrix.android.api.auth.data.SessionParams import im.vector.matrix.android.internal.di.Authenticated import im.vector.matrix.android.internal.network.ProgressRequestBody +import im.vector.matrix.android.internal.network.awaitResponse import im.vector.matrix.android.internal.network.toFailure import okhttp3.* import java.io.File @@ -38,28 +37,26 @@ internal class FileUploader @Inject constructor(@Authenticated private val responseAdapter = moshi.adapter(ContentUploadResponse::class.java) - fun uploadFile(file: File, - filename: String?, - mimeType: String, - progressListener: ProgressRequestBody.Listener? = null): Try { - + suspend fun uploadFile(file: File, + filename: String?, + mimeType: String, + progressListener: ProgressRequestBody.Listener? = null): ContentUploadResponse { val uploadBody = RequestBody.create(MediaType.parse(mimeType), file) return upload(uploadBody, filename, progressListener) } - fun uploadByteArray(byteArray: ByteArray, - filename: String?, - mimeType: String, - progressListener: ProgressRequestBody.Listener? = null): Try { - + suspend fun uploadByteArray(byteArray: ByteArray, + filename: String?, + mimeType: String, + progressListener: ProgressRequestBody.Listener? = null): ContentUploadResponse { val uploadBody = RequestBody.create(MediaType.parse(mimeType), byteArray) return upload(uploadBody, filename, progressListener) } - private fun upload(uploadBody: RequestBody, filename: String?, progressListener: ProgressRequestBody.Listener?): Try { - val urlBuilder = HttpUrl.parse(uploadUrl)?.newBuilder() ?: return raise(RuntimeException()) + private suspend fun upload(uploadBody: RequestBody, filename: String?, progressListener: ProgressRequestBody.Listener?): ContentUploadResponse { + val urlBuilder = HttpUrl.parse(uploadUrl)?.newBuilder() ?: throw RuntimeException() val httpUrl = urlBuilder .addQueryParameter("filename", filename) @@ -72,19 +69,15 @@ internal class FileUploader @Inject constructor(@Authenticated .post(requestBody) .build() - return Try { - okHttpClient.newCall(request).execute().use { response -> - if (!response.isSuccessful) { - throw response.toFailure() - } else { - response.body()?.source()?.let { - responseAdapter.fromJson(it) - } - ?: throw IOException() + return okHttpClient.newCall(request).awaitResponse().use { response -> + if (!response.isSuccessful) { + throw response.toFailure() + } else { + response.body()?.source()?.let { + responseAdapter.fromJson(it) } + ?: throw IOException() } } - } - } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt index b015670daa..2d9509b43d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt @@ -93,32 +93,28 @@ internal class UploadContentWorker(context: Context, params: WorkerParameters) : } } - val contentUploadResponse = if (params.isRoomEncrypted) { - Timber.v("Encrypt thumbnail") - contentUploadStateTracker.setEncryptingThumbnail(eventId) - MXEncryptedAttachments.encryptAttachment(ByteArrayInputStream(thumbnailData.bytes), thumbnailData.mimeType) - .flatMap { encryptionResult -> - uploadedThumbnailEncryptedFileInfo = encryptionResult.encryptedFileInfo + try { + val contentUploadResponse = if (params.isRoomEncrypted) { + Timber.v("Encrypt thumbnail") + contentUploadStateTracker.setEncryptingThumbnail(eventId) + val encryptionResult = MXEncryptedAttachments.encryptAttachment(ByteArrayInputStream(thumbnailData.bytes), thumbnailData.mimeType) + uploadedThumbnailEncryptedFileInfo = encryptionResult.encryptedFileInfo + fileUploader.uploadByteArray(encryptionResult.encryptedByteArray, + "thumb_${attachment.name}", + "application/octet-stream", + thumbnailProgressListener) + } else { + fileUploader.uploadByteArray(thumbnailData.bytes, + "thumb_${attachment.name}", + thumbnailData.mimeType, + thumbnailProgressListener) + } - fileUploader - .uploadByteArray(encryptionResult.encryptedByteArray, - "thumb_${attachment.name}", - "application/octet-stream", - thumbnailProgressListener) - } - } else { - fileUploader - .uploadByteArray(thumbnailData.bytes, - "thumb_${attachment.name}", - thumbnailData.mimeType, - thumbnailProgressListener) + uploadedThumbnailUrl = contentUploadResponse.contentUri + } catch (t: Throwable) { + Timber.e(t) + return handleFailure(params, t) } - - contentUploadResponse - .fold( - { Timber.e(it) }, - { uploadedThumbnailUrl = it.contentUri } - ) } val progressListener = object : ProgressRequestBody.Listener { @@ -133,27 +129,26 @@ internal class UploadContentWorker(context: Context, params: WorkerParameters) : var uploadedFileEncryptedFileInfo: EncryptedFileInfo? = null - val contentUploadResponse = if (params.isRoomEncrypted) { - Timber.v("Encrypt file") - contentUploadStateTracker.setEncrypting(eventId) + return try { + val contentUploadResponse = if (params.isRoomEncrypted) { + Timber.v("Encrypt file") + contentUploadStateTracker.setEncrypting(eventId) - MXEncryptedAttachments.encryptAttachment(FileInputStream(attachmentFile), attachment.mimeType) - .flatMap { encryptionResult -> - uploadedFileEncryptedFileInfo = encryptionResult.encryptedFileInfo + val encryptionResult = MXEncryptedAttachments.encryptAttachment(FileInputStream(attachmentFile), attachment.mimeType) + uploadedFileEncryptedFileInfo = encryptionResult.encryptedFileInfo - fileUploader - .uploadByteArray(encryptionResult.encryptedByteArray, attachment.name, "application/octet-stream", progressListener) - } - } else { - fileUploader - .uploadFile(attachmentFile, attachment.name, attachment.mimeType, progressListener) + fileUploader + .uploadByteArray(encryptionResult.encryptedByteArray, attachment.name, "application/octet-stream", progressListener) + } else { + fileUploader + .uploadFile(attachmentFile, attachment.name, attachment.mimeType, progressListener) + } + + handleSuccess(params, contentUploadResponse.contentUri, uploadedFileEncryptedFileInfo, uploadedThumbnailUrl, uploadedThumbnailEncryptedFileInfo) + } catch (t: Throwable) { + Timber.e(t) + handleFailure(params, t) } - - return contentUploadResponse - .fold( - { handleFailure(params, it) }, - { handleSuccess(params, it.contentUri, uploadedFileEncryptedFileInfo, uploadedThumbnailUrl, uploadedThumbnailEncryptedFileInfo) } - ) } private fun handleFailure(params: Params, failure: Throwable): Result { From 17cba1a43223bebeaae7595509cc958aa5c23f5a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 25 Sep 2019 14:39:33 +0200 Subject: [PATCH 14/22] Display progress in the timeline when uploading file --- .../im/vector/riotx/core/utils/FileUtils.kt | 2 ++ .../timeline/factory/MessageItemFactory.kt | 5 +++++ .../helper/ContentUploadStateTrackerBinder.kt | 9 ++++----- .../detail/timeline/item/MessageFileItem.kt | 18 ++++++++++++++++++ .../timeline/item/MessageImageVideoItem.kt | 7 +++++-- .../features/media/ImageContentRenderer.kt | 6 ++---- .../layout/item_timeline_event_file_stub.xml | 2 +- 7 files changed, 37 insertions(+), 12 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/core/utils/FileUtils.kt b/vector/src/main/java/im/vector/riotx/core/utils/FileUtils.kt index 4b2d0682d2..5812c395e5 100644 --- a/vector/src/main/java/im/vector/riotx/core/utils/FileUtils.kt +++ b/vector/src/main/java/im/vector/riotx/core/utils/FileUtils.kt @@ -24,6 +24,8 @@ import java.io.File // Implementation should return true in case of success typealias ActionOnFile = (file: File) -> Boolean +internal fun String?.isLocalFile() = this != null && File(this).exists() + /* ========================================================================================== * Delete * ========================================================================================== */ diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 4819db4075..0dfa44563c 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -42,6 +42,7 @@ import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.core.utils.DebouncedClickListener import im.vector.riotx.core.utils.DimensionConverter import im.vector.riotx.core.utils.containsOnlyEmojis +import im.vector.riotx.core.utils.isLocalFile import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController import im.vector.riotx.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder @@ -117,6 +118,8 @@ class MessageItemFactory @Inject constructor( .avatarRenderer(avatarRenderer) .colorProvider(colorProvider) .dimensionConverter(dimensionConverter) + .izLocalFile(messageContent.getFileUrl().isLocalFile()) + .contentUploadStateTrackerBinder(contentUploadStateTrackerBinder) .informationData(informationData) .highlighted(highlight) .avatarCallback(callback) @@ -147,6 +150,8 @@ class MessageItemFactory @Inject constructor( .avatarRenderer(avatarRenderer) .colorProvider(colorProvider) .dimensionConverter(dimensionConverter) + .izLocalFile(messageContent.getFileUrl().isLocalFile()) + .contentUploadStateTrackerBinder(contentUploadStateTrackerBinder) .informationData(informationData) .highlighted(highlight) .avatarCallback(callback) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/ContentUploadStateTrackerBinder.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/ContentUploadStateTrackerBinder.kt index ca79666747..a7db7c826e 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/ContentUploadStateTrackerBinder.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/ContentUploadStateTrackerBinder.kt @@ -27,7 +27,6 @@ import im.vector.matrix.android.api.session.room.send.SendState import im.vector.riotx.R import im.vector.riotx.core.di.ActiveSessionHolder import im.vector.riotx.core.resources.ColorProvider -import im.vector.riotx.features.media.ImageContentRenderer import im.vector.riotx.features.ui.getMessageTextColor import javax.inject.Inject @@ -37,12 +36,12 @@ class ContentUploadStateTrackerBinder @Inject constructor(private val activeSess private val updateListeners = mutableMapOf() fun bind(eventId: String, - mediaData: ImageContentRenderer.Data, + isLocalFile: Boolean, progressLayout: ViewGroup) { activeSessionHolder.getActiveSession().also { session -> val uploadStateTracker = session.contentUploadProgressTracker() - val updateListener = ContentMediaProgressUpdater(progressLayout, mediaData, colorProvider) + val updateListener = ContentMediaProgressUpdater(progressLayout, isLocalFile, colorProvider) updateListeners[eventId] = updateListener uploadStateTracker.track(eventId, updateListener) } @@ -60,7 +59,7 @@ class ContentUploadStateTrackerBinder @Inject constructor(private val activeSess } private class ContentMediaProgressUpdater(private val progressLayout: ViewGroup, - private val mediaData: ImageContentRenderer.Data, + private val isLocalFile: Boolean, private val colorProvider: ColorProvider) : ContentUploadStateTracker.UpdateListener { override fun onUpdate(state: ContentUploadStateTracker.State) { @@ -76,7 +75,7 @@ private class ContentMediaProgressUpdater(private val progressLayout: ViewGroup, } private fun handleIdle(state: ContentUploadStateTracker.State.Idle) { - if (mediaData.isLocalFile()) { + if (isLocalFile) { progressLayout.isVisible = true val progressBar = progressLayout.findViewById(R.id.mediaProgressBar) val progressTextView = progressLayout.findViewById(R.id.mediaProgressTextView) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageFileItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageFileItem.kt index 45e57b59db..56d6a33bc7 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageFileItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageFileItem.kt @@ -22,9 +22,11 @@ import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView import androidx.annotation.DrawableRes +import androidx.core.view.isVisible import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.riotx.R +import im.vector.riotx.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder @EpoxyModelClass(layout = R.layout.item_timeline_event_base) abstract class MessageFileItem : AbsMessageItem() { @@ -36,19 +38,35 @@ abstract class MessageFileItem : AbsMessageItem() { var iconRes: Int = 0 @EpoxyAttribute var clickListener: View.OnClickListener? = null + @EpoxyAttribute + var izLocalFile = false + @EpoxyAttribute + lateinit var contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder override fun bind(holder: Holder) { super.bind(holder) renderSendState(holder.fileLayout, holder.filenameView) + if (!informationData.sendState.hasFailed()) { + contentUploadStateTrackerBinder.bind(informationData.eventId, izLocalFile, holder.progressLayout) + } else { + holder.progressLayout.isVisible = false + } holder.filenameView.text = filename holder.fileImageView.setImageResource(iconRes) holder.filenameView.setOnClickListener(clickListener) holder.filenameView.paintFlags = (holder.filenameView.paintFlags or Paint.UNDERLINE_TEXT_FLAG) } + override fun unbind(holder: Holder) { + super.unbind(holder) + + contentUploadStateTrackerBinder.unbind(informationData.eventId) + } + override fun getViewType() = STUB_ID class Holder : AbsMessageItem.Holder(STUB_ID) { + val progressLayout by bind(R.id.messageFileUploadProgressLayout) val fileLayout by bind(R.id.messageFileLayout) val fileImageView by bind(R.id.messageFileImageView) val filenameView by bind(R.id.messageFilenameView) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageImageVideoItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageImageVideoItem.kt index 6f713b17fe..50e263267a 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageImageVideoItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageImageVideoItem.kt @@ -20,6 +20,7 @@ import android.view.View import android.view.ViewGroup import android.widget.ImageView import androidx.core.view.ViewCompat +import androidx.core.view.isVisible import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.riotx.R @@ -44,11 +45,13 @@ abstract class MessageImageVideoItem : AbsMessageItem Date: Wed, 25 Sep 2019 14:44:34 +0200 Subject: [PATCH 15/22] Human readable error --- .../helper/ContentUploadStateTrackerBinder.kt | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/ContentUploadStateTrackerBinder.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/ContentUploadStateTrackerBinder.kt index a7db7c826e..09b2e86a83 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/ContentUploadStateTrackerBinder.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/ContentUploadStateTrackerBinder.kt @@ -26,12 +26,14 @@ import im.vector.matrix.android.api.session.content.ContentUploadStateTracker import im.vector.matrix.android.api.session.room.send.SendState import im.vector.riotx.R import im.vector.riotx.core.di.ActiveSessionHolder +import im.vector.riotx.core.error.ErrorFormatter import im.vector.riotx.core.resources.ColorProvider import im.vector.riotx.features.ui.getMessageTextColor import javax.inject.Inject class ContentUploadStateTrackerBinder @Inject constructor(private val activeSessionHolder: ActiveSessionHolder, - private val colorProvider: ColorProvider) { + private val colorProvider: ColorProvider, + private val errorFormatter: ErrorFormatter) { private val updateListeners = mutableMapOf() @@ -41,7 +43,7 @@ class ContentUploadStateTrackerBinder @Inject constructor(private val activeSess activeSessionHolder.getActiveSession().also { session -> val uploadStateTracker = session.contentUploadProgressTracker() - val updateListener = ContentMediaProgressUpdater(progressLayout, isLocalFile, colorProvider) + val updateListener = ContentMediaProgressUpdater(progressLayout, isLocalFile, colorProvider, errorFormatter) updateListeners[eventId] = updateListener uploadStateTracker.track(eventId, updateListener) } @@ -60,7 +62,8 @@ class ContentUploadStateTrackerBinder @Inject constructor(private val activeSess private class ContentMediaProgressUpdater(private val progressLayout: ViewGroup, private val isLocalFile: Boolean, - private val colorProvider: ColorProvider) : ContentUploadStateTracker.UpdateListener { + private val colorProvider: ColorProvider, + private val errorFormatter: ErrorFormatter) : ContentUploadStateTracker.UpdateListener { override fun onUpdate(state: ContentUploadStateTracker.State) { when (state) { @@ -133,7 +136,7 @@ private class ContentMediaProgressUpdater(private val progressLayout: ViewGroup, val progressBar = progressLayout.findViewById(R.id.mediaProgressBar) val progressTextView = progressLayout.findViewById(R.id.mediaProgressTextView) progressBar?.isVisible = false - progressTextView?.text = state.throwable.localizedMessage + progressTextView?.text = errorFormatter.toHumanReadable(state.throwable) progressTextView?.setTextColor(colorProvider.getMessageTextColor(SendState.UNDELIVERED)) } From 643a2baabf082dbcf8301d68b3b5c7a3e1e22407 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 25 Sep 2019 15:03:16 +0200 Subject: [PATCH 16/22] Set click and long click listener even if information data are not displayed --- .../home/room/detail/timeline/item/AbsMessageItem.kt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/AbsMessageItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/AbsMessageItem.kt index 8cc181bd37..fa132be365 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/AbsMessageItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/AbsMessageItem.kt @@ -111,8 +111,6 @@ abstract class AbsMessageItem : BaseEventItem() { holder.timeView.text = informationData.time holder.memberNameView.text = informationData.memberName avatarRenderer.render(informationData.avatarUrl, informationData.senderId, informationData.memberName?.toString(), holder.avatarImageView) - holder.view.setOnClickListener(cellClickListener) - holder.view.setOnLongClickListener(longClickListener) holder.avatarImageView.setOnLongClickListener(longClickListener) holder.memberNameView.setOnLongClickListener(longClickListener) } else { @@ -121,12 +119,13 @@ abstract class AbsMessageItem : BaseEventItem() { holder.avatarImageView.visibility = View.GONE holder.memberNameView.visibility = View.GONE holder.timeView.visibility = View.GONE - holder.view.setOnClickListener(null) - holder.view.setOnLongClickListener(null) holder.avatarImageView.setOnLongClickListener(null) holder.memberNameView.setOnLongClickListener(null) } + holder.view.setOnClickListener(cellClickListener) + holder.view.setOnLongClickListener(longClickListener) + holder.readReceiptsView.render(informationData.readReceipts, avatarRenderer, _readReceiptsClickListener) if (!shouldShowReactionAtBottom() || informationData.orderedReactionList.isNullOrEmpty()) { From 1b66d1f746374992f0e30a3226a001f88313c899 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 25 Sep 2019 15:25:26 +0200 Subject: [PATCH 17/22] Fix bad rendering of file item if the filename is long --- .../layout/item_timeline_event_file_stub.xml | 86 ++++++++++--------- 1 file changed, 44 insertions(+), 42 deletions(-) diff --git a/vector/src/main/res/layout/item_timeline_event_file_stub.xml b/vector/src/main/res/layout/item_timeline_event_file_stub.xml index 729509b859..259e22b466 100644 --- a/vector/src/main/res/layout/item_timeline_event_file_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_file_stub.xml @@ -2,53 +2,55 @@ + android:layout_height="wrap_content" + android:paddingTop="8dp" + android:paddingBottom="8dp"> - + + + + + + + app:layout_constraintStart_toEndOf="@+id/messageFileImageView" + app:layout_constraintTop_toTopOf="parent" + tools:text="A filename here" /> - - - - - - - - - + + From a0b1ef3216522f8a468b0e7db47cb20a9ba0e76f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 25 Sep 2019 16:59:50 +0200 Subject: [PATCH 18/22] Do not upload file to big for the homeserver (#587) Also create a HomeServerCapabilitiesService which provide configuration of the homeserver. Data are retrieved every 8 hours (as RiotWeb?) --- CHANGES.md | 3 +- .../matrix/android/api/session/Session.kt | 2 + .../homeserver/HomeServerCapabilities.kt | 28 +++++++ .../HomeServerCapabilitiesService.kt | 29 ++++++++ .../mapper/HomeServerCapabilitiesMapper.kt | 38 ++++++++++ .../model/HomeServerCapabilitiesEntity.kt | 29 ++++++++ .../database/model/SessionRealmModule.kt | 3 +- .../query/HomeServerCapabilitiesQueries.kt | 37 ++++++++++ .../internal/network/NetworkConstants.kt | 5 ++ .../internal/session/DefaultSession.kt | 7 +- .../internal/session/SessionComponent.kt | 2 + .../android/internal/session/SessionModule.kt | 5 ++ .../session/homeserver/CapabilitiesAPI.kt | 31 ++++++++ .../DefaultGetHomeServerCapabilitiesTask.kt | 74 +++++++++++++++++++ .../DefaultHomeServerCapabilitiesService.kt | 37 ++++++++++ .../homeserver/GetUploadCapabilitiesResult.kt | 30 ++++++++ .../HomeServerCapabilitiesModule.kt | 41 ++++++++++ .../android/internal/session/sync/SyncTask.kt | 7 +- .../home/room/detail/FileTooBigError.kt | 23 ++++++ .../home/room/detail/RoomDetailFragment.kt | 17 +++++ .../home/room/detail/RoomDetailViewModel.kt | 20 ++++- vector/src/main/res/values/strings_riotX.xml | 2 + 22 files changed, 463 insertions(+), 7 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/homeserver/HomeServerCapabilities.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/homeserver/HomeServerCapabilitiesService.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/HomeServerCapabilitiesMapper.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/HomeServerCapabilitiesEntity.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/HomeServerCapabilitiesQueries.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/CapabilitiesAPI.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/DefaultGetHomeServerCapabilitiesTask.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/DefaultHomeServerCapabilitiesService.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/GetUploadCapabilitiesResult.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/HomeServerCapabilitiesModule.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/home/room/detail/FileTooBigError.kt diff --git a/CHANGES.md b/CHANGES.md index 53ed7748de..396419ea66 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,12 +6,13 @@ Features: Improvements: - Persist active tab between sessions (#503) + - Do not upload file to big for the homeserver (#587) Other changes: - Bugfix: - - + - Fix issue on upload error in loop (#587) Translations: - 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 53dc8e77a0..31e96bb3b8 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 @@ -26,6 +26,7 @@ import im.vector.matrix.android.api.session.content.ContentUrlResolver import im.vector.matrix.android.api.session.crypto.CryptoService import im.vector.matrix.android.api.session.file.FileService import im.vector.matrix.android.api.session.group.GroupService +import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilitiesService import im.vector.matrix.android.api.session.pushers.PushersService import im.vector.matrix.android.api.session.room.RoomDirectoryService import im.vector.matrix.android.api.session.room.RoomService @@ -52,6 +53,7 @@ interface Session : PushRuleService, PushersService, InitialSyncProgressService, + HomeServerCapabilitiesService, SecureStorageService { /** diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/homeserver/HomeServerCapabilities.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/homeserver/HomeServerCapabilities.kt new file mode 100644 index 0000000000..215516a6c1 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/homeserver/HomeServerCapabilities.kt @@ -0,0 +1,28 @@ +/* + * 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.matrix.android.api.session.homeserver + +data class HomeServerCapabilities( + /** + * Max size of file which can be uploaded to the homeserver in bytes. [MAX_UPLOAD_FILE_SIZE_UNKNOWN] if unknown or not retrieved yet + */ + val maxUploadFileSize: Long +) { + companion object { + const val MAX_UPLOAD_FILE_SIZE_UNKNOWN = -1L + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/homeserver/HomeServerCapabilitiesService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/homeserver/HomeServerCapabilitiesService.kt new file mode 100644 index 0000000000..f7107e9d47 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/homeserver/HomeServerCapabilitiesService.kt @@ -0,0 +1,29 @@ +/* + * 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.matrix.android.api.session.homeserver + +/** + * This interface defines a method to sign out. It's implemented at the session level. + */ +interface HomeServerCapabilitiesService { + + /** + * Get the HomeServer capabilities + */ + fun getHomeServerCapabilities(): HomeServerCapabilities + +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/HomeServerCapabilitiesMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/HomeServerCapabilitiesMapper.kt new file mode 100644 index 0000000000..23ab7b64be --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/HomeServerCapabilitiesMapper.kt @@ -0,0 +1,38 @@ +/* + * 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.matrix.android.internal.database.mapper + +import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilities +import im.vector.matrix.android.internal.database.model.HomeServerCapabilitiesEntity + +/** + * HomeServerCapabilitiesEntity <-> HomeSeverCapabilities + */ +internal object HomeServerCapabilitiesMapper { + + fun map(entity: HomeServerCapabilitiesEntity): HomeServerCapabilities { + return HomeServerCapabilities( + entity.maxUploadFileSize + ) + } + + fun map(domain: HomeServerCapabilities): HomeServerCapabilitiesEntity { + return HomeServerCapabilitiesEntity( + domain.maxUploadFileSize + ) + } +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/HomeServerCapabilitiesEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/HomeServerCapabilitiesEntity.kt new file mode 100644 index 0000000000..2bed0305c7 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/HomeServerCapabilitiesEntity.kt @@ -0,0 +1,29 @@ +/* + * 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.matrix.android.internal.database.model + +import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilities +import io.realm.RealmObject + +internal open class HomeServerCapabilitiesEntity( + var maxUploadFileSize: Long = HomeServerCapabilities.MAX_UPLOAD_FILE_SIZE_UNKNOWN, + var lastUpdatedTimestamp: Long = 0L +) : RealmObject() { + + companion object + +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/SessionRealmModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/SessionRealmModule.kt index 680e2eac7d..ffe20d9efe 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/SessionRealmModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/SessionRealmModule.kt @@ -45,6 +45,7 @@ import io.realm.annotations.RealmModule PusherDataEntity::class, ReadReceiptsSummaryEntity::class, UserDraftsEntity::class, - DraftEntity::class + DraftEntity::class, + HomeServerCapabilitiesEntity::class ]) internal class SessionRealmModule diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/HomeServerCapabilitiesQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/HomeServerCapabilitiesQueries.kt new file mode 100644 index 0000000000..64cd6e4770 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/HomeServerCapabilitiesQueries.kt @@ -0,0 +1,37 @@ +/* + * 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.matrix.android.internal.database.query + +import im.vector.matrix.android.internal.database.model.HomeServerCapabilitiesEntity +import io.realm.Realm +import io.realm.kotlin.createObject +import io.realm.kotlin.where + +/** + * Get the current HomeServerCapabilitiesEntity, create one if it does not exist + */ +internal fun HomeServerCapabilitiesEntity.Companion.getOrCreate(realm: Realm): HomeServerCapabilitiesEntity { + var homeServerCapabilitiesEntity = realm.where().findFirst() + if (homeServerCapabilitiesEntity == null) { + realm.executeTransaction { + realm.createObject() + } + homeServerCapabilitiesEntity = realm.where().findFirst()!! + } + + return homeServerCapabilitiesEntity +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/NetworkConstants.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/NetworkConstants.kt index cbd4d0c674..02ac778fcc 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/NetworkConstants.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/NetworkConstants.kt @@ -22,4 +22,9 @@ internal object NetworkConstants { const val URI_API_PREFIX_PATH_R0 = "$URI_API_PREFIX_PATH/r0/" const val URI_API_PREFIX_PATH_UNSTABLE = "$URI_API_PREFIX_PATH/unstable/" + + // Media + private const val URI_API_MEDIA_PREFIX_PATH = "_matrix/media" + const val URI_API_MEDIA_PREFIX_PATH_R0 = "$URI_API_MEDIA_PREFIX_PATH/r0/" + } 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 02addaceab..319cce491b 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 @@ -32,6 +32,7 @@ import im.vector.matrix.android.api.session.content.ContentUrlResolver import im.vector.matrix.android.api.session.crypto.CryptoService import im.vector.matrix.android.api.session.file.FileService import im.vector.matrix.android.api.session.group.GroupService +import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilitiesService import im.vector.matrix.android.api.session.pushers.PushersService import im.vector.matrix.android.api.session.room.RoomDirectoryService import im.vector.matrix.android.api.session.room.RoomService @@ -68,7 +69,8 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se private val syncThreadProvider: Provider, private val contentUrlResolver: ContentUrlResolver, private val contentUploadProgressTracker: ContentUploadStateTracker, - private val initialSyncProgressService: Lazy) + private val initialSyncProgressService: Lazy, + private val homeServerCapabilitiesService: Lazy) : Session, RoomService by roomService.get(), RoomDirectoryService by roomDirectoryService.get(), @@ -81,7 +83,8 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se PushersService by pushersService.get(), FileService by fileService.get(), InitialSyncProgressService by initialSyncProgressService.get(), - SecureStorageService by secureStorageService.get() { + SecureStorageService by secureStorageService.get(), + HomeServerCapabilitiesService by homeServerCapabilitiesService.get() { private var isOpen = false diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionComponent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionComponent.kt index c8745fc356..b2ed02ff3e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionComponent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionComponent.kt @@ -30,6 +30,7 @@ import im.vector.matrix.android.internal.session.content.UploadContentWorker import im.vector.matrix.android.internal.session.filter.FilterModule import im.vector.matrix.android.internal.session.group.GetGroupDataWorker import im.vector.matrix.android.internal.session.group.GroupModule +import im.vector.matrix.android.internal.session.homeserver.HomeServerCapabilitiesModule import im.vector.matrix.android.internal.session.pushers.AddHttpPusherWorker import im.vector.matrix.android.internal.session.pushers.PushersModule import im.vector.matrix.android.internal.session.room.RoomModule @@ -51,6 +52,7 @@ import im.vector.matrix.android.internal.task.TaskExecutor SessionModule::class, RoomModule::class, SyncModule::class, + HomeServerCapabilitiesModule::class, SignOutModule::class, GroupModule::class, UserModule::class, 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 db4997ca89..7b655dd939 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 @@ -27,6 +27,7 @@ import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig import im.vector.matrix.android.api.auth.data.SessionParams import im.vector.matrix.android.api.session.InitialSyncProgressService import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilitiesService import im.vector.matrix.android.api.session.securestorage.SecureStorageService import im.vector.matrix.android.internal.database.LiveEntityObserver import im.vector.matrix.android.internal.database.RealmKeysUtils @@ -36,6 +37,7 @@ import im.vector.matrix.android.internal.network.AccessTokenInterceptor import im.vector.matrix.android.internal.network.RetrofitFactory import im.vector.matrix.android.internal.network.interceptors.CurlLoggingInterceptor import im.vector.matrix.android.internal.session.group.GroupSummaryUpdater +import im.vector.matrix.android.internal.session.homeserver.DefaultHomeServerCapabilitiesService import im.vector.matrix.android.internal.session.room.EventRelationsAggregationUpdater import im.vector.matrix.android.internal.session.room.create.RoomCreateEventLiveObserver import im.vector.matrix.android.internal.session.room.prune.EventsPruner @@ -178,4 +180,7 @@ internal abstract class SessionModule { @Binds abstract fun bindSecureStorageService(secureStorageService: DefaultSecureStorageService): SecureStorageService + @Binds + abstract fun bindHomeServerCapabilitiesService(homeServerCapabilitiesService: DefaultHomeServerCapabilitiesService): HomeServerCapabilitiesService + } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/CapabilitiesAPI.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/CapabilitiesAPI.kt new file mode 100644 index 0000000000..69972a1f57 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/CapabilitiesAPI.kt @@ -0,0 +1,31 @@ +/* + * 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.matrix.android.internal.session.homeserver + +import im.vector.matrix.android.internal.network.NetworkConstants +import retrofit2.Call +import retrofit2.http.GET + +internal interface CapabilitiesAPI { + + /** + * Request the upload capabilities + */ + @GET(NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0 + "config") + fun getUploadCapabilities(): Call + +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/DefaultGetHomeServerCapabilitiesTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/DefaultGetHomeServerCapabilitiesTask.kt new file mode 100644 index 0000000000..d3cb945adb --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/DefaultGetHomeServerCapabilitiesTask.kt @@ -0,0 +1,74 @@ +/* + * 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.matrix.android.internal.session.homeserver + +import com.zhuinden.monarchy.Monarchy +import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilities +import im.vector.matrix.android.internal.database.model.HomeServerCapabilitiesEntity +import im.vector.matrix.android.internal.database.query.getOrCreate +import im.vector.matrix.android.internal.network.executeRequest +import im.vector.matrix.android.internal.task.Task +import java.util.* +import javax.inject.Inject + +internal interface GetHomeServerCapabilitiesTask : Task + +internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor( + private val capabilitiesAPI: CapabilitiesAPI, + private val monarchy: Monarchy +) : GetHomeServerCapabilitiesTask { + + + override suspend fun execute(params: Unit) { + var doRequest = false + monarchy.doWithRealm { realm -> + val homeServerCapabilitiesEntity = HomeServerCapabilitiesEntity.getOrCreate(realm) + + doRequest = homeServerCapabilitiesEntity.lastUpdatedTimestamp + MIN_DELAY_BETWEEN_TWO_REQUEST_MILLIS < Date().time + } + + if (!doRequest) { + return + } + + val uploadCapabilities = executeRequest { + apiCall = capabilitiesAPI.getUploadCapabilities() + } + + // TODO Add other call here (get version, etc.) + + insertInDb(uploadCapabilities) + } + + + private fun insertInDb(getUploadCapabilitiesResult: GetUploadCapabilitiesResult) { + monarchy + .writeAsync { realm -> + val homeServerCapabilitiesEntity = HomeServerCapabilitiesEntity.getOrCreate(realm) + + homeServerCapabilitiesEntity.maxUploadFileSize = getUploadCapabilitiesResult.maxUploadSize + ?: HomeServerCapabilities.MAX_UPLOAD_FILE_SIZE_UNKNOWN + + homeServerCapabilitiesEntity.lastUpdatedTimestamp = Date().time + } + } + + companion object { + // 8 hours like on Riot Web + private const val MIN_DELAY_BETWEEN_TWO_REQUEST_MILLIS = 8 * 60 * 60 * 1000 + } +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/DefaultHomeServerCapabilitiesService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/DefaultHomeServerCapabilitiesService.kt new file mode 100644 index 0000000000..6f6416ba4a --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/DefaultHomeServerCapabilitiesService.kt @@ -0,0 +1,37 @@ +/* + * 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.matrix.android.internal.session.homeserver + +import com.zhuinden.monarchy.Monarchy +import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilities +import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilitiesService +import im.vector.matrix.android.internal.database.mapper.HomeServerCapabilitiesMapper +import im.vector.matrix.android.internal.database.model.HomeServerCapabilitiesEntity +import im.vector.matrix.android.internal.database.query.getOrCreate +import javax.inject.Inject + +internal class DefaultHomeServerCapabilitiesService @Inject constructor(private val monarchy: Monarchy) : HomeServerCapabilitiesService { + + override fun getHomeServerCapabilities(): HomeServerCapabilities { + var entity: HomeServerCapabilitiesEntity? = null + monarchy.doWithRealm { realm -> + entity = HomeServerCapabilitiesEntity.getOrCreate(realm) + } + + return entity?.let { HomeServerCapabilitiesMapper.map(it) } ?: HomeServerCapabilities(HomeServerCapabilities.MAX_UPLOAD_FILE_SIZE_UNKNOWN) + } +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/GetUploadCapabilitiesResult.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/GetUploadCapabilitiesResult.kt new file mode 100644 index 0000000000..8e410cc834 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/GetUploadCapabilitiesResult.kt @@ -0,0 +1,30 @@ +/* + * 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.matrix.android.internal.session.homeserver + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class GetUploadCapabilitiesResult( + /** + * The maximum size an upload can be in bytes. Clients SHOULD use this as a guide when uploading content. + * If not listed or null, the size limit should be treated as unknown. + */ + @Json(name = "m.upload.size") + val maxUploadSize: Long? = null +) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/HomeServerCapabilitiesModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/HomeServerCapabilitiesModule.kt new file mode 100644 index 0000000000..71b3ee63b8 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/HomeServerCapabilitiesModule.kt @@ -0,0 +1,41 @@ +/* + * 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.matrix.android.internal.session.homeserver + +import dagger.Binds +import dagger.Module +import dagger.Provides +import im.vector.matrix.android.internal.session.SessionScope +import retrofit2.Retrofit + +@Module +internal abstract class HomeServerCapabilitiesModule { + + @Module + companion object { + @Provides + @JvmStatic + @SessionScope + fun providesCapabilitiesAPI(retrofit: Retrofit): CapabilitiesAPI { + return retrofit.create(CapabilitiesAPI::class.java) + } + } + + @Binds + abstract fun bindGetHomeServerCapabilitiesTask(getHomeServerCapabilitiesTask: DefaultGetHomeServerCapabilitiesTask): GetHomeServerCapabilitiesTask + +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTask.kt index 28d4d5fc48..f9cbd05d26 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTask.kt @@ -16,7 +16,6 @@ package im.vector.matrix.android.internal.session.sync -import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.R import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.failure.MatrixError @@ -25,6 +24,7 @@ import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.DefaultInitialSyncProgressService import im.vector.matrix.android.internal.session.filter.FilterRepository +import im.vector.matrix.android.internal.session.homeserver.GetHomeServerCapabilitiesTask import im.vector.matrix.android.internal.session.sync.model.SyncResponse import im.vector.matrix.android.internal.task.Task import javax.inject.Inject @@ -42,11 +42,14 @@ internal class DefaultSyncTask @Inject constructor(private val syncAPI: SyncAPI, private val sessionParamsStore: SessionParamsStore, private val initialSyncProgressService: DefaultInitialSyncProgressService, private val syncTokenStore: SyncTokenStore, - private val monarchy: Monarchy + private val getHomeServerCapabilitiesTask: GetHomeServerCapabilitiesTask ) : SyncTask { override suspend fun execute(params: SyncTask.Params) { + // Maybe refresh the home server capabilities data we know + getHomeServerCapabilitiesTask.execute(Unit) + val requestParams = HashMap() var timeout = 0L val token = syncTokenStore.getLastToken() diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/FileTooBigError.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/FileTooBigError.kt new file mode 100644 index 0000000000..0f9bfebb47 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/FileTooBigError.kt @@ -0,0 +1,23 @@ +/* + * 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.riotx.features.home.room.detail + +data class FileTooBigError( + val filename: String, + val fileSizeInBytes: Long, + val homeServerLimitInBytes: Long +) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index 7bc5cf7016..7934e0ccae 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -28,6 +28,7 @@ import android.os.Parcelable import android.text.Editable import android.text.Spannable import android.text.TextUtils +import android.text.format.Formatter import android.view.* import android.view.inputmethod.InputMethodManager import android.widget.TextView @@ -227,6 +228,10 @@ class RoomDetailFragment : scrollOnHighlightedEventCallback.scheduleScrollTo(it) } + roomDetailViewModel.fileTooBigEvent.observeEvent(this) { + displayFileTooBigWarning(it) + } + roomDetailViewModel.selectSubscribe(this, RoomDetailViewState::tombstoneEventHandling, uniqueOnly("tombstoneEventHandling")) { renderTombstoneEventHandling(it) } @@ -254,6 +259,18 @@ class RoomDetailFragment : } } + private fun displayFileTooBigWarning(error: FileTooBigError) { + AlertDialog.Builder(requireActivity()) + .setTitle(R.string.dialog_title_error) + .setMessage(getString(R.string.error_file_too_big, + error.filename, + Formatter.formatFileSize(requireContext(), error.homeServerLimitInBytes), + Formatter.formatFileSize(requireContext(), error.fileSizeInBytes) + )) + .setPositiveButton(R.string.ok, null) + .show() + } + private fun setupNotificationView() { notificationAreaView.delegate = object : NotificationAreaView.Delegate { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index ac56114319..d31a25a8d9 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -37,6 +37,7 @@ import im.vector.matrix.android.api.session.events.model.isImageMessage import im.vector.matrix.android.api.session.events.model.isTextMessage import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.file.FileService +import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilities import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.message.MessageContent import im.vector.matrix.android.api.session.room.model.message.MessageType @@ -228,6 +229,10 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro val navigateToEvent: LiveData> get() = _navigateToEvent + private val _fileTooBigEvent = MutableLiveData>() + val fileTooBigEvent: LiveData> + get() = _fileTooBigEvent + private val _downloadedFileEvent = MutableLiveData>() val downloadedFileEvent: LiveData> get() = _downloadedFileEvent @@ -466,7 +471,20 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro type = ContentAttachmentData.Type.values()[it.mediaType] ) } - room.sendMedias(attachments) + + val homeServerCapabilities = session.getHomeServerCapabilities() + + val maxUploadFileSize = homeServerCapabilities.maxUploadFileSize + + if (maxUploadFileSize == HomeServerCapabilities.MAX_UPLOAD_FILE_SIZE_UNKNOWN) { + // Unknown limitation + room.sendMedias(attachments) + } else { + attachments.find { it.size > maxUploadFileSize } + ?.let { + _fileTooBigEvent.postValue(LiveEvent(FileTooBigError(it.name ?: it.path, it.size, maxUploadFileSize))) + } ?: run { room.sendMedias(attachments) } + } } private fun handleEventDisplayed(action: RoomDetailActions.EventDisplayed) { diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index 6b46d359be..7d3312cf24 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -22,4 +22,6 @@ Create a new room Close keys backup banner + The file %1$s is too large to upload. The file size limit is %2$s but this file is %3$s. + \ No newline at end of file From 60f6b3ef02a6e2b62f031aa6cff161ff50f2c941 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 25 Sep 2019 17:08:58 +0200 Subject: [PATCH 19/22] Auto review --- CHANGES.md | 2 +- .../api/session/homeserver/HomeServerCapabilitiesService.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 396419ea66..dc4c743bc4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,7 +6,7 @@ Features: Improvements: - Persist active tab between sessions (#503) - - Do not upload file to big for the homeserver (#587) + - Do not upload file too big for the homeserver (#587) Other changes: - diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/homeserver/HomeServerCapabilitiesService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/homeserver/HomeServerCapabilitiesService.kt index f7107e9d47..8e23c21068 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/homeserver/HomeServerCapabilitiesService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/homeserver/HomeServerCapabilitiesService.kt @@ -17,7 +17,7 @@ package im.vector.matrix.android.api.session.homeserver /** - * This interface defines a method to sign out. It's implemented at the session level. + * This interface defines a method to retrieve the homeserver capabilities. */ interface HomeServerCapabilitiesService { From 62b7a83a31849bb047ec1271f55fee8613c2d169 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 26 Sep 2019 10:08:44 +0200 Subject: [PATCH 20/22] Update after Dominaezzz's review --- .../DefaultGetHomeServerCapabilitiesTask.kt | 3 ++- .../DefaultHomeServerCapabilitiesService.kt | 11 +++++++++-- .../features/home/room/detail/RoomDetailFragment.kt | 4 ++-- .../features/home/room/detail/RoomDetailViewModel.kt | 8 ++++---- vector/src/main/res/values/strings_riotX.xml | 2 +- 5 files changed, 18 insertions(+), 10 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/DefaultGetHomeServerCapabilitiesTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/DefaultGetHomeServerCapabilitiesTask.kt index d3cb945adb..defcfbaa81 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/DefaultGetHomeServerCapabilitiesTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/DefaultGetHomeServerCapabilitiesTask.kt @@ -22,6 +22,7 @@ import im.vector.matrix.android.internal.database.model.HomeServerCapabilitiesEn import im.vector.matrix.android.internal.database.query.getOrCreate import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.task.Task +import im.vector.matrix.android.internal.util.awaitTransaction import java.util.* import javax.inject.Inject @@ -35,7 +36,7 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor( override suspend fun execute(params: Unit) { var doRequest = false - monarchy.doWithRealm { realm -> + monarchy.awaitTransaction { realm -> val homeServerCapabilitiesEntity = HomeServerCapabilitiesEntity.getOrCreate(realm) doRequest = homeServerCapabilitiesEntity.lastUpdatedTimestamp + MIN_DELAY_BETWEEN_TWO_REQUEST_MILLIS < Date().time diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/DefaultHomeServerCapabilitiesService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/DefaultHomeServerCapabilitiesService.kt index 6f6416ba4a..0af849af2e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/DefaultHomeServerCapabilitiesService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/DefaultHomeServerCapabilitiesService.kt @@ -32,6 +32,13 @@ internal class DefaultHomeServerCapabilitiesService @Inject constructor(private entity = HomeServerCapabilitiesEntity.getOrCreate(realm) } - return entity?.let { HomeServerCapabilitiesMapper.map(it) } ?: HomeServerCapabilities(HomeServerCapabilities.MAX_UPLOAD_FILE_SIZE_UNKNOWN) + return with(entity) { + if (this != null) { + HomeServerCapabilitiesMapper.map(this) + } else { + // Should not happen + HomeServerCapabilities(HomeServerCapabilities.MAX_UPLOAD_FILE_SIZE_UNKNOWN) + } + } } -} \ No newline at end of file +} diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index 7934e0ccae..7c7b90cd4b 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -264,8 +264,8 @@ class RoomDetailFragment : .setTitle(R.string.dialog_title_error) .setMessage(getString(R.string.error_file_too_big, error.filename, - Formatter.formatFileSize(requireContext(), error.homeServerLimitInBytes), - Formatter.formatFileSize(requireContext(), error.fileSizeInBytes) + Formatter.formatFileSize(requireContext(), error.fileSizeInBytes), + Formatter.formatFileSize(requireContext(), error.homeServerLimitInBytes) )) .setPositiveButton(R.string.ok, null) .show() diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index d31a25a8d9..f1d2431351 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -480,10 +480,10 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro // Unknown limitation room.sendMedias(attachments) } else { - attachments.find { it.size > maxUploadFileSize } - ?.let { - _fileTooBigEvent.postValue(LiveEvent(FileTooBigError(it.name ?: it.path, it.size, maxUploadFileSize))) - } ?: run { room.sendMedias(attachments) } + when (val tooBigFile = attachments.find { it.size > maxUploadFileSize }) { + null -> room.sendMedias(attachments) + else -> _fileTooBigEvent.postValue(LiveEvent(FileTooBigError(tooBigFile.name ?: tooBigFile.path, tooBigFile.size, maxUploadFileSize))) + } } } diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index 7d3312cf24..a48376e085 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -22,6 +22,6 @@ Create a new room Close keys backup banner - The file %1$s is too large to upload. The file size limit is %2$s but this file is %3$s. + "The file '%1$s' (%2$s) is too large to upload. The limit is %3$s." \ No newline at end of file From f02f16d9c55d7bf037abddcbed54ede96ea698c2 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 26 Sep 2019 10:41:52 +0200 Subject: [PATCH 21/22] Use IEC units instead of SI units for file sizes --- tools/check/forbidden_strings_in_code.txt | 8 ++++- .../im/vector/riotx/core/utils/TextUtils.kt | 27 +++++++++++++++++ .../home/room/detail/RoomDetailFragment.kt | 29 ++++++++----------- .../helper/ContentUploadStateTrackerBinder.kt | 6 ++-- .../settings/VectorSettingsGeneralFragment.kt | 14 ++++----- 5 files changed, 56 insertions(+), 28 deletions(-) diff --git a/tools/check/forbidden_strings_in_code.txt b/tools/check/forbidden_strings_in_code.txt index 7686cb0b7c..7e00295a48 100644 --- a/tools/check/forbidden_strings_in_code.txt +++ b/tools/check/forbidden_strings_in_code.txt @@ -149,4 +149,10 @@ android\.app\.AlertDialog new Gson\(\) ### Use matrixOneTimeWorkRequestBuilder -import androidx.work.OneTimeWorkRequestBuilder===1 \ No newline at end of file +import androidx.work.OneTimeWorkRequestBuilder===1 + +### Use TextUtils.formatFileSize +Formatter\.formatFileSize===1 + +### Use TextUtils.formatFileSize with short format param to true +Formatter\.formatShortFileSize===1 diff --git a/vector/src/main/java/im/vector/riotx/core/utils/TextUtils.kt b/vector/src/main/java/im/vector/riotx/core/utils/TextUtils.kt index 9d980e7f98..a7fa2cb350 100644 --- a/vector/src/main/java/im/vector/riotx/core/utils/TextUtils.kt +++ b/vector/src/main/java/im/vector/riotx/core/utils/TextUtils.kt @@ -16,6 +16,9 @@ package im.vector.riotx.core.utils +import android.content.Context +import android.os.Build +import android.text.format.Formatter import java.util.* object TextUtils { @@ -42,4 +45,28 @@ object TextUtils { return value.toString() } } + + /** + * Since Android O, the system considers that 1ko = 1000 bytes instead of 1024 bytes. We want to avoid that for the moment. + */ + fun formatFileSize(context: Context, sizeBytes: Long, useShortFormat: Boolean = false): String { + val normalizedSize = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N) { + sizeBytes + } else { + // First convert the size + when { + sizeBytes < 1024 -> sizeBytes + sizeBytes < 1024 * 1024 -> sizeBytes * 1000 / 1024 + sizeBytes < 1024 * 1024 * 1024 -> sizeBytes * 1000 / 1024 * 1000 / 1024 + else -> sizeBytes * 1000 / 1024 * 1000 / 1024 * 1000 / 1024 + } + } + + return if (useShortFormat) { + Formatter.formatShortFileSize(context, normalizedSize) + } else { + Formatter.formatFileSize(context, normalizedSize) + + } + } } \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index 7c7b90cd4b..ad9201c628 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -27,8 +27,6 @@ import android.os.Bundle import android.os.Parcelable import android.text.Editable import android.text.Spannable -import android.text.TextUtils -import android.text.format.Formatter import android.view.* import android.view.inputmethod.InputMethodManager import android.widget.TextView @@ -149,18 +147,15 @@ class RoomDetailFragment : * @param displayName the display name to sanitize * @return the sanitized display name */ - fun sanitizeDisplayname(displayName: String): String? { - // sanity checks - if (!TextUtils.isEmpty(displayName)) { - val ircPattern = " (IRC)" - - if (displayName.endsWith(ircPattern)) { - return displayName.substring(0, displayName.length - ircPattern.length) - } + private fun sanitizeDisplayName(displayName: String): String { + if (displayName.endsWith(ircPattern)) { + return displayName.substring(0, displayName.length - ircPattern.length) } return displayName } + + private const val ircPattern = " (IRC)" } private val roomDetailArgs: RoomDetailArgs by args() @@ -264,8 +259,8 @@ class RoomDetailFragment : .setTitle(R.string.dialog_title_error) .setMessage(getString(R.string.error_file_too_big, error.filename, - Formatter.formatFileSize(requireContext(), error.fileSizeInBytes), - Formatter.formatFileSize(requireContext(), error.homeServerLimitInBytes) + TextUtils.formatFileSize(requireContext(), error.fileSizeInBytes), + TextUtils.formatFileSize(requireContext(), error.homeServerLimitInBytes) )) .setPositiveButton(R.string.ok, null) .show() @@ -987,23 +982,23 @@ class RoomDetailFragment : // var vibrate = false val myDisplayName = session.getUser(session.myUserId)?.displayName - if (TextUtils.equals(myDisplayName, text)) { + if (myDisplayName == text) { // current user - if (TextUtils.isEmpty(composerLayout.composerEditText.text)) { + if (composerLayout.composerEditText.text.isBlank()) { composerLayout.composerEditText.append(Command.EMOTE.command + " ") composerLayout.composerEditText.setSelection(composerLayout.composerEditText.text.length) // vibrate = true } } else { // another user - if (TextUtils.isEmpty(composerLayout.composerEditText.text)) { + if (composerLayout.composerEditText.text.isBlank()) { // Ensure displayName will not be interpreted as a Slash command if (text.startsWith("/")) { composerLayout.composerEditText.append("\\") } - composerLayout.composerEditText.append(sanitizeDisplayname(text)!! + ": ") + composerLayout.composerEditText.append(sanitizeDisplayName(text) + ": ") } else { - composerLayout.composerEditText.text.insert(composerLayout.composerEditText.selectionStart, sanitizeDisplayname(text)!! + " ") + composerLayout.composerEditText.text.insert(composerLayout.composerEditText.selectionStart, sanitizeDisplayName(text) + " ") } // vibrate = true diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/ContentUploadStateTrackerBinder.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/ContentUploadStateTrackerBinder.kt index 09b2e86a83..96cb7f0d8e 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/ContentUploadStateTrackerBinder.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/ContentUploadStateTrackerBinder.kt @@ -16,7 +16,6 @@ package im.vector.riotx.features.home.room.detail.timeline.helper -import android.text.format.Formatter import android.view.View import android.view.ViewGroup import android.widget.ProgressBar @@ -28,6 +27,7 @@ import im.vector.riotx.R import im.vector.riotx.core.di.ActiveSessionHolder import im.vector.riotx.core.error.ErrorFormatter import im.vector.riotx.core.resources.ColorProvider +import im.vector.riotx.core.utils.TextUtils import im.vector.riotx.features.ui.getMessageTextColor import javax.inject.Inject @@ -126,8 +126,8 @@ private class ContentMediaProgressUpdater(private val progressLayout: ViewGroup, progressBar?.isIndeterminate = false progressBar?.progress = percent.toInt() progressTextView?.text = progressLayout.context.getString(resId, - Formatter.formatShortFileSize(progressLayout.context, current), - Formatter.formatShortFileSize(progressLayout.context, total)) + TextUtils.formatFileSize(progressLayout.context, current, true), + TextUtils.formatFileSize(progressLayout.context, total, true)) progressTextView?.setTextColor(colorProvider.getMessageTextColor(SendState.SENDING)) } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsGeneralFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsGeneralFragment.kt index 2bec8cf103..e51feb2363 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsGeneralFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsGeneralFragment.kt @@ -20,7 +20,7 @@ import android.app.Activity import android.content.Context import android.content.Intent import android.text.Editable -import android.text.TextUtils +import android.util.Patterns import android.view.View import android.view.ViewGroup import android.view.inputmethod.InputMethodManager @@ -171,7 +171,7 @@ class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() { MXSession.getApplicationSizeCaches(activity, object : SimpleApiCallback() { override fun onSuccess(size: Long) { if (null != activity) { - it.summary = android.text.format.Formatter.formatFileSize(activity, size) + it.summary = TextUtils.formatFileSize(activity, size) } } }) @@ -189,7 +189,7 @@ class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() { val size = getSizeOfFiles(requireContext(), File(requireContext().cacheDir, DiskCache.Factory.DEFAULT_DISK_CACHE_DIR)) - it.summary = android.text.format.Formatter.formatFileSize(activity, size.toLong()) + it.summary = TextUtils.formatFileSize(requireContext(), size.toLong()) it.onPreferenceClickListener = Preference.OnPreferenceClickListener { GlobalScope.launch(Dispatchers.Main) { @@ -208,7 +208,7 @@ class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() { File(requireContext().cacheDir, DiskCache.Factory.DEFAULT_DISK_CACHE_DIR)) } - it.summary = android.text.format.Formatter.formatFileSize(activity, newSize.toLong()) + it.summary = TextUtils.formatFileSize(requireContext(), newSize.toLong()) hideLoadingView() } @@ -534,7 +534,7 @@ class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() { private fun addEmail(email: String) { // check first if the email syntax is valid // if email is null , then also its invalid email - if (TextUtils.isEmpty(email) || !android.util.Patterns.EMAIL_ADDRESS.matcher(email).matches()) { + if (email.isBlank() || !Patterns.EMAIL_ADDRESS.matcher(email).matches()) { activity?.toast(R.string.auth_invalid_email) return } @@ -719,9 +719,9 @@ class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() { val newPwd = newPasswordText.text.toString().trim() val newConfirmPwd = confirmNewPasswordText.text.toString().trim() - updateButton.isEnabled = oldPwd.isNotEmpty() && newPwd.isNotEmpty() && TextUtils.equals(newPwd, newConfirmPwd) + updateButton.isEnabled = oldPwd.isNotEmpty() && newPwd.isNotEmpty() && newPwd == newConfirmPwd - if (newPwd.isNotEmpty() && newConfirmPwd.isNotEmpty() && !TextUtils.equals(newPwd, newConfirmPwd)) { + if (newPwd.isNotEmpty() && newConfirmPwd.isNotEmpty() && newPwd != newConfirmPwd) { confirmNewPasswordTil.error = getString(R.string.passwords_do_not_match) } } From 810a97c63999c1f37ff10d28cde6d6c71de71f34 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 26 Sep 2019 11:14:13 +0200 Subject: [PATCH 22/22] Import string from Android-SDK (#355) --- .../src/main/res/values-en-rUS/strings.xml | 11 +++++++++++ tools/import_from_riot.sh | 1 + 2 files changed, 12 insertions(+) create mode 100644 matrix-sdk-android/src/main/res/values-en-rUS/strings.xml diff --git a/matrix-sdk-android/src/main/res/values-en-rUS/strings.xml b/matrix-sdk-android/src/main/res/values-en-rUS/strings.xml new file mode 100644 index 0000000000..09e9dfc111 --- /dev/null +++ b/matrix-sdk-android/src/main/res/values-en-rUS/strings.xml @@ -0,0 +1,11 @@ + + + + + + Wrench + Airplane + + + + diff --git a/tools/import_from_riot.sh b/tools/import_from_riot.sh index cdc4275095..4bc19ca86c 100755 --- a/tools/import_from_riot.sh +++ b/tools/import_from_riot.sh @@ -32,6 +32,7 @@ cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-da/strings.xml ./mat cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-de/strings.xml ./matrix-sdk-android/src/main/res/values-de/strings.xml cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-el/strings.xml ./matrix-sdk-android/src/main/res/values-el/strings.xml cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-eo/strings.xml ./matrix-sdk-android/src/main/res/values-eo/strings.xml +cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-en-rUS/strings.xml ./matrix-sdk-android/src/main/res/values-en-rUS/strings.xml cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-es/strings.xml ./matrix-sdk-android/src/main/res/values-es/strings.xml cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-es-rMX/strings.xml ./matrix-sdk-android/src/main/res/values-es-rMX/strings.xml cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-eu/strings.xml ./matrix-sdk-android/src/main/res/values-eu/strings.xml