Create home shortcut for any room (#1525)

This commit is contained in:
Benoit Marty 2020-10-01 18:33:33 +02:00 committed by Benoit Marty
parent 28b039fde3
commit 92e021a3d7
10 changed files with 151 additions and 55 deletions

View file

@ -9,6 +9,7 @@ Improvements 🙌:
- PIN code: request PIN code if phone has been locked
- Small optimisation of scrolling experience in timeline (#2114)
- Allow user to reset cross signing if he has no way to recover (#2052)
- Create home shortcut for any room (#1525)
Bugfix 🐛:
- Improve support for image/audio/video/file selection with intent changes (#1376)

View file

@ -0,0 +1,78 @@
/*
* Copyright (c) 2020 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.app.features.home
import android.content.Context
import android.graphics.Bitmap
import android.os.Build
import androidx.annotation.WorkerThread
import androidx.core.content.pm.ShortcutInfoCompat
import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.graphics.drawable.IconCompat
import im.vector.app.core.glide.GlideApp
import im.vector.app.core.utils.DimensionConverter
import im.vector.app.features.home.room.detail.RoomDetailActivity
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.util.toMatrixItem
import javax.inject.Inject
private val useAdaptiveIcon = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
private const val adaptiveIconSizeDp = 108
private const val adaptiveIconOuterSidesDp = 18
class ShortcutCreator @Inject constructor(
private val context: Context,
private val avatarRenderer: AvatarRenderer,
private val dimensionConverter: DimensionConverter
) {
private val adaptiveIconSize = dimensionConverter.dpToPx(adaptiveIconSizeDp)
private val adaptiveIconOuterSides = dimensionConverter.dpToPx(adaptiveIconOuterSidesDp)
private val iconSize by lazy {
if (useAdaptiveIcon) {
adaptiveIconSize - adaptiveIconOuterSides
} else {
dimensionConverter.dpToPx(72)
}
}
fun canCreateShortcut(): Boolean {
return ShortcutManagerCompat.isRequestPinShortcutSupported(context)
}
@WorkerThread
fun create(roomSummary: RoomSummary): ShortcutInfoCompat {
val intent = RoomDetailActivity.shortcutIntent(context, roomSummary.roomId)
val bitmap = try {
avatarRenderer.shortcutDrawable(GlideApp.with(context), roomSummary.toMatrixItem(), iconSize)
} catch (failure: Throwable) {
null
}
return ShortcutInfoCompat.Builder(context, roomSummary.roomId)
.setShortLabel(roomSummary.displayName)
.setIcon(bitmap?.toProfileImageIcon())
.setIntent(intent)
.build()
}
private fun Bitmap.toProfileImageIcon(): IconCompat {
return if (useAdaptiveIcon) {
IconCompat.createWithAdaptiveBitmap(this)
} else {
IconCompat.createWithBitmap(this)
}
}
}

View file

@ -18,40 +18,19 @@ package im.vector.app.features.home
import android.content.Context
import android.content.pm.ShortcutManager
import android.graphics.Bitmap
import android.os.Build
import androidx.core.content.getSystemService
import androidx.core.content.pm.ShortcutInfoCompat
import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.graphics.drawable.IconCompat
import im.vector.app.core.glide.GlideApp
import im.vector.app.core.utils.DimensionConverter
import im.vector.app.features.home.room.detail.RoomDetailActivity
import org.matrix.android.sdk.api.util.toMatrixItem
import io.reactivex.Observable
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import javax.inject.Inject
private val useAdaptiveIcon = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
private const val adaptiveIconSizeDp = 108
private const val adaptiveIconOuterSidesDp = 18
class ShortcutsHandler @Inject constructor(
private val context: Context,
private val homeRoomListStore: HomeRoomListDataSource,
private val avatarRenderer: AvatarRenderer,
private val dimensionConverter: DimensionConverter
private val shortcutCreator: ShortcutCreator
) {
private val adaptiveIconSize = dimensionConverter.dpToPx(adaptiveIconSizeDp)
private val adaptiveIconOuterSides = dimensionConverter.dpToPx(adaptiveIconOuterSidesDp)
private val iconSize by lazy {
if (useAdaptiveIcon) {
adaptiveIconSize - adaptiveIconOuterSides
} else {
dimensionConverter.dpToPx(72)
}
}
fun observeRoomsAndBuildShortcuts(): Disposable {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) {
@ -67,19 +46,7 @@ class ShortcutsHandler @Inject constructor(
val shortcuts = rooms
.filter { room -> room.isFavorite }
.take(n = 4) // Android only allows us to create 4 shortcuts
.map { room ->
val intent = RoomDetailActivity.shortcutIntent(context, room.roomId)
val bitmap = try {
avatarRenderer.shortcutDrawable(GlideApp.with(context), room.toMatrixItem(), iconSize)
} catch (failure: Throwable) {
null
}
ShortcutInfoCompat.Builder(context, room.roomId)
.setShortLabel(room.displayName)
.setIcon(bitmap?.toProfileImageIcon())
.setIntent(intent)
.build()
}
.map { shortcutCreator.create(it) }
ShortcutManagerCompat.removeAllDynamicShortcuts(context)
ShortcutManagerCompat.addDynamicShortcuts(context, shortcuts)
@ -104,14 +71,4 @@ class ShortcutsHandler @Inject constructor(
}
}
}
// PRIVATE API *********************************************************************************
private fun Bitmap.toProfileImageIcon(): IconCompat {
return if (useAdaptiveIcon) {
IconCompat.createWithAdaptiveBitmap(this)
} else {
IconCompat.createWithBitmap(this)
}
}
}

View file

@ -26,4 +26,5 @@ sealed class RoomProfileAction: VectorViewModelAction {
data class ChangeRoomNotificationState(val notificationState: RoomNotificationState) : RoomProfileAction()
data class ChangeRoomAvatar(val uri: Uri, val fileName: String?) : RoomProfileAction()
object ShareRoomProfile : RoomProfileAction()
object CreateShortcut : RoomProfileAction()
}

View file

@ -24,6 +24,7 @@ import im.vector.app.core.epoxy.profiles.buildProfileSection
import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.ui.list.genericFooterItem
import im.vector.app.features.home.ShortcutCreator
import im.vector.app.features.settings.VectorPreferences
import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
import javax.inject.Inject
@ -31,6 +32,7 @@ import javax.inject.Inject
class RoomProfileController @Inject constructor(
private val stringProvider: StringProvider,
private val vectorPreferences: VectorPreferences,
private val shortcutCreator: ShortcutCreator,
colorProvider: ColorProvider
) : TypedEpoxyController<RoomProfileViewState>() {
@ -44,6 +46,7 @@ class RoomProfileController @Inject constructor(
fun onBannedMemberListClicked()
fun onNotificationsClicked()
fun onUploadsClicked()
fun createShortcut()
fun onSettingsClicked()
fun onLeaveRoomClicked()
fun onRoomIdClicked()
@ -114,6 +117,16 @@ class RoomProfileController @Inject constructor(
icon = R.drawable.ic_room_profile_uploads,
action = { callback?.onUploadsClicked() }
)
if (shortcutCreator.canCreateShortcut()) {
buildProfileAction(
id = "shortcut",
title = stringProvider.getString(R.string.room_settings_add_homescreen_shortcut),
dividerColor = dividerColor,
editable = false,
icon = R.drawable.ic_add_to_home_screen_24dp,
action = { callback?.createShortcut() }
)
}
buildProfileAction(
id = "leave",
title = stringProvider.getString(if (roomSummary.isDirect) {

View file

@ -27,6 +27,7 @@ import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.core.app.ActivityOptionsCompat
import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.net.toUri
import androidx.core.view.ViewCompat
import androidx.core.view.isVisible
@ -115,6 +116,7 @@ class RoomProfileFragment @Inject constructor(
is RoomProfileViewEvents.Failure -> showFailure(it.throwable)
is RoomProfileViewEvents.ShareRoomProfile -> onShareRoomProfile(it.permalink)
RoomProfileViewEvents.OnChangeAvatarSuccess -> dismissLoadingDialog()
is RoomProfileViewEvents.OnShortcutReady -> addShortcut(it)
}.exhaustive
}
roomListQuickActionsSharedActionViewModel
@ -232,6 +234,16 @@ class RoomProfileFragment @Inject constructor(
roomProfileSharedActionViewModel.post(RoomProfileSharedAction.OpenRoomUploads)
}
override fun createShortcut() {
// Ask the view model to prepare it...
roomProfileViewModel.handle(RoomProfileAction.CreateShortcut)
}
private fun addShortcut(onShortcutReady: RoomProfileViewEvents.OnShortcutReady) {
// ... and propose the user to add it
ShortcutManagerCompat.requestPinShortcut(requireContext(), onShortcutReady.shortcutInfo, null)
}
override fun onLeaveRoomClicked() {
AlertDialog.Builder(requireContext())
.setTitle(R.string.room_participants_leave_prompt_title)

View file

@ -16,6 +16,7 @@
package im.vector.app.features.roomprofile
import androidx.core.content.pm.ShortcutInfoCompat
import im.vector.app.core.platform.VectorViewEvents
/**
@ -27,4 +28,5 @@ sealed class RoomProfileViewEvents : VectorViewEvents {
object OnChangeAvatarSuccess : RoomProfileViewEvents()
data class ShareRoomProfile(val permalink: String) : RoomProfileViewEvents()
data class OnShortcutReady(val shortcutInfo: ShortcutInfoCompat) : RoomProfileViewEvents()
}

View file

@ -17,15 +17,20 @@
package im.vector.app.features.roomprofile
import androidx.lifecycle.viewModelScope
import com.airbnb.mvrx.FragmentViewModelContext
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.app.R
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.resources.StringProvider
import im.vector.app.features.home.ShortcutCreator
import im.vector.app.features.powerlevel.PowerLevelsObservableFactory
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.events.model.EventType
@ -36,10 +41,12 @@ import org.matrix.android.sdk.rx.rx
import org.matrix.android.sdk.rx.unwrap
import java.util.UUID
class RoomProfileViewModel @AssistedInject constructor(@Assisted private val initialState: RoomProfileViewState,
private val stringProvider: StringProvider,
private val session: Session)
: VectorViewModel<RoomProfileViewState, RoomProfileAction, RoomProfileViewEvents>(initialState) {
class RoomProfileViewModel @AssistedInject constructor(
@Assisted private val initialState: RoomProfileViewState,
private val stringProvider: StringProvider,
private val shortcutCreator: ShortcutCreator,
private val session: Session
) : VectorViewModel<RoomProfileViewState, RoomProfileAction, RoomProfileViewEvents>(initialState) {
@AssistedInject.Factory
interface Factory {
@ -88,11 +95,24 @@ class RoomProfileViewModel @AssistedInject constructor(@Assisted private val ini
}
}
override fun handle(action: RoomProfileAction) = when (action) {
RoomProfileAction.LeaveRoom -> handleLeaveRoom()
is RoomProfileAction.ChangeRoomNotificationState -> handleChangeNotificationMode(action)
is RoomProfileAction.ShareRoomProfile -> handleShareRoomProfile()
is RoomProfileAction.ChangeRoomAvatar -> handleChangeAvatar(action)
override fun handle(action: RoomProfileAction) {
when (action) {
RoomProfileAction.LeaveRoom -> handleLeaveRoom()
is RoomProfileAction.ChangeRoomNotificationState -> handleChangeNotificationMode(action)
is RoomProfileAction.ShareRoomProfile -> handleShareRoomProfile()
is RoomProfileAction.ChangeRoomAvatar -> handleChangeAvatar(action)
RoomProfileAction.CreateShortcut -> handleCreateShortcut()
}.exhaustive
}
private fun handleCreateShortcut() {
viewModelScope.launch(Dispatchers.IO) {
withState { state ->
state.roomSummary()
?.let { shortcutCreator.create(it) }
?.let { _viewEvents.post(RoomProfileViewEvents.OnShortcutReady(it)) }
}
}
}
private fun handleChangeNotificationMode(action: RoomProfileAction.ChangeRoomNotificationState) {

View file

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M18,1.01L8,1C6.9,1 6,1.9 6,3V6C6,6.55 6.45,7 7,7C7.55,7 8,6.55 8,6V4.5H18V19.5H8V18C8,17.45 7.55,17 7,17C6.45,17 6,17.45 6,18V21C6,22.1 6.9,23 8,23H18C19.1,23 20,22.1 20,21V3C20,1.9 19.1,1.01 18,1.01Z"
android:fillColor="#000000"/>
<path
android:pathData="M11,14.45C11,15 10.55,15.45 10,15.45C9.45,15.45 9,15 9,14.45V11.86L4.11,16.75C3.72,17.14 3.09,17.14 2.7,16.75C2.31,16.36 2.31,15.73 2.7,15.34L7.59,10.45H5C4.45,10.45 4,10 4,9.45C4,8.9 4.45,8.45 5,8.45H10C10.55,8.45 11,8.9 11,9.45V14.45Z"
android:fillColor="#000000"/>
</vector>

View file

@ -664,7 +664,7 @@
<string name="room_settings_direct_chat">Direct Chat</string>
<string name="room_settings_leave_conversation">Leave Conversation</string>
<string name="room_settings_forget">Forget</string>
<string name="room_settings_add_homescreen_shortcut">Add Homescreen Shortcut</string>
<string name="room_settings_add_homescreen_shortcut">Add to Home screen</string>
<!-- home sliding menu -->
<string name="room_sliding_menu_messages">Messages</string>