Merge pull request #6655 from vector-im/feature/eric/app-layout-toolbar

New App Layout Toolbar
This commit is contained in:
Eric Decanini 2022-08-02 17:44:53 +02:00 committed by GitHub
commit e2ed4b4ae1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 707 additions and 20 deletions

1
changelog.d/6655.feature Normal file
View file

@ -0,0 +1 @@
Adds new app layout toolbar (feature flagged)

View file

@ -0,0 +1,4 @@
<vector android:height="22dp" android:viewportHeight="22"
android:viewportWidth="22" android:width="22dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#737D8C" android:fillType="evenOdd" android:pathData="M16.999,14.899C18.07,13.407 18.7,11.577 18.7,9.6C18.7,4.574 14.626,0.5 9.6,0.5C4.574,0.5 0.5,4.574 0.5,9.6C0.5,14.626 4.574,18.7 9.6,18.7C11.577,18.7 13.406,18.07 14.899,16.999C14.941,17.055 14.988,17.109 15.039,17.161L18.939,21.061C19.525,21.646 20.475,21.646 21.06,21.061C21.646,20.475 21.646,19.525 21.06,18.939L17.16,15.039C17.109,14.988 17.055,14.941 16.999,14.899ZM15.7,9.6C15.7,12.969 12.969,15.7 9.6,15.7C6.231,15.7 3.5,12.969 3.5,9.6C3.5,6.231 6.231,3.5 9.6,3.5C12.969,3.5 15.7,6.231 15.7,9.6Z"/>
</vector>

View file

@ -71,4 +71,7 @@
<dimen name="location_sharing_compass_button_margin_horizontal">8dp</dimen>
<dimen name="location_sharing_live_duration_choice_margin_horizontal">12dp</dimen>
<dimen name="location_sharing_live_duration_choice_margin_vertical">22dp</dimen>
<!-- Material 3 -->
<dimen name="collapsing_toolbar_layout_medium_size">112dp</dimen>
</resources>

View file

@ -39,4 +39,14 @@
<item name="android:textSize">12sp</item>
</style>
</resources>
<!-- Material 3 -->
<style name="Widget.Vector.Material3.Toolbar" parent="Widget.Material3.Toolbar" />
<style name="Widget.Vector.Material3.CollapsingToolbar.Medium" parent="Widget.Material3.CollapsingToolbar.Medium">
<item name="expandedTitleTextAppearance">@style/TextAppearance.Vector.Title.Medium</item>
<item name="expandedTitleMarginBottom">20dp</item>
<item name="collapsedTitleTextAppearance">@style/TextAppearance.Vector.Headline.Bold</item>
</style>
</resources>

View file

@ -32,6 +32,15 @@
<item name="android:textColor">?vctr_content_primary</item>
</style>
<style name="TextAppearance.Vector.Headline.Bold" parent="TextAppearance.MaterialComponents.Headline1">
<item name="fontFamily">sans-serif</item>
<item name="android:fontFamily">sans-serif</item>
<item name="android:textStyle">bold</item>
<item name="android:textSize">@dimen/text_size_headline</item>
<item name="android:letterSpacing">0</item>
<item name="android:textColor">?vctr_content_primary</item>
</style>
<style name="TextAppearance.Vector.Subtitle" parent="TextAppearance.MaterialComponents.Subtitle1">
<item name="fontFamily">sans-serif</item>
<item name="android:fontFamily">sans-serif</item>

View file

@ -25,4 +25,4 @@
<item name="android:backgroundDimEnabled">false</item>
</style>
</resources>
</resources>

View file

@ -149,6 +149,9 @@
<!-- Location sharing -->
<item name="vctr_live_location">@color/vctr_live_location_dark</item>
<!-- Material 3 -->
<item name="collapsingToolbarLayoutMediumSize">@dimen/collapsing_toolbar_layout_medium_size</item>
</style>
<style name="Theme.Vector.Dark" parent="Base.Theme.Vector.Dark" />

View file

@ -150,8 +150,12 @@
<!-- Location sharing -->
<item name="vctr_live_location">@color/vctr_live_location_light</item>
<!-- Material 3 -->
<item name="collapsingToolbarLayoutMediumSize">@dimen/collapsing_toolbar_layout_medium_size</item>
</style>
<style name="Theme.Vector.Light" parent="Base.Theme.Vector.Light" />
</resources>

View file

@ -57,6 +57,7 @@ import im.vector.app.features.discovery.change.SetIdentityServerFragment
import im.vector.app.features.home.HomeDetailFragment
import im.vector.app.features.home.HomeDrawerFragment
import im.vector.app.features.home.LoadingFragment
import im.vector.app.features.home.NewHomeDetailFragment
import im.vector.app.features.home.room.breadcrumbs.BreadcrumbsFragment
import im.vector.app.features.home.room.detail.TimelineFragment
import im.vector.app.features.home.room.detail.search.SearchFragment
@ -258,6 +259,11 @@ interface FragmentModule {
@FragmentKey(HomeDetailFragment::class)
fun bindHomeDetailFragment(fragment: HomeDetailFragment): Fragment
@Binds
@IntoMap
@FragmentKey(NewHomeDetailFragment::class)
fun bindNewHomeDetailFragment(fragment: NewHomeDetailFragment): Fragment
@Binds
@IntoMap
@FragmentKey(EmojiSearchResultFragment::class)

View file

@ -28,7 +28,7 @@ import im.vector.app.R
import im.vector.app.core.platform.SimpleTextWatcher
fun EditText.setupAsSearch(
@DrawableRes searchIconRes: Int = R.drawable.ic_search,
@DrawableRes searchIconRes: Int = R.drawable.ic_home_search,
@DrawableRes clearIconRes: Int = R.drawable.ic_x_gray
) {
addTextChangedListener(object : SimpleTextWatcher() {

View file

@ -50,6 +50,7 @@ import androidx.viewbinding.ViewBinding
import com.airbnb.mvrx.MavericksView
import com.bumptech.glide.util.Util
import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.EntryPointAccessors
@ -72,6 +73,7 @@ import im.vector.app.core.utils.ToolbarConfig
import im.vector.app.core.utils.toast
import im.vector.app.features.MainActivity
import im.vector.app.features.MainActivityArgs
import im.vector.app.features.VectorFeatures
import im.vector.app.features.analytics.AnalyticsTracker
import im.vector.app.features.analytics.plan.MobileScreen
import im.vector.app.features.configuration.VectorConfiguration
@ -161,6 +163,9 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
@Inject
lateinit var fontScalePreferences: FontScalePreferences
@Inject
lateinit var vectorFeatures: VectorFeatures
lateinit var navigator: Navigator
private set
private lateinit var fragmentFactory: FragmentFactory
@ -253,6 +258,14 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
initUiAndData()
if (vectorFeatures.isNewAppLayoutEnabled()) {
tryOrNull { // Add to XML theme when feature flag is removed
val toolbarBackground = MaterialColors.getColor(views.root, R.attr.vctr_toolbar_background)
window.statusBarColor = toolbarBackground
window.navigationBarColor = toolbarBackground
}
}
val titleRes = getTitleRes()
if (titleRes != -1) {
supportActionBar?.let {

View file

@ -159,7 +159,7 @@ fun startInstallFromSourceIntent(context: Context, activityResultLauncher: Activ
}
fun startSharePlainTextIntent(
fragment: Fragment,
context: Context,
activityResultLauncher: ActivityResultLauncher<Intent>?,
chooserTitle: String?,
text: String,
@ -182,10 +182,10 @@ fun startSharePlainTextIntent(
if (activityResultLauncher != null) {
activityResultLauncher.launch(intent)
} else {
fragment.startActivity(intent)
context.startActivity(intent)
}
} catch (activityNotFoundException: ActivityNotFoundException) {
fragment.activity?.toast(R.string.error_no_external_application_found)
context.toast(R.string.error_no_external_application_found)
}
}

View file

@ -142,7 +142,7 @@ class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment<Fr
dialog.findViewById<View>(R.id.keys_backup_setup_share)?.debouncedClicks {
startSharePlainTextIntent(
fragment = this,
context = requireContext(),
activityResultLauncher = null,
chooserTitle = context?.getString(R.string.keys_backup_setup_step3_share_intent_chooser_title),
text = recoveryKey,

View file

@ -104,7 +104,7 @@ class BootstrapSaveRecoveryKeyFragment @Inject constructor(
?: return@withState
startSharePlainTextIntent(
this,
requireContext(),
copyStartForActivityResult,
context?.getString(R.string.keys_backup_setup_step3_share_intent_chooser_title),
recoveryKey,

View file

@ -46,6 +46,7 @@ import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.platform.VectorMenuProvider
import im.vector.app.core.pushers.PushersManager
import im.vector.app.core.pushers.UnifiedPushHelper
import im.vector.app.core.utils.startSharePlainTextIntent
import im.vector.app.databinding.ActivityHomeBinding
import im.vector.app.features.MainActivity
import im.vector.app.features.MainActivityArgs
@ -203,11 +204,16 @@ class HomeActivity :
)
}
}
sharedActionViewModel = viewModelProvider.get(HomeSharedActionViewModel::class.java)
sharedActionViewModel = viewModelProvider[HomeSharedActionViewModel::class.java]
views.drawerLayout.addDrawerListener(drawerListener)
if (isFirstCreation()) {
replaceFragment(views.homeDetailFragmentContainer, HomeDetailFragment::class.java)
replaceFragment(views.homeDrawerFragmentContainer, HomeDrawerFragment::class.java)
if (vectorFeatures.isNewAppLayoutEnabled()) {
views.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
replaceFragment(views.homeDetailFragmentContainer, NewHomeDetailFragment::class.java)
} else {
replaceFragment(views.homeDetailFragmentContainer, HomeDetailFragment::class.java)
replaceFragment(views.homeDrawerFragmentContainer, HomeDrawerFragment::class.java)
}
}
sharedActionViewModel
@ -552,7 +558,7 @@ class HomeActivity :
nightlyProxy.onHomeResumed()
}
override fun getMenuRes() = R.menu.home
override fun getMenuRes() = if (vectorFeatures.isNewAppLayoutEnabled()) R.menu.menu_new_home else R.menu.menu_home
override fun handlePrepareMenu(menu: Menu) {
menu.findItem(R.id.menu_home_init_sync_legacy).isVisible = vectorPreferences.developerMode()
@ -591,10 +597,29 @@ class HomeActivity :
navigator.openSettings(this)
true
}
R.id.menu_home_invite_friends -> {
launchInviteFriends()
true
}
else -> false
}
}
private fun launchInviteFriends() {
activeSessionHolder.getSafeActiveSession()?.permalinkService()?.createPermalink(sharedActionViewModel.session.myUserId)?.let { permalink ->
analyticsTracker.screen(MobileScreen(screenName = MobileScreen.ScreenName.InviteFriends))
val text = getString(R.string.invite_friends_text, permalink)
startSharePlainTextIntent(
context = this,
activityResultLauncher = null,
chooserTitle = getString(R.string.invite_friends),
text = text,
extraTitle = getString(R.string.invite_friends_rich_title)
)
}
}
override fun onBackPressed() {
if (views.drawerLayout.isDrawerOpen(GravityCompat.START)) {
views.drawerLayout.closeDrawer(GravityCompat.START)

View file

@ -102,7 +102,7 @@ class HomeDrawerFragment @Inject constructor(
val text = getString(R.string.invite_friends_text, permalink)
startSharePlainTextIntent(
fragment = this,
context = requireContext(),
activityResultLauncher = null,
chooserTitle = getString(R.string.invite_friends),
text = text,

View file

@ -0,0 +1,462 @@
/*
* 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.app.features.home
import android.os.Bundle
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
import com.google.android.material.badge.BadgeDrawable
import im.vector.app.AppStateHandler
import im.vector.app.R
import im.vector.app.core.extensions.commitTransaction
import im.vector.app.core.extensions.toMvRxBundle
import im.vector.app.core.platform.OnBackPressed
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.platform.VectorMenuProvider
import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.ui.views.CurrentCallsView
import im.vector.app.core.ui.views.CurrentCallsViewPresenter
import im.vector.app.core.ui.views.KeysBackupBanner
import im.vector.app.databinding.FragmentNewHomeDetailBinding
import im.vector.app.features.call.SharedKnownCallsViewModel
import im.vector.app.features.call.VectorCallActivity
import im.vector.app.features.call.dialpad.DialPadFragment
import im.vector.app.features.call.webrtc.WebRtcCallManager
import im.vector.app.features.home.room.list.RoomListFragment
import im.vector.app.features.home.room.list.RoomListParams
import im.vector.app.features.popup.PopupAlertManager
import im.vector.app.features.popup.VerificationVectorAlert
import im.vector.app.features.settings.VectorLocale
import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.settings.VectorSettingsActivity.Companion.EXTRA_DIRECT_ACCESS_SECURITY_PRIVACY_MANAGE_SESSIONS
import im.vector.app.features.themes.ThemeUtils
import im.vector.app.features.workers.signout.BannerState
import im.vector.app.features.workers.signout.ServerBackupStatusViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.util.toMatrixItem
import javax.inject.Inject
class NewHomeDetailFragment @Inject constructor(
private val avatarRenderer: AvatarRenderer,
private val colorProvider: ColorProvider,
private val alertManager: PopupAlertManager,
private val callManager: WebRtcCallManager,
private val vectorPreferences: VectorPreferences,
private val appStateHandler: AppStateHandler,
private val session: Session,
) : VectorBaseFragment<FragmentNewHomeDetailBinding>(),
KeysBackupBanner.Delegate,
CurrentCallsView.Callback,
OnBackPressed,
VectorMenuProvider {
private val viewModel: HomeDetailViewModel by fragmentViewModel()
private val unknownDeviceDetectorSharedViewModel: UnknownDeviceDetectorSharedViewModel by activityViewModel()
private val unreadMessagesSharedViewModel: UnreadMessagesSharedViewModel by activityViewModel()
private val serverBackupStatusViewModel: ServerBackupStatusViewModel by activityViewModel()
private lateinit var sharedActionViewModel: HomeSharedActionViewModel
private lateinit var sharedCallActionViewModel: SharedKnownCallsViewModel
private var hasUnreadRooms = false
set(value) {
if (value != field) {
field = value
invalidateOptionsMenu()
}
}
override fun getMenuRes() = R.menu.room_list
override fun handleMenuItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.menu_home_mark_all_as_read -> {
viewModel.handle(HomeDetailAction.MarkAllRoomsRead)
true
}
else -> false
}
}
override fun handlePrepareMenu(menu: Menu) {
withState(viewModel) { state ->
val isRoomList = state.currentTab is HomeTab.RoomList
menu.findItem(R.id.menu_home_mark_all_as_read).isVisible = isRoomList && hasUnreadRooms
}
}
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentNewHomeDetailBinding {
return FragmentNewHomeDetailBinding.inflate(inflater, container, false)
}
private val currentCallsViewPresenter = CurrentCallsViewPresenter()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
sharedActionViewModel = activityViewModelProvider.get(HomeSharedActionViewModel::class.java)
sharedCallActionViewModel = activityViewModelProvider.get(SharedKnownCallsViewModel::class.java)
setupBottomNavigationView()
setupToolbar()
setupKeysBackupBanner()
setupActiveCallView()
withState(viewModel) {
// Update the navigation view if needed (for when we restore the tabs)
views.bottomNavigationView.selectedItemId = it.currentTab.toMenuId()
}
viewModel.onEach(HomeDetailViewState::selectedSpace) { selectedSpace ->
onSpaceChange(selectedSpace)
}
viewModel.onEach(HomeDetailViewState::currentTab) { currentTab ->
updateUIForTab(currentTab)
}
viewModel.onEach(HomeDetailViewState::showDialPadTab) { showDialPadTab ->
updateTabVisibilitySafely(R.id.bottom_action_dial_pad, showDialPadTab)
}
viewModel.observeViewEvents { viewEvent ->
when (viewEvent) {
HomeDetailViewEvents.CallStarted -> handleCallStarted()
is HomeDetailViewEvents.FailToCall -> showFailure(viewEvent.failure)
HomeDetailViewEvents.Loading -> showLoadingDialog()
}
}
unknownDeviceDetectorSharedViewModel.onEach { state ->
state.unknownSessions.invoke()?.let { unknownDevices ->
if (unknownDevices.firstOrNull()?.currentSessionTrust == true) {
val uid = "review_login"
alertManager.cancelAlert(uid)
val olderUnverified = unknownDevices.filter { !it.isNew }
val newest = unknownDevices.firstOrNull { it.isNew }?.deviceInfo
if (newest != null) {
promptForNewUnknownDevices(uid, state, newest)
} else if (olderUnverified.isNotEmpty()) {
// In this case we prompt to go to settings to review logins
promptToReviewChanges(uid, state, olderUnverified.map { it.deviceInfo })
}
}
}
}
sharedCallActionViewModel
.liveKnownCalls
.observe(viewLifecycleOwner) {
currentCallsViewPresenter.updateCall(callManager.getCurrentCall(), callManager.getCalls())
invalidateOptionsMenu()
}
}
private fun navigateBack() {
val previousSpaceId = appStateHandler.getSpaceBackstack().removeLastOrNull()
val parentSpaceId = appStateHandler.getCurrentSpace()?.flattenParentIds?.lastOrNull()
setCurrentSpace(previousSpaceId ?: parentSpaceId)
}
private fun setCurrentSpace(spaceId: String?) {
appStateHandler.setCurrentSpace(spaceId, isForwardNavigation = false)
sharedActionViewModel.post(HomeActivitySharedAction.OnCloseSpace)
}
private fun handleCallStarted() {
dismissLoadingDialog()
val fragmentTag = HomeTab.DialPad.toFragmentTag()
(childFragmentManager.findFragmentByTag(fragmentTag) as? DialPadFragment)?.clear()
}
override fun onDestroyView() {
currentCallsViewPresenter.unBind()
super.onDestroyView()
}
override fun onResume() {
super.onResume()
updateTabVisibilitySafely(R.id.bottom_action_notification, vectorPreferences.labAddNotificationTab())
callManager.checkForProtocolsSupportIfNeeded()
refreshSpaceState()
}
private fun refreshSpaceState() {
appStateHandler.getCurrentSpace()?.let {
onSpaceChange(it)
}
}
private fun promptForNewUnknownDevices(uid: String, state: UnknownDevicesState, newest: DeviceInfo) {
val user = state.myMatrixItem
alertManager.postVectorAlert(
VerificationVectorAlert(
uid = uid,
title = getString(R.string.new_session),
description = getString(R.string.verify_this_session, newest.displayName ?: newest.deviceId ?: ""),
iconId = R.drawable.ic_shield_warning
).apply {
viewBinder = VerificationVectorAlert.ViewBinder(user, avatarRenderer)
colorInt = colorProvider.getColorFromAttribute(R.attr.colorPrimary)
contentAction = Runnable {
(weakCurrentActivity?.get() as? VectorBaseActivity<*>)
?.navigator
?.requestSessionVerification(requireContext(), newest.deviceId ?: "")
unknownDeviceDetectorSharedViewModel.handle(
UnknownDeviceDetectorSharedViewModel.Action.IgnoreDevice(newest.deviceId?.let { listOf(it) }.orEmpty())
)
}
dismissedAction = Runnable {
unknownDeviceDetectorSharedViewModel.handle(
UnknownDeviceDetectorSharedViewModel.Action.IgnoreDevice(newest.deviceId?.let { listOf(it) }.orEmpty())
)
}
}
)
}
private fun promptToReviewChanges(uid: String, state: UnknownDevicesState, oldUnverified: List<DeviceInfo>) {
val user = state.myMatrixItem
alertManager.postVectorAlert(
VerificationVectorAlert(
uid = uid,
title = getString(R.string.review_logins),
description = getString(R.string.verify_other_sessions),
iconId = R.drawable.ic_shield_warning
).apply {
viewBinder = VerificationVectorAlert.ViewBinder(user, avatarRenderer)
colorInt = colorProvider.getColorFromAttribute(R.attr.colorPrimary)
contentAction = Runnable {
(weakCurrentActivity?.get() as? VectorBaseActivity<*>)?.let { activity ->
// mark as ignored to avoid showing it again
unknownDeviceDetectorSharedViewModel.handle(
UnknownDeviceDetectorSharedViewModel.Action.IgnoreDevice(oldUnverified.mapNotNull { it.deviceId })
)
activity.navigator.openSettings(activity, EXTRA_DIRECT_ACCESS_SECURITY_PRIVACY_MANAGE_SESSIONS)
}
}
dismissedAction = Runnable {
unknownDeviceDetectorSharedViewModel.handle(
UnknownDeviceDetectorSharedViewModel.Action.IgnoreDevice(oldUnverified.mapNotNull { it.deviceId })
)
}
}
)
}
private fun onSpaceChange(spaceSummary: RoomSummary?) {
// Reimplement in next PR
println(spaceSummary)
}
private fun setupKeysBackupBanner() {
serverBackupStatusViewModel
.onEach {
when (val banState = it.bannerState.invoke()) {
is BannerState.Setup -> views.homeKeysBackupBanner.render(KeysBackupBanner.State.Setup(banState.numberOfKeys), false)
BannerState.BackingUp -> views.homeKeysBackupBanner.render(KeysBackupBanner.State.BackingUp, false)
null,
BannerState.Hidden -> views.homeKeysBackupBanner.render(KeysBackupBanner.State.Hidden, false)
}
}
views.homeKeysBackupBanner.delegate = this
}
private fun setupActiveCallView() {
currentCallsViewPresenter.bind(views.currentCallsView, this)
}
private fun setupToolbar() {
setupToolbar(views.toolbar)
lifecycleScope.launch(Dispatchers.IO) {
session.userService().getUser(session.myUserId)?.let { user ->
avatarRenderer.render(user.toMatrixItem(), views.avatar)
}
}
views.avatar.debouncedClicks {
navigator.openSettings(requireContext())
}
}
private fun setupBottomNavigationView() {
views.bottomNavigationView.menu.findItem(R.id.bottom_action_notification).isVisible = vectorPreferences.labAddNotificationTab()
views.bottomNavigationView.setOnItemSelectedListener {
val tab = when (it.itemId) {
R.id.bottom_action_people -> HomeTab.RoomList(RoomListDisplayMode.PEOPLE)
R.id.bottom_action_rooms -> HomeTab.RoomList(RoomListDisplayMode.ROOMS)
R.id.bottom_action_notification -> HomeTab.RoomList(RoomListDisplayMode.NOTIFICATIONS)
else -> HomeTab.DialPad
}
viewModel.handle(HomeDetailAction.SwitchTab(tab))
true
}
}
private fun updateUIForTab(tab: HomeTab) {
views.bottomNavigationView.menu.findItem(tab.toMenuId()).isChecked = true
updateSelectedFragment(tab)
invalidateOptionsMenu()
}
private fun HomeTab.toFragmentTag() = "FRAGMENT_TAG_$this"
private fun updateSelectedFragment(tab: HomeTab) {
val fragmentTag = tab.toFragmentTag()
val fragmentToShow = childFragmentManager.findFragmentByTag(fragmentTag)
childFragmentManager.commitTransaction {
childFragmentManager.fragments
.filter { it != fragmentToShow }
.forEach {
detach(it)
}
if (fragmentToShow == null) {
when (tab) {
is HomeTab.RoomList -> {
val params = RoomListParams(tab.displayMode)
add(R.id.roomListContainer, RoomListFragment::class.java, params.toMvRxBundle(), fragmentTag)
}
is HomeTab.DialPad -> {
add(R.id.roomListContainer, createDialPadFragment(), fragmentTag)
}
}
} else {
if (tab is HomeTab.DialPad) {
(fragmentToShow as? DialPadFragment)?.applyCallback()
}
attach(fragmentToShow)
}
}
}
private fun createDialPadFragment(): Fragment {
val fragment = childFragmentManager.fragmentFactory.instantiate(vectorBaseActivity.classLoader, DialPadFragment::class.java.name)
return (fragment as DialPadFragment).apply {
arguments = Bundle().apply {
putBoolean(DialPadFragment.EXTRA_ENABLE_DELETE, true)
putBoolean(DialPadFragment.EXTRA_ENABLE_OK, true)
putString(DialPadFragment.EXTRA_REGION_CODE, VectorLocale.applicationLocale.country)
}
applyCallback()
}
}
private fun updateTabVisibilitySafely(tabId: Int, isVisible: Boolean) {
val wasVisible = views.bottomNavigationView.menu.findItem(tabId).isVisible
views.bottomNavigationView.menu.findItem(tabId).isVisible = isVisible
if (wasVisible && !isVisible) {
// As we hide it check if it's not the current item!
withState(viewModel) {
if (it.currentTab.toMenuId() == tabId) {
viewModel.handle(HomeDetailAction.SwitchTab(HomeTab.RoomList(RoomListDisplayMode.PEOPLE)))
}
}
}
}
/* ==========================================================================================
* KeysBackupBanner Listener
* ========================================================================================== */
override fun setupKeysBackup() {
navigator.openKeysBackupSetup(requireActivity(), false)
}
override fun recoverKeysBackup() {
navigator.openKeysBackupManager(requireActivity())
}
override fun invalidate() = withState(viewModel) {
views.bottomNavigationView.getOrCreateBadge(R.id.bottom_action_people).render(it.notificationCountPeople, it.notificationHighlightPeople)
views.bottomNavigationView.getOrCreateBadge(R.id.bottom_action_rooms).render(it.notificationCountRooms, it.notificationHighlightRooms)
views.bottomNavigationView.getOrCreateBadge(R.id.bottom_action_notification).render(it.notificationCountCatchup, it.notificationHighlightCatchup)
views.syncStateView.render(
it.syncState,
it.incrementalSyncRequestState,
it.pushCounter,
vectorPreferences.developerShowDebugInfo()
)
hasUnreadRooms = it.hasUnreadMessages
}
private fun BadgeDrawable.render(count: Int, highlight: Boolean) {
isVisible = count > 0
number = count
maxCharacterCount = 3
badgeTextColor = ThemeUtils.getColor(requireContext(), R.attr.colorOnPrimary)
backgroundColor = if (highlight) {
ThemeUtils.getColor(requireContext(), R.attr.colorError)
} else {
ThemeUtils.getColor(requireContext(), R.attr.vctr_unread_background)
}
}
private fun HomeTab.toMenuId() = when (this) {
is HomeTab.DialPad -> R.id.bottom_action_dial_pad
is HomeTab.RoomList -> when (displayMode) {
RoomListDisplayMode.PEOPLE -> R.id.bottom_action_people
RoomListDisplayMode.ROOMS -> R.id.bottom_action_rooms
else -> R.id.bottom_action_notification
}
}
override fun onTapToReturnToCall() {
callManager.getCurrentCall()?.let { call ->
VectorCallActivity.newIntent(
context = requireContext(),
callId = call.callId,
signalingRoomId = call.signalingRoomId,
otherUserId = call.mxCall.opponentUserId,
isIncomingCall = !call.mxCall.isOutgoing,
isVideoCall = call.mxCall.isVideoCall,
mode = null
).let {
startActivity(it)
}
}
}
private fun DialPadFragment.applyCallback(): DialPadFragment {
callback = object : DialPadFragment.Callback {
override fun onOkClicked(formatted: String?, raw: String?) {
if (raw.isNullOrEmpty()) return
viewModel.handle(HomeDetailAction.StartCallWithPhoneNumber(raw))
}
}
return this
}
override fun onBackPressed(toolbarButton: Boolean) = if (appStateHandler.getCurrentSpace() != null) {
navigateBack()
true
} else {
false
}
}

View file

@ -334,7 +334,7 @@ class RoomMemberProfileFragment @Inject constructor(
.setNeutralButton(R.string.ok, null)
.setPositiveButton(R.string.share_by_text) { _, _ ->
startSharePlainTextIntent(
fragment = this,
context = requireContext(),
activityResultLauncher = null,
chooserTitle = null,
text = permalink

View file

@ -337,7 +337,7 @@ class RoomProfileFragment @Inject constructor(
private fun onShareRoomProfile(permalink: String) {
startSharePlainTextIntent(
fragment = this,
context = requireContext(),
activityResultLauncher = null,
chooserTitle = null,
text = permalink

View file

@ -89,7 +89,7 @@ class ShareSpaceBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetSpa
}
is ShareSpaceViewEvents.ShowInviteByLink -> {
startSharePlainTextIntent(
fragment = this,
context = requireContext(),
activityResultLauncher = null,
chooserTitle = getString(R.string.share_by_text),
text = getString(R.string.share_space_link_message, event.spaceName, event.permalink),

View file

@ -67,7 +67,7 @@ class ShowUserCodeFragment @Inject constructor(
sharedViewModel.observeViewEvents {
if (it is UserCodeShareViewEvents.SharePlainText) {
startSharePlainTextIntent(
fragment = this,
context = requireContext(),
activityResultLauncher = null,
chooserTitle = it.title,
text = it.text,

View file

@ -96,7 +96,7 @@ class UserListFragment @Inject constructor(
is UserListViewEvents.OpenShareMatrixToLink -> {
val text = getString(R.string.invite_friends_text, it.link)
startSharePlainTextIntent(
fragment = this,
context = requireContext(),
activityResultLauncher = null,
chooserTitle = getString(R.string.invite_friends),
text = text,

View file

@ -17,4 +17,4 @@
android:left="14dp"
android:right="14dp"
android:top="14dp" />
</layer-list>
</layer-list>

View file

@ -0,0 +1,103 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<im.vector.app.core.ui.views.KeysBackupBanner
android:id="@+id/homeKeysBackupBanner"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="?vctr_keys_backup_banner_accent_color"
android:minHeight="67dp"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="gone" />
<im.vector.app.core.ui.views.CurrentCallsView
android:id="@+id/currentCallsView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/homeKeysBackupBanner"
tools:visibility="gone" />
<im.vector.app.features.sync.widget.SyncStateView
android:id="@+id/syncStateView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/currentCallsView"
tools:visibility="gone" />
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/currentCallsView">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
app:layout_constraintTop_toBottomOf="@id/syncStateView">
<com.google.android.material.appbar.CollapsingToolbarLayout
style="@style/Widget.Vector.Material3.CollapsingToolbar.Medium"
android:layout_width="match_parent"
android:layout_height="?attr/collapsingToolbarLayoutMediumSize"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
style="@style/Widget.Vector.Material3.Toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:elevation="0dp"
app:layout_collapseMode="pin"
app:title="@string/all_chats">
<ImageView
android:id="@+id/avatar"
android:layout_width="36dp"
android:layout_height="36dp"
android:padding="6dp"
android:contentDescription="@string/a11y_open_settings"
tools:src="@sample/user_round_avatars" />
</com.google.android.material.appbar.MaterialToolbar>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/roomListContainer"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@id/bottomNavigationView"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottomNavigationView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:menu="@menu/home_bottom_navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -3,7 +3,7 @@
xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/search"
android:icon="@drawable/ic_search"
android:icon="@drawable/ic_home_search"
app:iconTint="?vctr_content_primary"
android:title="@string/search"
app:actionViewClass="android.widget.SearchView"

View file

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<item
android:id="@+id/menu_home_invite_friends"
android:title="@string/invite_friends"
app:showAsAction="never" />
<item
android:id="@+id/menu_home_suggestion"
android:icon="@drawable/ic_material_bug_report"
android:title="@string/send_suggestion"
app:showAsAction="never" />
<item
android:id="@+id/menu_home_report_bug"
android:icon="@drawable/ic_material_bug_report"
android:title="@string/send_bug_report"
app:showAsAction="never" />
<item
android:id="@+id/menu_home_init_sync_legacy"
android:title="Do a legacy init sync"
tools:ignore="HardcodedText"
app:showAsAction="never" />
<item
android:id="@+id/menu_home_init_sync_optimized"
android:title="Do an optimized init sync"
tools:ignore="HardcodedText"
app:showAsAction="never" />
<item
android:id="@+id/menu_home_filter"
android:icon="@drawable/ic_home_search"
android:title="@string/home_filter_placeholder_home"
app:iconTint="?vctr_content_secondary"
app:showAsAction="always" />
</menu>

View file

@ -136,6 +136,7 @@
<string name="matrix_error">Matrix error</string>
<!-- Home Screen -->
<string name="all_chats">All Chats</string>
<!-- Last seen time -->
@ -2808,6 +2809,7 @@
<string name="a11y_screenshot">Screenshot</string>
<string name="a11y_open_widget">Open widgets</string>
<string name="a11y_open_settings">Open settings</string>
<string name="a11y_import_key_from_file">Import key from file</string>
<string name="a11y_image">Image</string>
<string name="a11y_change_avatar">Change avatar</string>