mirror of
https://github.com/element-hq/element-android
synced 2024-11-27 11:59:12 +03:00
Create home shortcut for any room (#1525)
This commit is contained in:
parent
28b039fde3
commit
92e021a3d7
10 changed files with 151 additions and 55 deletions
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
12
vector/src/main/res/drawable/ic_add_to_home_screen_24dp.xml
Normal file
12
vector/src/main/res/drawable/ic_add_to_home_screen_24dp.xml
Normal 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>
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue