diff --git a/.github/workflows/triage-labelled.yml b/.github/workflows/triage-labelled.yml
index f478d2bd7b..174e3c54c0 100644
--- a/.github/workflows/triage-labelled.yml
+++ b/.github/workflows/triage-labelled.yml
@@ -142,32 +142,6 @@ jobs:
env:
PROJECT_ID: "PN_kwDOAM0swc2KCw"
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
-
- move_threads_issues:
- name: A-Threads to Thread board
- runs-on: ubuntu-latest
- # Skip in forks
- if: >
- github.repository == 'vector-im/element-android' &&
- contains(github.event.issue.labels.*.name, 'A-Threads')
- steps:
- - uses: octokit/graphql-action@v2.x
- with:
- headers: '{"GraphQL-Features": "projects_next_graphql"}'
- query: |
- mutation add_to_project($projectid:ID!,$contentid:ID!) {
- addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) {
- projectNextItem {
- id
- }
- }
- }
- projectid: ${{ env.PROJECT_ID }}
- contentid: ${{ github.event.issue.node_id }}
- env:
- PROJECT_ID: "PN_kwDOAM0swc0rRA"
- GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
-
move_message_bubbles_issues:
name: A-Message-Bubbles to Message bubbles board
runs-on: ubuntu-latest
diff --git a/changelog.d/6646.misc b/changelog.d/6646.misc
new file mode 100644
index 0000000000..0f2e3f0881
--- /dev/null
+++ b/changelog.d/6646.misc
@@ -0,0 +1 @@
+[App Layout] Obsolete settings are not shown when App Layout flag is enabled
diff --git a/changelog.d/6754.bugfix b/changelog.d/6754.bugfix
new file mode 100644
index 0000000000..e9f6960595
--- /dev/null
+++ b/changelog.d/6754.bugfix
@@ -0,0 +1 @@
+[App Layout] - space switcher now has empty state
diff --git a/changelog.d/6835.feature b/changelog.d/6835.feature
new file mode 100644
index 0000000000..e4e610f7e0
--- /dev/null
+++ b/changelog.d/6835.feature
@@ -0,0 +1 @@
+[App Layout] New empty states for home screen
diff --git a/changelog.d/6876.feature b/changelog.d/6876.feature
new file mode 100644
index 0000000000..12a2b78a1e
--- /dev/null
+++ b/changelog.d/6876.feature
@@ -0,0 +1 @@
+[App Layout] - Invites now show empty screen after you reject last invite
diff --git a/changelog.d/6989.bugfix b/changelog.d/6989.bugfix
new file mode 100644
index 0000000000..cf740ca741
--- /dev/null
+++ b/changelog.d/6989.bugfix
@@ -0,0 +1 @@
+Catch race condition crash in voice recording
diff --git a/changelog.d/7010.feature b/changelog.d/7010.feature
new file mode 100644
index 0000000000..d6c7c85b3e
--- /dev/null
+++ b/changelog.d/7010.feature
@@ -0,0 +1 @@
+Try to detect devices that lack Opus encoder support, use bundled libopus library for those.
diff --git a/changelog.d/7016.wip b/changelog.d/7016.wip
new file mode 100644
index 0000000000..6918991a86
--- /dev/null
+++ b/changelog.d/7016.wip
@@ -0,0 +1 @@
+[New Layout] Improves talkback accessibility
diff --git a/docs/nightly_build.md b/docs/nightly_build.md
index 7750e0466a..77cc676c7f 100644
--- a/docs/nightly_build.md
+++ b/docs/nightly_build.md
@@ -47,7 +47,7 @@ git checkout develop
mv towncrier.toml towncrier.toml.bak
sed 's/CHANGES\.md/CHANGES_NIGHTLY\.md/' towncrier.toml.bak > towncrier.toml
rm towncrier.toml.bak
-yes n | towncrier --version nightly
+yes n | towncrier build --version nightly
./gradlew assembleGplayNightly appDistributionUploadGplayNightly $CI_GRADLE_ARG_PROPERTIES
```
diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml
index 8621fa7a4b..fd18ee8992 100644
--- a/library/ui-strings/src/main/res/values/strings.xml
+++ b/library/ui-strings/src/main/res/values/strings.xml
@@ -140,8 +140,10 @@
Start Chat
Create Room
Explore Rooms
- Expand space children
- Collapse space children
+
+ Expand %s children
+
+ Collapse %s children
@@ -442,9 +444,16 @@
"System Alerts"
Suggested Rooms
+
+ No spaces yet.
+ Spaces are a new way to group rooms and people. Create a space to get started.
+
Invites
+ Nothing new.
+ This is where your new requests and invites will be.
+
Conversations
Matrix contacts only
@@ -3252,6 +3261,27 @@
Session
Last activity %1$s
+
+ %s\nis looking a little empty.
+
+ Spaces are a new way to group rooms and people. Add an existing room, or create a new one, using the bottom-right button.
+
+ Welcome to ${app_name},\n%s.
+ The all-in-one secure chat app for teams, friends and organisations. Create a chat, or join an existing room, to get started.
+ Nothing to report.
+ This is where your unread messages will show up, when you have some.
+
+ Welcome to a new view!
+
+ To simplify your ${app_name}, tabs are now optional. Manage them using the top-right menu.
+ Access Spaces
+
+ Access your Spaces (bottom-right) faster and easier than ever before.
+ Give Feedback
+
+ Tap top right to see the option to feedback.
+ Try it out
+
Filter
All session
Verified
diff --git a/library/ui-styles/src/main/res/values-h720dp/dimens.xml b/library/ui-styles/src/main/res/values-h720dp/dimens.xml
index 1a7791720d..2a7b12cf2f 100644
--- a/library/ui-styles/src/main/res/values-h720dp/dimens.xml
+++ b/library/ui-styles/src/main/res/values-h720dp/dimens.xml
@@ -2,4 +2,8 @@
- 0.05
- 0.40
-
\ No newline at end of file
+
+ 16dp
+ 40dp
+ 46dp
+
diff --git a/library/ui-styles/src/main/res/values/dimens.xml b/library/ui-styles/src/main/res/values/dimens.xml
index 53f1044a12..758dd6e978 100644
--- a/library/ui-styles/src/main/res/values/dimens.xml
+++ b/library/ui-styles/src/main/res/values/dimens.xml
@@ -74,4 +74,9 @@
112dp
+
+
+ 8dp
+ 16dp
+ 28dp
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreHelper.kt
index ba1afd4758..48cfbebe5b 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreHelper.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreHelper.kt
@@ -21,6 +21,7 @@ import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStore
import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreModule
import org.matrix.android.sdk.internal.crypto.store.db.mapper.CrossSigningKeysMapper
+import org.matrix.android.sdk.internal.crypto.store.db.mapper.MyDeviceLastSeenInfoEntityMapper
import org.matrix.android.sdk.internal.di.MoshiProvider
import org.matrix.android.sdk.internal.util.time.DefaultClock
import kotlin.random.Random
@@ -37,6 +38,7 @@ internal class CryptoStoreHelper {
userId = "userId_" + Random.nextInt(),
deviceId = "deviceId_sample",
clock = DefaultClock(),
+ myDeviceLastSeenInfoEntityMapper = MyDeviceLastSeenInfoEntityMapper()
)
}
}
diff --git a/vector-app/build.gradle b/vector-app/build.gradle
index 2368586bfb..a8262fde40 100644
--- a/vector-app/build.gradle
+++ b/vector-app/build.gradle
@@ -340,10 +340,6 @@ android {
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
]
}
-
- buildFeatures {
- viewBinding true
- }
}
dependencies {
diff --git a/vector-app/src/gplay/AndroidManifest.xml b/vector-app/src/gplay/AndroidManifest.xml
index bc74b9bb52..a5f0eae6be 100755
--- a/vector-app/src/gplay/AndroidManifest.xml
+++ b/vector-app/src/gplay/AndroidManifest.xml
@@ -1,7 +1,7 @@
-
+
+
diff --git a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt
index bd105436f3..40484f57e8 100644
--- a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt
+++ b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt
@@ -53,6 +53,7 @@ import im.vector.app.features.home.room.detail.upgrade.MigrateRoomViewModel
import im.vector.app.features.home.room.list.RoomListViewModel
import im.vector.app.features.home.room.list.home.HomeRoomListViewModel
import im.vector.app.features.home.room.list.home.invites.InvitesViewModel
+import im.vector.app.features.home.room.list.home.release.ReleaseNotesViewModel
import im.vector.app.features.homeserver.HomeServerCapabilitiesViewModel
import im.vector.app.features.invite.InviteUsersToRoomViewModel
import im.vector.app.features.location.LocationSharingViewModel
@@ -626,6 +627,11 @@ interface MavericksViewModelModule {
@MavericksViewModelKey(InvitesViewModel::class)
fun invitesViewModel(factory: InvitesViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
+ @Binds
+ @IntoMap
+ @MavericksViewModelKey(ReleaseNotesViewModel::class)
+ fun releaseNotesViewModel(factory: ReleaseNotesViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
+
@Binds
@IntoMap
@MavericksViewModelKey(SessionOverviewViewModel::class)
diff --git a/vector/src/main/java/im/vector/app/core/platform/StateView.kt b/vector/src/main/java/im/vector/app/core/platform/StateView.kt
index 6f36787d0c..2fb99c705a 100755
--- a/vector/src/main/java/im/vector/app/core/platform/StateView.kt
+++ b/vector/src/main/java/im/vector/app/core/platform/StateView.kt
@@ -21,6 +21,7 @@ import android.graphics.drawable.Drawable
import android.util.AttributeSet
import android.view.View
import android.widget.FrameLayout
+import android.widget.ImageView
import androidx.core.view.isVisible
import im.vector.app.R
import im.vector.app.core.extensions.updateConstraintSet
@@ -36,7 +37,8 @@ class StateView @JvmOverloads constructor(context: Context, attrs: AttributeSet?
val title: CharSequence? = null,
val image: Drawable? = null,
val isBigImage: Boolean = false,
- val message: CharSequence? = null
+ val message: CharSequence? = null,
+ val imageScaleType: ImageView.ScaleType? = ImageView.ScaleType.FIT_CENTER,
) : State()
data class Error(val message: CharSequence? = null) : State()
@@ -79,6 +81,7 @@ class StateView @JvmOverloads constructor(context: Context, attrs: AttributeSet?
is State.Content -> Unit
is State.Loading -> Unit
is State.Empty -> {
+ views.emptyImageView.scaleType = newState.imageScaleType
views.emptyImageView.setImageDrawable(newState.image)
views.emptyView.updateConstraintSet {
it.constrainPercentHeight(R.id.emptyImageView, if (newState.isBigImage) 0.5f else 0.1f)
diff --git a/vector/src/main/java/im/vector/app/features/analytics/plan/Interaction.kt b/vector/src/main/java/im/vector/app/features/analytics/plan/Interaction.kt
index 6336faa74c..1df1b35439 100644
--- a/vector/src/main/java/im/vector/app/features/analytics/plan/Interaction.kt
+++ b/vector/src/main/java/im/vector/app/features/analytics/plan/Interaction.kt
@@ -40,6 +40,46 @@ data class Interaction(
) : VectorAnalyticsEvent {
enum class Name {
+ /**
+ * User tapped the All filter in the All Chats filter tab.
+ */
+ MobileAllChatsFilterAll,
+
+ /**
+ * User tapped the Favourites filter in the All Chats filter tab.
+ */
+ MobileAllChatsFilterFavourites,
+
+ /**
+ * User tapped the People filter in the All Chats filter tab.
+ */
+ MobileAllChatsFilterPeople,
+
+ /**
+ * User tapped the Unreads filter in the All Chats filter tab.
+ */
+ MobileAllChatsFilterUnreads,
+
+ /**
+ * User disabled filters from the all chats layout settings.
+ */
+ MobileAllChatsFiltersDisabled,
+
+ /**
+ * User enabled filters from the all chats layout settings.
+ */
+ MobileAllChatsFiltersEnabled,
+
+ /**
+ * User disabled recents from the all chats layout settings.
+ */
+ MobileAllChatsRecentsDisabled,
+
+ /**
+ * User enabled recents from the all chats layout settings.
+ */
+ MobileAllChatsRecentsEnabled,
+
/**
* User tapped on Add to Home button on Room Details screen.
*/
@@ -60,6 +100,11 @@ data class Interaction(
*/
MobileRoomThreadSummaryItem,
+ /**
+ * User validated the creation of a new space.
+ */
+ MobileSpaceCreationValidated,
+
/**
* User tapped on the filter button on ThreadList screen.
*/
@@ -81,6 +126,12 @@ data class Interaction(
*/
SpacePanelSwitchSpace,
+ /**
+ * User tapped an unselected sub space from the space list -> space
+ * switching should occur.
+ */
+ SpacePanelSwitchSubSpace,
+
/**
* User clicked the create room button in the add existing room to space
* dialog in Element Web/Desktop.
diff --git a/vector/src/main/java/im/vector/app/features/analytics/plan/MobileScreen.kt b/vector/src/main/java/im/vector/app/features/analytics/plan/MobileScreen.kt
index 3ce3dfb578..7ea41e2d78 100644
--- a/vector/src/main/java/im/vector/app/features/analytics/plan/MobileScreen.kt
+++ b/vector/src/main/java/im/vector/app/features/analytics/plan/MobileScreen.kt
@@ -43,6 +43,11 @@ data class MobileScreen(
*/
CreateRoom,
+ /**
+ * The screen shown to create a new space.
+ */
+ CreateSpace,
+
/**
* The confirmation screen shown before deactivating an account.
*/
@@ -78,6 +83,11 @@ data class MobileScreen(
*/
InviteFriends,
+ /**
+ * Room accessed via space bottom sheet list.
+ */
+ Invites,
+
/**
* The screen that displays the login flow (when the user already has an
* account).
@@ -261,6 +271,11 @@ data class MobileScreen(
*/
Sidebar,
+ /**
+ * Room accessed via space bottom sheet list.
+ */
+ SpaceBottomSheet,
+
/**
* Screen that displays the list of rooms and spaces of a space.
*/
diff --git a/vector/src/main/java/im/vector/app/features/analytics/plan/UserProperties.kt b/vector/src/main/java/im/vector/app/features/analytics/plan/UserProperties.kt
index 77be2456cd..28732c9a42 100644
--- a/vector/src/main/java/im/vector/app/features/analytics/plan/UserProperties.kt
+++ b/vector/src/main/java/im/vector/app/features/analytics/plan/UserProperties.kt
@@ -44,6 +44,10 @@ data class UserProperties(
* Whether the user has the people space enabled.
*/
val webMetaSpacePeopleEnabled: Boolean? = null,
+ /**
+ * The active filter in the All Chats screen.
+ */
+ val allChatsActiveFilter: AllChatsActiveFilter? = null,
/**
* The selected messaging use case during the onboarding flow.
*/
@@ -80,6 +84,29 @@ data class UserProperties(
WorkMessaging,
}
+ enum class AllChatsActiveFilter {
+
+ /**
+ * Filters are activated and All is selected.
+ */
+ All,
+
+ /**
+ * Filters are activated and Favourites is selected.
+ */
+ Favourites,
+
+ /**
+ * Filters are activated and People is selected.
+ */
+ People,
+
+ /**
+ * Filters are activated and Unreads is selected.
+ */
+ Unreads,
+ }
+
fun getProperties(): Map? {
return mutableMapOf().apply {
webMetaSpaceFavouritesEnabled?.let { put("WebMetaSpaceFavouritesEnabled", it) }
@@ -87,6 +114,7 @@ data class UserProperties(
webMetaSpaceHomeEnabled?.let { put("WebMetaSpaceHomeEnabled", it) }
webMetaSpaceOrphansEnabled?.let { put("WebMetaSpaceOrphansEnabled", it) }
webMetaSpacePeopleEnabled?.let { put("WebMetaSpacePeopleEnabled", it) }
+ allChatsActiveFilter?.let { put("allChatsActiveFilter", it.name) }
ftueUseCaseSelection?.let { put("ftueUseCaseSelection", it.name) }
numFavouriteRooms?.let { put("numFavouriteRooms", it) }
numSpaces?.let { put("numSpaces", it) }
diff --git a/vector/src/main/java/im/vector/app/features/analytics/plan/ViewRoom.kt b/vector/src/main/java/im/vector/app/features/analytics/plan/ViewRoom.kt
index f6a724304b..366979025a 100644
--- a/vector/src/main/java/im/vector/app/features/analytics/plan/ViewRoom.kt
+++ b/vector/src/main/java/im/vector/app/features/analytics/plan/ViewRoom.kt
@@ -110,6 +110,11 @@ data class ViewRoom(
*/
MobileSearchContactDetail,
+ /**
+ * Room accessed via space bottom sheet list.
+ */
+ MobileSpaceBottomSheet,
+
/**
* Room accessed via interacting with direct chat item in the space
* contact detail screen.
diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt
index 2a8390c93c..78b4364f38 100644
--- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt
@@ -60,6 +60,7 @@ import im.vector.app.features.disclaimer.showDisclaimerDialog
import im.vector.app.features.home.room.list.actions.RoomListSharedAction
import im.vector.app.features.home.room.list.actions.RoomListSharedActionViewModel
import im.vector.app.features.home.room.list.home.layout.HomeLayoutSettingBottomDialogFragment
+import im.vector.app.features.home.room.list.home.release.ReleaseNotesActivity
import im.vector.app.features.matrixto.MatrixToBottomSheet
import im.vector.app.features.matrixto.OriginOfMatrixTo
import im.vector.app.features.navigation.Navigator
@@ -268,6 +269,7 @@ class HomeActivity :
}
is HomeActivityViewEvents.OnCrossSignedInvalidated -> handleCrossSigningInvalidated(it)
HomeActivityViewEvents.ShowAnalyticsOptIn -> handleShowAnalyticsOptIn()
+ HomeActivityViewEvents.ShowReleaseNotes -> handleShowReleaseNotes()
HomeActivityViewEvents.NotifyUserForThreadsMigration -> handleNotifyUserForThreadsMigration()
is HomeActivityViewEvents.MigrateThreads -> migrateThreadsIfNeeded(it.checkSession)
}
@@ -282,6 +284,10 @@ class HomeActivity :
homeActivityViewModel.handle(HomeActivityViewActions.ViewStarted)
}
+ private fun handleShowReleaseNotes() {
+ startActivity(Intent(this, ReleaseNotesActivity::class.java))
+ }
+
private fun showSpaceSettings(spaceId: String) {
// open bottom sheet
SpaceSettingsMenuBottomSheet
diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt
index 170550d5b4..e0b9e8ceb5 100644
--- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt
+++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt
@@ -31,6 +31,7 @@ sealed interface HomeActivityViewEvents : VectorViewEvents {
data class OnCrossSignedInvalidated(val userItem: MatrixItem.UserItem) : HomeActivityViewEvents
object PromptToEnableSessionPush : HomeActivityViewEvents
object ShowAnalyticsOptIn : HomeActivityViewEvents
+ object ShowReleaseNotes : HomeActivityViewEvents
object NotifyUserForThreadsMigration : HomeActivityViewEvents
data class MigrateThreads(val checkSession: Boolean) : HomeActivityViewEvents
object StartRecoverySetupFlow : HomeActivityViewEvents
diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt
index cfe76706a5..dd54285fb5 100644
--- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt
@@ -26,11 +26,13 @@ import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.di.MavericksAssistedViewModelFactory
import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.platform.VectorViewModel
+import im.vector.app.features.VectorFeatures
import im.vector.app.features.analytics.AnalyticsConfig
import im.vector.app.features.analytics.AnalyticsTracker
import im.vector.app.features.analytics.extensions.toAnalyticsType
import im.vector.app.features.analytics.plan.Signup
import im.vector.app.features.analytics.store.AnalyticsStore
+import im.vector.app.features.home.room.list.home.release.ReleaseNotesPreferencesStore
import im.vector.app.features.login.ReAuthHelper
import im.vector.app.features.onboarding.AuthenticationDescription
import im.vector.app.features.raw.wellknown.ElementWellKnown
@@ -82,6 +84,8 @@ class HomeActivityViewModel @AssistedInject constructor(
private val vectorPreferences: VectorPreferences,
private val analyticsTracker: AnalyticsTracker,
private val analyticsConfig: AnalyticsConfig,
+ private val releaseNotesPreferencesStore: ReleaseNotesPreferencesStore,
+ private val vectorFeatures: VectorFeatures,
) : VectorViewModel(initialState) {
@AssistedFactory
@@ -110,9 +114,27 @@ class HomeActivityViewModel @AssistedInject constructor(
checkSessionPushIsOn()
observeCrossSigningReset()
observeAnalytics()
+ observeReleaseNotes()
initThreadsMigration()
}
+ private fun observeReleaseNotes() = withState { state ->
+ // we don't want to show release notes for new users or after relogin
+ if (state.authenticationDescription == null && vectorFeatures.isNewAppLayoutEnabled()) {
+ releaseNotesPreferencesStore.appLayoutOnboardingShown.onEach { isAppLayoutOnboardingShown ->
+ if (!isAppLayoutOnboardingShown) {
+ releaseNotesPreferencesStore.setAppLayoutOnboardingShown(true)
+ _viewEvents.post(HomeActivityViewEvents.ShowReleaseNotes)
+ }
+ }.launchIn(viewModelScope)
+ } else {
+ // we assume that users which came from auth flow either have seen updates already (relogin) or don't need them (new user)
+ viewModelScope.launch {
+ releaseNotesPreferencesStore.setAppLayoutOnboardingShown(true)
+ }
+ }
+ }
+
private fun observeAnalytics() {
if (analyticsConfig.isEnabled) {
analyticsStore.didAskUserConsentFlow
diff --git a/vector/src/main/java/im/vector/app/features/home/NewHomeDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/NewHomeDetailFragment.kt
index f47da1e6a0..12dee92430 100644
--- a/vector/src/main/java/im/vector/app/features/home/NewHomeDetailFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/home/NewHomeDetailFragment.kt
@@ -58,14 +58,11 @@ import im.vector.app.features.settings.VectorSettingsActivity.Companion.EXTRA_DI
import im.vector.app.features.spaces.SpaceListBottomSheet
import im.vector.app.features.workers.signout.BannerState
import im.vector.app.features.workers.signout.ServerBackupStatusViewModel
-import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
-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
@AndroidEntryPoint
@@ -322,12 +319,6 @@ class NewHomeDetailFragment :
private fun setupToolbar() {
setupToolbar(views.toolbar)
- lifecycleScope.launch(Dispatchers.IO) {
- session.userService().getUser(session.myUserId)?.let { user ->
- avatarRenderer.render(user.toMatrixItem(), views.avatar)
- }
- }
-
views.collapsingToolbar.debouncedClicks(::openSpaceSettings)
views.toolbar.debouncedClicks(::openSpaceSettings)
@@ -373,9 +364,16 @@ class NewHomeDetailFragment :
vectorPreferences.developerShowDebugInfo()
)
+ refreshAvatar()
hasUnreadRooms = it.hasUnreadMessages
}
+ private fun refreshAvatar() = withState(viewModel) { state ->
+ state.myMatrixItem?.let { user ->
+ avatarRenderer.render(user, views.avatar)
+ }
+ }
+
override fun onTapToReturnToCall() {
callManager.getCurrentCall()?.let { call ->
VectorCallActivity.newIntent(
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt
index 2e150daee6..a5e899c672 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt
@@ -48,7 +48,7 @@ class AudioMessageHelper @Inject constructor(
) {
private var mediaPlayer: MediaPlayer? = null
private var currentPlayingId: String? = null
- private var voiceRecorder: VoiceRecorder = voiceRecorderProvider.provideVoiceRecorder()
+ private val voiceRecorder: VoiceRecorder by lazy { voiceRecorderProvider.provideVoiceRecorder() }
private val amplitudeList = mutableListOf()
@@ -79,18 +79,19 @@ class AudioMessageHelper @Inject constructor(
}
fun stopRecording(): MultiPickerAudioType? {
- tryOrNull("Cannot stop media recording amplitude") {
- stopRecordingAmplitudes()
- }
val voiceMessageFile = tryOrNull("Cannot stop media recorder!") {
voiceRecorder.stopRecord()
voiceRecorder.getVoiceMessageFile()
}
- try {
+ tryOrNull("Cannot stop media recording amplitude") {
+ stopRecordingAmplitudes()
+ }
+
+ return try {
voiceMessageFile?.let {
val outputFileUri = FileProvider.getUriForFile(context, buildMeta.applicationId + ".fileProvider", it, "Voice message.${it.extension}")
- return outputFileUri
+ outputFileUri
.toMultiPickerAudioType(context)
?.apply {
waveform = if (amplitudeList.size < 50) {
@@ -99,10 +100,13 @@ class AudioMessageHelper @Inject constructor(
amplitudeList.chunked(amplitudeList.size / 50) { items -> items.maxOrNull() ?: 0 }
}
}
- } ?: return null
+ }
} catch (e: FileNotFoundException) {
Timber.e(e, "Cannot stop voice recording")
- return null
+ null
+ } catch (e: RuntimeException) {
+ Timber.e(e, "Error while retrieving metadata")
+ null
}
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt
index ca86010915..7d67ec8c60 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt
@@ -883,7 +883,11 @@ class MessageComposerViewModel @AssistedInject constructor(
}
private fun handlePlayOrPauseRecordingPlayback() {
- audioMessageHelper.startOrPauseRecordingPlayback()
+ try {
+ audioMessageHelper.startOrPauseRecordingPlayback()
+ } catch (failure: Throwable) {
+ _viewEvents.post(MessageComposerViewEvents.VoicePlaybackOrRecordingFailure(failure))
+ }
}
fun endAllVoiceActions(deleteRecord: Boolean = true) {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt
index 32635e3407..edb619cd90 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt
@@ -199,11 +199,17 @@ class HomeRoomListFragment :
).also { controller ->
controller.listener = this
controller.onFilterChanged = ::onRoomFilterChanged
+ roomListViewModel.emptyStateFlow.onEach { emptyStateOptional ->
+ controller.submitEmptyStateData(emptyStateOptional.getOrNull())
+ }.launchIn(lifecycleScope)
section.filtersData.onEach {
controller.submitFiltersData(it.getOrNull())
}.launchIn(lifecycleScope)
section.list.observe(viewLifecycleOwner) { list ->
controller.submitList(list)
+ if (list.isEmpty()) {
+ controller.requestForcedModelBuild()
+ }
}
}.adapter
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt
index 425f309202..b52c4e0190 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt
@@ -16,6 +16,7 @@
package im.vector.app.features.home.room.list.home
+import android.widget.ImageView
import androidx.lifecycle.map
import androidx.paging.PagedList
import arrow.core.toOption
@@ -23,11 +24,14 @@ import com.airbnb.mvrx.MavericksViewModelFactory
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
+import im.vector.app.R
import im.vector.app.SpaceStateHandler
import im.vector.app.core.di.MavericksAssistedViewModelFactory
import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.platform.StateView
import im.vector.app.core.platform.VectorViewModel
+import im.vector.app.core.resources.DrawableProvider
+import im.vector.app.core.resources.StringProvider
import im.vector.app.features.home.room.list.home.filter.HomeRoomFilter
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow
@@ -36,6 +40,7 @@ import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
@@ -52,6 +57,7 @@ import org.matrix.android.sdk.api.session.room.RoomSortOrder
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult
import org.matrix.android.sdk.api.session.room.model.Membership
+import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.model.tag.RoomTag
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
import org.matrix.android.sdk.api.session.room.state.isPublic
@@ -63,6 +69,8 @@ class HomeRoomListViewModel @AssistedInject constructor(
private val session: Session,
private val spaceStateHandler: SpaceStateHandler,
private val preferencesStore: HomeLayoutPreferencesStore,
+ private val stringProvider: StringProvider,
+ private val drawableProvider: DrawableProvider,
) : VectorViewModel(initialState) {
@AssistedFactory
@@ -82,6 +90,10 @@ class HomeRoomListViewModel @AssistedInject constructor(
private val _sections = MutableSharedFlow>(replay = 1)
val sections = _sections.asSharedFlow()
+ private var currentFilter: HomeRoomFilter = HomeRoomFilter.ALL
+ private val _emptyStateFlow = MutableSharedFlow>(replay = 1)
+ val emptyStateFlow = _emptyStateFlow.asSharedFlow()
+
private var filteredPagedRoomSummariesLive: UpdatableLivePageResult? = null
init {
@@ -109,6 +121,7 @@ class HomeRoomListViewModel @AssistedInject constructor(
}
newSections.add(getFilteredRoomsSection())
+ emitEmptyState()
_sections.emit(newSections)
setState {
@@ -171,6 +184,7 @@ class HomeRoomListViewModel @AssistedInject constructor(
liveResults.queryParams = liveResults.queryParams.copy(
spaceFilter = selectedSpace?.roomId.toActiveSpaceOrNoFilter()
)
+ emitEmptyState()
}.launchIn(viewModelScope)
return HomeRoomSection.RoomSummaryData(
@@ -179,27 +193,48 @@ class HomeRoomListViewModel @AssistedInject constructor(
)
}
+ private fun emitEmptyState() {
+ viewModelScope.launch {
+ val emptyState = getEmptyStateData(currentFilter, spaceStateHandler.getCurrentSpace())
+ _emptyStateFlow.emit(Optional.from(emptyState))
+ }
+ }
+
private fun getFiltersDataFlow(): SharedFlow>> {
val flow = MutableSharedFlow>>(replay = 1)
- val favouritesFlow = session.flow()
- .liveRoomSummaries(
- RoomSummaryQueryParams.Builder().also { builder ->
- builder.roomTagQueryFilter = RoomTagQueryFilter(true, null, null)
- }.build()
- )
- .map { it.isNotEmpty() }
+ val spaceFLow = spaceStateHandler.getSelectedSpaceFlow()
.distinctUntilChanged()
+ .onStart {
+ emit(spaceStateHandler.getCurrentSpace().toOption())
+ }
- val dmsFLow = session.flow()
- .liveRoomSummaries(
- RoomSummaryQueryParams.Builder().also { builder ->
- builder.memberships = listOf(Membership.JOIN)
- builder.roomCategoryFilter = RoomCategoryFilter.ONLY_DM
- }.build()
- )
- .map { it.isNotEmpty() }
- .distinctUntilChanged()
+ val favouritesFlow =
+ spaceFLow.flatMapLatest { selectedSpace ->
+ session.flow()
+ .liveRoomSummaries(
+ RoomSummaryQueryParams.Builder().also { builder ->
+ builder.spaceFilter = selectedSpace.orNull()?.roomId.toActiveSpaceOrNoFilter()
+ builder.roomTagQueryFilter = RoomTagQueryFilter(true, null, null)
+ }.build()
+ )
+ }
+ .map { it.isNotEmpty() }
+ .distinctUntilChanged()
+
+ val dmsFLow =
+ spaceFLow.flatMapLatest { selectedSpace ->
+ session.flow()
+ .liveRoomSummaries(
+ RoomSummaryQueryParams.Builder().also { builder ->
+ builder.spaceFilter = selectedSpace.orNull()?.roomId.toActiveSpaceOrNoFilter()
+ builder.memberships = listOf(Membership.JOIN)
+ builder.roomCategoryFilter = RoomCategoryFilter.ONLY_DM
+ }.build()
+ )
+ }
+ .map { it.isNotEmpty() }
+ .distinctUntilChanged()
combine(favouritesFlow, dmsFLow, preferencesStore.areFiltersEnabledFlow) { hasFavourite, hasDm, areFiltersEnabled ->
Triple(hasFavourite, hasDm, areFiltersEnabled)
@@ -250,6 +285,38 @@ class HomeRoomListViewModel @AssistedInject constructor(
}
}
+ private fun getEmptyStateData(filter: HomeRoomFilter, selectedSpace: RoomSummary?): StateView.State.Empty? {
+ return when (filter) {
+ HomeRoomFilter.ALL ->
+ if (selectedSpace != null) {
+ StateView.State.Empty(
+ title = stringProvider.getString(R.string.home_empty_space_no_rooms_title, selectedSpace.displayName),
+ message = stringProvider.getString(R.string.home_empty_space_no_rooms_message),
+ image = drawableProvider.getDrawable(R.drawable.ill_empty_space),
+ isBigImage = true
+ )
+ } else {
+ val userName = session.userService().getUser(session.myUserId)?.displayName ?: ""
+ StateView.State.Empty(
+ title = stringProvider.getString(R.string.home_empty_no_rooms_title, userName),
+ message = stringProvider.getString(R.string.home_empty_no_rooms_message),
+ image = drawableProvider.getDrawable(R.drawable.ill_empty_all_chats),
+ isBigImage = true
+ )
+ }
+ HomeRoomFilter.UNREADS ->
+ StateView.State.Empty(
+ title = stringProvider.getString(R.string.home_empty_no_unreads_title),
+ message = stringProvider.getString(R.string.home_empty_no_unreads_message),
+ image = drawableProvider.getDrawable(R.drawable.ill_empty_unreads),
+ isBigImage = true,
+ imageScaleType = ImageView.ScaleType.CENTER_INSIDE
+ )
+ else ->
+ null
+ }
+ }
+
override fun handle(action: HomeRoomListAction) {
when (action) {
is HomeRoomListAction.SelectRoom -> handleSelectRoom(action)
@@ -261,9 +328,12 @@ class HomeRoomListViewModel @AssistedInject constructor(
}
private fun handleChangeRoomFilter(action: HomeRoomListAction.ChangeRoomFilter) {
+ currentFilter = action.filter
filteredPagedRoomSummariesLive?.let { liveResults ->
liveResults.queryParams = getFilteredQueryParams(action.filter, liveResults.queryParams)
}
+
+ emitEmptyState()
}
fun isPublicRoom(roomId: String): Boolean {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/RoomListEmptyItem.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/RoomListEmptyItem.kt
new file mode 100644
index 0000000000..f7b3262529
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/RoomListEmptyItem.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2022 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.room.list.home
+
+import com.airbnb.epoxy.EpoxyAttribute
+import com.airbnb.epoxy.EpoxyModelClass
+import im.vector.app.R
+import im.vector.app.core.epoxy.VectorEpoxyHolder
+import im.vector.app.core.epoxy.VectorEpoxyModel
+import im.vector.app.core.platform.StateView
+
+@EpoxyModelClass
+abstract class RoomListEmptyItem : VectorEpoxyModel(R.layout.item_state_view) {
+
+ @EpoxyAttribute
+ lateinit var emptyData: StateView.State.Empty
+
+ override fun bind(holder: Holder) {
+ super.bind(holder)
+ holder.stateView.state = emptyData
+ }
+
+ class Holder : VectorEpoxyHolder() {
+ val stateView by bind(R.id.stateView)
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/filter/HomeFilteredRoomsController.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/filter/HomeFilteredRoomsController.kt
index 2d673bc089..789c9e9985 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/home/filter/HomeFilteredRoomsController.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/filter/HomeFilteredRoomsController.kt
@@ -18,11 +18,13 @@ package im.vector.app.features.home.room.list.home.filter
import com.airbnb.epoxy.EpoxyModel
import com.airbnb.epoxy.paging.PagedListEpoxyController
+import im.vector.app.core.platform.StateView
import im.vector.app.core.utils.createUIHandler
import im.vector.app.features.home.RoomListDisplayMode
import im.vector.app.features.home.room.list.RoomListListener
import im.vector.app.features.home.room.list.RoomSummaryItemFactory
import im.vector.app.features.home.room.list.RoomSummaryItemPlaceHolder_
+import im.vector.app.features.home.room.list.home.roomListEmptyItem
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
import org.matrix.android.sdk.api.session.room.model.RoomSummary
@@ -44,6 +46,8 @@ class HomeFilteredRoomsController(
var onFilterChanged: ((HomeRoomFilter) -> Unit)? = null
private var filtersData: List? = null
+ private var emptyStateData: StateView.State.Empty? = null
+ private var currentState: StateView.State = StateView.State.Content
override fun addModels(models: List>) {
val host = this
@@ -54,14 +58,29 @@ class HomeFilteredRoomsController(
onFilterChangedListener(host.onFilterChanged)
}
}
- super.addModels(models)
+
+ if (models.isEmpty() && emptyStateData != null) {
+ emptyStateData?.let { emptyState ->
+ roomListEmptyItem {
+ id("state_item")
+ emptyData(emptyState)
+ }
+ currentState = emptyState
+ }
+ } else {
+ currentState = StateView.State.Content
+ super.addModels(models)
+ }
+ }
+
+ fun submitEmptyStateData(state: StateView.State.Empty?) {
+ this.emptyStateData = state
}
fun submitFiltersData(data: List?) {
this.filtersData = data
requestForcedModelBuild()
}
-
override fun buildItemModel(currentPosition: Int, item: RoomSummary?): EpoxyModel<*> {
item ?: return RoomSummaryItemPlaceHolder_().apply { id(currentPosition) }
return roomSummaryItemFactory.create(item, roomChangeMembershipStates.orEmpty(), emptySet(), RoomListDisplayMode.ROOMS, listener)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesFragment.kt
index 74b46cec33..f557483289 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesFragment.kt
@@ -20,15 +20,18 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import androidx.lifecycle.lifecycleScope
import com.airbnb.mvrx.fragmentViewModel
-import com.airbnb.mvrx.withState
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.core.extensions.configureWith
+import im.vector.app.core.platform.StateView
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentInvitesBinding
import im.vector.app.features.analytics.plan.ViewRoom
import im.vector.app.features.home.room.list.RoomListListener
import im.vector.app.features.notifications.NotificationDrawerManager
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
import javax.inject.Inject
@@ -51,6 +54,8 @@ class InvitesFragment : VectorBaseFragment(), RoomListLi
setupToolbar(views.invitesToolbar)
.allowBack()
+ views.invitesStateView.contentView = views.invitesRecycler
+
views.invitesRecycler.configureWith(controller)
controller.listener = this
@@ -62,13 +67,31 @@ class InvitesFragment : VectorBaseFragment(), RoomListLi
when (it) {
is InvitesViewEvents.Failure -> showFailure(it.throwable)
is InvitesViewEvents.OpenRoom -> handleOpenRoom(it.roomSummary, it.shouldCloseInviteView)
- InvitesViewEvents.Close -> handleClose()
}
}
- }
- private fun handleClose() {
- requireActivity().finish()
+ viewModel.invites.onEach {
+ when (it) {
+ is InvitesContentState.Content -> {
+ views.invitesStateView.state = StateView.State.Content
+ controller.submitList(it.content)
+ }
+ is InvitesContentState.Empty -> {
+ views.invitesStateView.state = StateView.State.Empty(
+ title = it.title,
+ image = it.image,
+ message = it.message
+ )
+ }
+ is InvitesContentState.Error -> {
+ when (views.invitesStateView.state) {
+ StateView.State.Content -> showErrorInSnackbar(it.throwable)
+ else -> views.invitesStateView.state = StateView.State.Error(it.throwable.message)
+ }
+ }
+ InvitesContentState.Loading -> views.invitesStateView.state = StateView.State.Loading
+ }
+ }.launchIn(viewLifecycleOwner.lifecycleScope)
}
private fun handleOpenRoom(roomSummary: RoomSummary, shouldCloseInviteView: Boolean) {
@@ -83,14 +106,6 @@ class InvitesFragment : VectorBaseFragment(), RoomListLi
}
}
- override fun invalidate(): Unit = withState(viewModel) { state ->
- super.invalidate()
-
- state.pagedList?.observe(viewLifecycleOwner) { list ->
- controller.submitList(list)
- }
- }
-
override fun onRejectRoomInvitation(room: RoomSummary) {
notificationDrawerManager.updateEvents { it.clearMemberShipNotificationForRoom(room.roomId) }
viewModel.handle(InvitesAction.RejectInvitation(room))
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesViewEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesViewEvents.kt
index d68577cf95..21310592a4 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesViewEvents.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesViewEvents.kt
@@ -22,5 +22,4 @@ import org.matrix.android.sdk.api.session.room.model.RoomSummary
sealed class InvitesViewEvents : VectorViewEvents {
data class Failure(val throwable: Throwable) : InvitesViewEvents()
data class OpenRoom(val roomSummary: RoomSummary, val shouldCloseInviteView: Boolean) : InvitesViewEvents()
- object Close : InvitesViewEvents()
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesViewModel.kt
index b0d854be66..b8034d2364 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesViewModel.kt
@@ -16,14 +16,25 @@
package im.vector.app.features.home.room.list.home.invites
+import androidx.lifecycle.asFlow
import androidx.paging.PagedList
import com.airbnb.mvrx.MavericksViewModelFactory
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
+import im.vector.app.R
import im.vector.app.core.di.MavericksAssistedViewModelFactory
import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.platform.VectorViewModel
+import im.vector.app.core.resources.DrawableProvider
+import im.vector.app.core.resources.StringProvider
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.asSharedFlow
+import kotlinx.coroutines.flow.catch
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.Session
@@ -36,6 +47,8 @@ import timber.log.Timber
class InvitesViewModel @AssistedInject constructor(
@Assisted val initialState: InvitesViewState,
private val session: Session,
+ private val stringProvider: StringProvider,
+ private val drawableProvider: DrawableProvider
) : VectorViewModel(initialState) {
private val pagedListConfig = PagedList.Config.Builder()
@@ -52,6 +65,11 @@ class InvitesViewModel @AssistedInject constructor(
companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory()
+ private val _invites = MutableSharedFlow(replay = 1)
+ val invites = _invites.asSharedFlow()
+
+ private var invitesCount = -1
+
init {
observeInvites()
}
@@ -72,8 +90,6 @@ class InvitesViewModel @AssistedInject constructor(
return@withState
}
- val shouldCloseInviteView = state.pagedList?.value?.size == 1
-
viewModelScope.launch {
try {
session.roomService().leaveRoom(roomId)
@@ -81,9 +97,6 @@ class InvitesViewModel @AssistedInject constructor(
// Instead, we wait for the room to be rejected
// Known bug: if the user is invited again (after rejecting the first invitation), the loading will be displayed instead of the buttons.
// If we update the state, the button will be displayed again, so it's not ideal...
- if (shouldCloseInviteView) {
- _viewEvents.post(InvitesViewEvents.Close)
- }
} catch (failure: Throwable) {
// Notify the user
_viewEvents.post(InvitesViewEvents.Failure(failure))
@@ -101,9 +114,7 @@ class InvitesViewModel @AssistedInject constructor(
}
// close invites view when navigate to a room from the last one invite
- val shouldCloseInviteView = state.pagedList?.value?.size == 1
-
- _viewEvents.post(InvitesViewEvents.OpenRoom(action.roomSummary, shouldCloseInviteView))
+ val shouldCloseInviteView = invitesCount == 1
// quick echo
setState {
@@ -117,6 +128,8 @@ class InvitesViewModel @AssistedInject constructor(
}
)
}
+
+ _viewEvents.post(InvitesViewEvents.OpenRoom(action.roomSummary, shouldCloseInviteView))
}
private fun observeInvites() {
@@ -129,8 +142,26 @@ class InvitesViewModel @AssistedInject constructor(
sortOrder = RoomSortOrder.ACTIVITY
)
- setState {
- copy(pagedList = pagedList)
- }
+ pagedList.asFlow()
+ .map {
+ if (it.isEmpty()) {
+ InvitesContentState.Empty(
+ title = stringProvider.getString(R.string.invites_empty_title),
+ image = drawableProvider.getDrawable(R.drawable.ic_invites_empty),
+ message = stringProvider.getString(R.string.invites_empty_message)
+ )
+ } else {
+ invitesCount = it.loadedCount
+ InvitesContentState.Content(it)
+ }
+ }
+ .catch {
+ emit(InvitesContentState.Error(it))
+ }
+ .onStart {
+ emit(InvitesContentState.Loading)
+ }.onEach {
+ _invites.emit(it)
+ }.launchIn(viewModelScope)
}
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesViewState.kt
index 708db29604..2f82c3fe76 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesViewState.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesViewState.kt
@@ -16,13 +16,24 @@
package im.vector.app.features.home.room.list.home.invites
-import androidx.lifecycle.LiveData
+import android.graphics.drawable.Drawable
import androidx.paging.PagedList
import com.airbnb.mvrx.MavericksState
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
import org.matrix.android.sdk.api.session.room.model.RoomSummary
data class InvitesViewState(
- val pagedList: LiveData>? = null,
val roomMembershipChanges: Map = emptyMap(),
) : MavericksState
+
+sealed interface InvitesContentState {
+ object Loading : InvitesContentState
+ data class Empty(
+ val title: CharSequence,
+ val image: Drawable?,
+ val message: CharSequence
+ ) : InvitesContentState
+
+ data class Content(val content: PagedList) : InvitesContentState
+ data class Error(val throwable: Throwable) : InvitesContentState
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/recent/RecentRoomCarouselController.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/recent/RecentRoomCarouselController.kt
index ebec912779..0c5d9a3533 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/home/recent/RecentRoomCarouselController.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/recent/RecentRoomCarouselController.kt
@@ -23,6 +23,8 @@ import com.airbnb.epoxy.CarouselModelBuilder
import com.airbnb.epoxy.EpoxyController
import com.airbnb.epoxy.EpoxyModel
import com.airbnb.epoxy.carousel
+import com.google.android.material.color.MaterialColors
+import im.vector.app.R
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.room.list.RoomListListener
import org.matrix.android.sdk.api.session.room.model.RoomSummary
@@ -43,6 +45,12 @@ class RecentRoomCarouselController @Inject constructor(
resources.displayMetrics
).toInt()
+ private val topPadding = TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_DIP,
+ 12f,
+ resources.displayMetrics
+ ).toInt()
+
private val itemSpacing = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
24f,
@@ -61,11 +69,15 @@ class RecentRoomCarouselController @Inject constructor(
id("recents_carousel")
padding(Carousel.Padding(
host.hPadding,
- 0,
+ host.topPadding,
host.hPadding,
0,
host.itemSpacing)
)
+ onBind { _, view, _ ->
+ val colorSurface = MaterialColors.getColor(view, R.attr.vctr_toolbar_background)
+ view.setBackgroundColor(colorSurface)
+ }
withModelsFrom(data) { roomSummary ->
val onClick = host.listener?.let { it::onRoomClicked }
val onLongClick = host.listener?.let { it::onRoomLongClicked }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/release/ReleaseCarouselData.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/release/ReleaseCarouselData.kt
new file mode 100644
index 0000000000..22431b0bf9
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/release/ReleaseCarouselData.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2022 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.room.list.home.release
+
+import androidx.annotation.DrawableRes
+import androidx.annotation.StringRes
+
+class ReleaseCarouselData(
+ val items: List-
+) {
+ data class Item(
+ @StringRes val title: Int,
+ @StringRes val body: Int,
+ @DrawableRes val image: Int,
+ )
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/release/ReleaseCarouselItem.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/release/ReleaseCarouselItem.kt
new file mode 100644
index 0000000000..49eb0761f7
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/release/ReleaseCarouselItem.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2022 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.room.list.home.release
+
+import android.widget.ImageView
+import android.widget.TextView
+import com.airbnb.epoxy.EpoxyAttribute
+import com.airbnb.epoxy.EpoxyModelClass
+import im.vector.app.R
+import im.vector.app.core.epoxy.VectorEpoxyHolder
+import im.vector.app.core.epoxy.VectorEpoxyModel
+
+@EpoxyModelClass
+abstract class ReleaseCarouselItem : VectorEpoxyModel(R.layout.item_release_carousel) {
+
+ @EpoxyAttribute
+ lateinit var item: ReleaseCarouselData.Item
+
+ override fun bind(holder: Holder) {
+ super.bind(holder)
+
+ holder.image.setImageResource(item.image)
+ holder.title.setText(item.title)
+ holder.body.setText(item.body)
+ }
+
+ class Holder : VectorEpoxyHolder() {
+ val image by bind(R.id.carousel_item_image)
+ val title by bind(R.id.carousel_item_title)
+ val body by bind(R.id.carousel_item_body)
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/release/ReleaseNotesAction.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/release/ReleaseNotesAction.kt
new file mode 100644
index 0000000000..7a66d00589
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/release/ReleaseNotesAction.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2022 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.room.list.home.release
+
+import im.vector.app.core.platform.VectorViewModelAction
+
+sealed class ReleaseNotesAction : VectorViewModelAction {
+ data class NextPressed(
+ val isLastItemSelected: Boolean = false
+ ) : ReleaseNotesAction()
+ data class PageSelected(
+ val selectedPageIndex: Int = 0
+ ) : ReleaseNotesAction()
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/release/ReleaseNotesActivity.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/release/ReleaseNotesActivity.kt
new file mode 100644
index 0000000000..c5cc55d7bb
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/release/ReleaseNotesActivity.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2022 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.room.list.home.release
+
+import dagger.hilt.android.AndroidEntryPoint
+import im.vector.app.core.extensions.addFragment
+import im.vector.app.core.platform.ScreenOrientationLocker
+import im.vector.app.core.platform.VectorBaseActivity
+import im.vector.app.databinding.ActivitySimpleBinding
+import javax.inject.Inject
+
+@AndroidEntryPoint
+class ReleaseNotesActivity : VectorBaseActivity() {
+
+ @Inject lateinit var orientationLocker: ScreenOrientationLocker
+
+ override fun getBinding() = ActivitySimpleBinding.inflate(layoutInflater)
+
+ override fun getCoordinatorLayout() = views.coordinatorLayout
+
+ override fun initUiAndData() {
+ orientationLocker.lockPhonesToPortrait(this)
+ if (isFirstCreation()) {
+ addFragment(views.simpleFragmentContainer, ReleaseNotesFragment::class.java)
+ }
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/release/ReleaseNotesCarouselController.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/release/ReleaseNotesCarouselController.kt
new file mode 100644
index 0000000000..22d2915c47
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/release/ReleaseNotesCarouselController.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2021 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.room.list.home.release
+
+import com.airbnb.epoxy.TypedEpoxyController
+import javax.inject.Inject
+
+class ReleaseNotesCarouselController @Inject constructor() : TypedEpoxyController() {
+ override fun buildModels(data: ReleaseCarouselData) {
+ data.items.forEachIndexed { index, item ->
+ releaseCarouselItem {
+ id(index)
+ item(item)
+ }
+ }
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/release/ReleaseNotesFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/release/ReleaseNotesFragment.kt
new file mode 100644
index 0000000000..6b86897dc8
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/release/ReleaseNotesFragment.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) 2022 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.room.list.home.release
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.lifecycle.DefaultLifecycleObserver
+import androidx.lifecycle.LifecycleOwner
+import androidx.viewpager2.widget.ViewPager2
+import com.airbnb.mvrx.fragmentViewModel
+import com.google.android.material.tabs.TabLayoutMediator
+import dagger.hilt.android.AndroidEntryPoint
+import im.vector.app.R
+import im.vector.app.core.epoxy.onClick
+import im.vector.app.core.platform.VectorBaseFragment
+import im.vector.app.databinding.BottomSheetReleaseNotesBinding
+import javax.inject.Inject
+
+@AndroidEntryPoint
+class ReleaseNotesFragment : VectorBaseFragment() {
+
+ @Inject lateinit var carouselController: ReleaseNotesCarouselController
+ private var tabLayoutMediator: TabLayoutMediator? = null
+
+ private val viewModel by fragmentViewModel(ReleaseNotesViewModel::class)
+
+ override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetReleaseNotesBinding {
+ return BottomSheetReleaseNotesBinding.inflate(inflater, container, false)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ val carouselAdapter = carouselController.adapter
+ views.releaseNotesCarousel.adapter = carouselAdapter
+
+ tabLayoutMediator = TabLayoutMediator(views.releaseNotesCarouselIndicator, views.releaseNotesCarousel) { _, _ -> }
+ .also { it.attach() }
+
+ val pageCallback = object : ViewPager2.OnPageChangeCallback() {
+ override fun onPageSelected(position: Int) {
+ viewModel.handle(ReleaseNotesAction.PageSelected(position))
+ updateButtonText(position)
+ }
+ }
+
+ viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver {
+ override fun onCreate(owner: LifecycleOwner) {
+ views.releaseNotesCarousel.registerOnPageChangeCallback(pageCallback)
+ }
+
+ override fun onDestroy(owner: LifecycleOwner) {
+ views.releaseNotesCarousel.unregisterOnPageChangeCallback(pageCallback)
+ }
+ })
+
+ carouselController.setData(createCarouselData())
+
+ views.releaseNotesBtnClose.onClick { close() }
+ views.releaseNotesButtonNext.onClick {
+ val isLastItemSelected = with(views.releaseNotesCarouselIndicator) {
+ selectedTabPosition == tabCount - 1
+ }
+ viewModel.handle(ReleaseNotesAction.NextPressed(isLastItemSelected))
+ }
+
+ viewModel.observeViewEvents {
+ when (it) {
+ is ReleaseNotesViewEvents.SelectPage -> selectPage(it.index)
+ ReleaseNotesViewEvents.Close -> close()
+ }
+ }
+ }
+
+ private fun createCarouselData(): ReleaseCarouselData {
+ return ReleaseCarouselData(
+ listOf(
+ ReleaseCarouselData.Item(
+ R.string.onboarding_new_app_layout_welcome_title,
+ R.string.onboarding_new_app_layout_welcome_message,
+ R.drawable.ill_app_layout_onboarding_rooms
+ ),
+ ReleaseCarouselData.Item(
+ R.string.onboarding_new_app_layout_spaces_title,
+ R.string.onboarding_new_app_layout_spaces_message,
+ R.drawable.ill_app_layout_onboarding_spaces
+ ),
+ ReleaseCarouselData.Item(
+ R.string.onboarding_new_app_layout_feedback_title,
+ R.string.onboarding_new_app_layout_feedback_message,
+ R.drawable.ill_app_layout_onboarding_rooms
+ ),
+ )
+ )
+ }
+
+ private fun close() {
+ requireActivity().finish()
+ }
+
+ private fun selectPage(index: Int) {
+ views.releaseNotesCarouselIndicator.selectTab(views.releaseNotesCarouselIndicator.getTabAt(index))
+ updateButtonText(index)
+ }
+
+ private fun updateButtonText(selectedIndex: Int) {
+ val isLastItem = selectedIndex == views.releaseNotesCarouselIndicator.tabCount - 1
+ if (isLastItem) {
+ views.releaseNotesButtonNext.setText(R.string.onboarding_new_app_layout_button_try)
+ } else {
+ views.releaseNotesButtonNext.setText(R.string.action_next)
+ }
+ }
+
+ override fun onDestroyView() {
+ tabLayoutMediator?.detach()
+ tabLayoutMediator = null
+
+ views.releaseNotesCarousel.adapter = null
+ super.onDestroyView()
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/release/ReleaseNotesPreferencesStore.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/release/ReleaseNotesPreferencesStore.kt
new file mode 100644
index 0000000000..cefe107905
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/release/ReleaseNotesPreferencesStore.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2022 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.room.list.home.release
+
+import android.content.Context
+import androidx.datastore.core.DataStore
+import androidx.datastore.preferences.core.Preferences
+import androidx.datastore.preferences.core.booleanPreferencesKey
+import androidx.datastore.preferences.core.edit
+import androidx.datastore.preferences.preferencesDataStore
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import org.matrix.android.sdk.api.extensions.orFalse
+import javax.inject.Inject
+
+private val Context.dataStore: DataStore by preferencesDataStore(name = "release_notes")
+
+class ReleaseNotesPreferencesStore @Inject constructor(
+ private val context: Context
+) {
+
+ private val isAppLayoutOnboardingShown = booleanPreferencesKey("SETTINGS_APP_LAYOUT_ONBOARDING_SHOWN")
+
+ val appLayoutOnboardingShown: Flow = context.dataStore.data
+ .map { preferences -> preferences[isAppLayoutOnboardingShown].orFalse() }
+ .distinctUntilChanged()
+
+ suspend fun setAppLayoutOnboardingShown(isShown: Boolean) {
+ context.dataStore.edit { settings ->
+ settings[isAppLayoutOnboardingShown] = isShown
+ }
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/release/ReleaseNotesViewEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/release/ReleaseNotesViewEvents.kt
new file mode 100644
index 0000000000..7901a8b28f
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/release/ReleaseNotesViewEvents.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2022 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.room.list.home.release
+
+import im.vector.app.core.platform.VectorViewEvents
+
+sealed class ReleaseNotesViewEvents : VectorViewEvents {
+ object Close : ReleaseNotesViewEvents()
+ data class SelectPage(val index: Int) : ReleaseNotesViewEvents()
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/release/ReleaseNotesViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/release/ReleaseNotesViewModel.kt
new file mode 100644
index 0000000000..23e2364d0c
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/release/ReleaseNotesViewModel.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2022 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.room.list.home.release
+
+import com.airbnb.mvrx.MavericksViewModelFactory
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import im.vector.app.core.di.MavericksAssistedViewModelFactory
+import im.vector.app.core.di.hiltMavericksViewModelFactory
+import im.vector.app.core.platform.VectorDummyViewState
+import im.vector.app.core.platform.VectorViewModel
+
+class ReleaseNotesViewModel @AssistedInject constructor(
+ @Assisted initialState: VectorDummyViewState,
+) : VectorViewModel(initialState) {
+
+ @AssistedFactory
+ interface Factory : MavericksAssistedViewModelFactory {
+ override fun create(initialState: VectorDummyViewState): ReleaseNotesViewModel
+ }
+
+ companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory()
+
+ private var selectedPageIndex = 0
+
+ init {
+ _viewEvents.post(ReleaseNotesViewEvents.SelectPage(0))
+ }
+
+ override fun handle(action: ReleaseNotesAction) {
+ when (action) {
+ is ReleaseNotesAction.NextPressed -> handleNextPressed(action)
+ is ReleaseNotesAction.PageSelected -> handlePageSelected(action)
+ }
+ }
+
+ private fun handlePageSelected(action: ReleaseNotesAction.PageSelected) {
+ selectedPageIndex = action.selectedPageIndex
+ }
+
+ private fun handleNextPressed(action: ReleaseNotesAction.NextPressed) {
+ if (action.isLastItemSelected) {
+ _viewEvents.post(ReleaseNotesViewEvents.Close)
+ } else {
+ _viewEvents.post(ReleaseNotesViewEvents.SelectPage(++selectedPageIndex))
+ }
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt
index 9661feb002..52c445f1fa 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt
@@ -627,7 +627,7 @@ class OnboardingViewModel @AssistedInject constructor(
_viewEvents.post(OnboardingViewEvents.OnAccountCreated)
}
AuthenticationDescription.Login -> {
- setState { copy(isLoading = false) }
+ setState { copy(isLoading = false, selectedAuthenticationState = SelectedAuthenticationState(authenticationDescription)) }
_viewEvents.post(OnboardingViewEvents.OnAccountSignedIn)
}
}
diff --git a/vector/src/main/java/im/vector/app/features/popup/PopupAlertManager.kt b/vector/src/main/java/im/vector/app/features/popup/PopupAlertManager.kt
index 40ef6d819e..b1327f0caf 100644
--- a/vector/src/main/java/im/vector/app/features/popup/PopupAlertManager.kt
+++ b/vector/src/main/java/im/vector/app/features/popup/PopupAlertManager.kt
@@ -28,6 +28,7 @@ import im.vector.app.core.time.Clock
import im.vector.app.core.utils.isAnimationEnabled
import im.vector.app.features.MainActivity
import im.vector.app.features.analytics.ui.consent.AnalyticsOptInActivity
+import im.vector.app.features.home.room.list.home.release.ReleaseNotesActivity
import im.vector.app.features.pin.PinActivity
import im.vector.app.features.signout.hard.SignedOutActivity
import im.vector.app.features.themes.ThemeUtils
@@ -307,6 +308,7 @@ class PopupAlertManager @Inject constructor(
activity !is PinActivity &&
activity !is SignedOutActivity &&
activity !is AnalyticsOptInActivity &&
+ activity !is ReleaseNotesActivity &&
activity is VectorBaseActivity<*> &&
alert.shouldBeDisplayedIn.invoke(activity)
}
diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt
index cefbe64d9d..5e0cdf3baf 100755
--- a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt
@@ -165,6 +165,11 @@ class VectorPreferences @Inject constructor(
const val SETTINGS_LABS_AUTO_REPORT_UISI = "SETTINGS_LABS_AUTO_REPORT_UISI"
const val SETTINGS_PREF_SPACE_SHOW_ALL_ROOM_IN_HOME = "SETTINGS_PREF_SPACE_SHOW_ALL_ROOM_IN_HOME"
+ /**
+ * This is not preference, but category on preferences screen which contains [SETTINGS_PREF_SPACE_SHOW_ALL_ROOM_IN_HOME].
+ * Needed to show/hide this category, depending on visibility of [SETTINGS_PREF_SPACE_SHOW_ALL_ROOM_IN_HOME]. */
+ const val SETTINGS_PREF_SPACE_CATEGORY = "SETTINGS_PREF_SPACE_CATEGORY"
+
private const val SETTINGS_DEVELOPER_MODE_PREFERENCE_KEY = "SETTINGS_DEVELOPER_MODE_PREFERENCE_KEY"
private const val SETTINGS_LABS_SHOW_HIDDEN_EVENTS_PREFERENCE_KEY = "SETTINGS_LABS_SHOW_HIDDEN_EVENTS_PREFERENCE_KEY"
private const val SETTINGS_LABS_ENABLE_SWIPE_TO_REPLY = "SETTINGS_LABS_ENABLE_SWIPE_TO_REPLY"
diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt
index eb7864a89d..c1253f4ab4 100644
--- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt
@@ -27,6 +27,7 @@ import im.vector.app.R
import im.vector.app.core.preference.VectorSwitchPreference
import im.vector.app.features.MainActivity
import im.vector.app.features.MainActivityArgs
+import im.vector.app.features.VectorFeatures
import im.vector.app.features.analytics.plan.MobileScreen
import im.vector.app.features.home.room.threads.ThreadsManager
import org.matrix.android.sdk.api.settings.LightweightSettingsStorage
@@ -39,6 +40,7 @@ class VectorSettingsLabsFragment :
@Inject lateinit var vectorPreferences: VectorPreferences
@Inject lateinit var lightweightSettingsStorage: LightweightSettingsStorage
@Inject lateinit var threadsManager: ThreadsManager
+ @Inject lateinit var vectorFeatures: VectorFeatures
override var titleRes = R.string.room_settings_labs_pref_title
override val preferenceXmlRes = R.xml.vector_settings_labs
@@ -72,6 +74,10 @@ class VectorSettingsLabsFragment :
true
}
}
+
+ findPreference(VectorPreferences.SETTINGS_LABS_UNREAD_NOTIFICATIONS_AS_TAB)!!.let {
+ it.isVisible = !vectorFeatures.isNewAppLayoutEnabled()
+ }
}
/**
diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt
index 0bd5316b8f..135c25cd8d 100644
--- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt
@@ -31,6 +31,7 @@ import im.vector.app.core.preference.VectorPreference
import im.vector.app.core.preference.VectorSwitchPreference
import im.vector.app.features.MainActivity
import im.vector.app.features.MainActivityArgs
+import im.vector.app.features.VectorFeatures
import im.vector.app.features.analytics.plan.MobileScreen
import im.vector.app.features.settings.font.FontScaleSettingActivity
import im.vector.app.features.themes.ThemeUtils
@@ -44,6 +45,7 @@ class VectorSettingsPreferencesFragment :
@Inject lateinit var vectorPreferences: VectorPreferences
@Inject lateinit var fontScalePreferences: FontScalePreferences
+ @Inject lateinit var vectorFeatures: VectorFeatures
override var titleRes = R.string.settings_preferences
override val preferenceXmlRes = R.xml.vector_settings_preferences
@@ -99,6 +101,10 @@ class VectorSettingsPreferencesFragment :
}
}
+ findPreference(VectorPreferences.SETTINGS_PREF_SPACE_CATEGORY)!!.let { pref ->
+ pref.isVisible = !vectorFeatures.isNewAppLayoutEnabled()
+ }
+
// Url preview
/*
TODO Note: we keep the setting client side for now
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt
index 03e2d2fd98..554001ad43 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt
@@ -43,7 +43,6 @@ import im.vector.app.features.settings.devices.DeviceFullInfo
import im.vector.app.features.settings.devices.DevicesAction
import im.vector.app.features.settings.devices.DevicesViewEvents
import im.vector.app.features.settings.devices.DevicesViewModel
-import im.vector.app.features.settings.devices.v2.list.OtherSessionsController
import im.vector.app.features.settings.devices.v2.list.OtherSessionsView
import im.vector.app.features.settings.devices.v2.list.SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS
import im.vector.app.features.settings.devices.v2.list.SecurityRecommendationViewState
@@ -129,11 +128,6 @@ class VectorSettingsDevicesFragment :
private fun initOtherSessionsView() {
views.deviceListOtherSessions.callback = this
- views.deviceListOtherSessions.setCallback(object : OtherSessionsController.Callback {
- override fun onItemClicked(deviceId: String) {
- navigateToSessionOverview(deviceId)
- }
- })
}
override fun onDestroyView() {
@@ -264,6 +258,10 @@ class VectorSettingsDevicesFragment :
}
}
+ override fun onOtherSessionClicked(deviceId: String) {
+ navigateToSessionOverview(deviceId)
+ }
+
override fun onViewAllOtherSessionsClicked() {
viewNavigator.navigateToOtherSessions(requireActivity())
}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsView.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsView.kt
index c6f8c02d22..bb61e0f595 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsView.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsView.kt
@@ -32,9 +32,10 @@ class OtherSessionsView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
-) : ConstraintLayout(context, attrs, defStyleAttr) {
+) : ConstraintLayout(context, attrs, defStyleAttr), OtherSessionsController.Callback {
interface Callback {
+ fun onOtherSessionClicked(deviceId: String)
fun onViewAllOtherSessionsClicked()
}
@@ -47,6 +48,8 @@ class OtherSessionsView @JvmOverloads constructor(
inflate(context, R.layout.view_other_sessions, this)
views = ViewOtherSessionsBinding.bind(this)
+ otherSessionsController.callback = this
+
views.otherSessionsViewAllButton.setOnClickListener {
callback?.onViewAllOtherSessionsClicked()
}
@@ -58,13 +61,13 @@ class OtherSessionsView @JvmOverloads constructor(
otherSessionsController.setData(devices)
}
- fun setCallback(callback: OtherSessionsController.Callback) {
- otherSessionsController.callback = callback
- }
-
override fun onDetachedFromWindow() {
otherSessionsController.callback = null
views.otherSessionsRecyclerView.cleanup()
super.onDetachedFromWindow()
}
+
+ override fun onItemClicked(deviceId: String) {
+ callback?.onOtherSessionClicked(deviceId)
+ }
}
diff --git a/vector/src/main/java/im/vector/app/features/spaces/NewSpaceSummaryItem.kt b/vector/src/main/java/im/vector/app/features/spaces/NewSpaceSummaryItem.kt
index e6e4aadb9c..d59de6ac99 100644
--- a/vector/src/main/java/im/vector/app/features/spaces/NewSpaceSummaryItem.kt
+++ b/vector/src/main/java/im/vector/app/features/spaces/NewSpaceSummaryItem.kt
@@ -58,7 +58,10 @@ abstract class NewSpaceSummaryItem : VectorEpoxyModel 0
holder.indent.updateLayoutParams {
diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceListFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceListFragment.kt
index ca9279cb37..0153d64df7 100644
--- a/vector/src/main/java/im/vector/app/features/spaces/SpaceListFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceListFragment.kt
@@ -21,6 +21,7 @@ import android.view.HapticFeedbackConstants
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import androidx.core.view.isVisible
import com.airbnb.epoxy.EpoxyTouchHelper
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.Success
@@ -28,6 +29,7 @@ import com.airbnb.mvrx.Uninitialized
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
import dagger.hilt.android.AndroidEntryPoint
+import im.vector.app.core.epoxy.onClick
import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith
import im.vector.app.core.platform.StateView
@@ -71,6 +73,7 @@ class SpaceListFragment :
homeActivitySharedActionViewModel = activityViewModelProvider[HomeSharedActionViewModel::class.java]
roomListSharedActionViewModel = activityViewModelProvider[RoomListSharedActionViewModel::class.java]
views.stateView.contentView = views.groupListView
+ views.spacesEmptyButton.onClick { onAddSpaceSelected() }
setupSpaceController()
observeViewEvents()
}
@@ -147,13 +150,22 @@ class SpaceListFragment :
}
override fun invalidate() = withState(viewModel) { state ->
- when (state.asyncSpaces) {
+ when (val spaces = state.asyncSpaces) {
Uninitialized,
is Loading -> {
views.stateView.state = StateView.State.Loading
return@withState
}
- is Success -> views.stateView.state = StateView.State.Content
+ is Success -> {
+ views.stateView.state = StateView.State.Content
+ if (spaces.invoke().isEmpty()) {
+ views.spacesEmptyGroup.isVisible = true
+ views.groupListView.isVisible = false
+ } else {
+ views.spacesEmptyGroup.isVisible = false
+ views.groupListView.isVisible = true
+ }
+ }
else -> Unit
}
diff --git a/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderProvider.kt b/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderProvider.kt
index 28693ca287..1bf289fb4c 100644
--- a/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderProvider.kt
+++ b/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderProvider.kt
@@ -17,7 +17,10 @@
package im.vector.app.features.voice
import android.content.Context
+import android.media.MediaCodecList
+import android.media.MediaFormat
import android.os.Build
+import androidx.annotation.VisibleForTesting
import im.vector.app.features.VectorFeatures
import kotlinx.coroutines.Dispatchers
import javax.inject.Inject
@@ -27,10 +30,21 @@ class VoiceRecorderProvider @Inject constructor(
private val vectorFeatures: VectorFeatures,
) {
fun provideVoiceRecorder(): VoiceRecorder {
- return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && vectorFeatures.forceUsageOfOpusEncoder().not()) {
- VoiceRecorderQ(context)
- } else {
+ return if (useFallbackRecorder()) {
VoiceRecorderL(context, Dispatchers.IO)
+ } else {
+ VoiceRecorderQ(context)
}
}
+
+ private fun useFallbackRecorder(): Boolean {
+ return Build.VERSION.SDK_INT < Build.VERSION_CODES.Q || !hasOpusEncoder() || vectorFeatures.forceUsageOfOpusEncoder()
+ }
+
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ internal fun hasOpusEncoder(): Boolean {
+ val codecList = MediaCodecList(MediaCodecList.ALL_CODECS)
+ val format = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_OPUS, 48000, 1)
+ return codecList.findEncoderForFormat(format) != null
+ }
}
diff --git a/vector/src/main/res/drawable-hdpi/ill_app_layout_onboarding_rooms.webp b/vector/src/main/res/drawable-hdpi/ill_app_layout_onboarding_rooms.webp
new file mode 100644
index 0000000000..5ac890e617
Binary files /dev/null and b/vector/src/main/res/drawable-hdpi/ill_app_layout_onboarding_rooms.webp differ
diff --git a/vector/src/main/res/drawable-hdpi/ill_app_layout_onboarding_spaces.webp b/vector/src/main/res/drawable-hdpi/ill_app_layout_onboarding_spaces.webp
new file mode 100644
index 0000000000..35f2a04236
Binary files /dev/null and b/vector/src/main/res/drawable-hdpi/ill_app_layout_onboarding_spaces.webp differ
diff --git a/vector/src/main/res/drawable-hdpi/ill_empty_all_chats.webp b/vector/src/main/res/drawable-hdpi/ill_empty_all_chats.webp
new file mode 100644
index 0000000000..6f5211b17e
Binary files /dev/null and b/vector/src/main/res/drawable-hdpi/ill_empty_all_chats.webp differ
diff --git a/vector/src/main/res/drawable-hdpi/ill_empty_space.webp b/vector/src/main/res/drawable-hdpi/ill_empty_space.webp
new file mode 100644
index 0000000000..b33fe7937c
Binary files /dev/null and b/vector/src/main/res/drawable-hdpi/ill_empty_space.webp differ
diff --git a/vector/src/main/res/drawable-hdpi/ill_empty_unreads.webp b/vector/src/main/res/drawable-hdpi/ill_empty_unreads.webp
new file mode 100644
index 0000000000..ce94823cc7
Binary files /dev/null and b/vector/src/main/res/drawable-hdpi/ill_empty_unreads.webp differ
diff --git a/vector/src/main/res/drawable-mdpi/ill_app_layout_onboarding_rooms.webp b/vector/src/main/res/drawable-mdpi/ill_app_layout_onboarding_rooms.webp
new file mode 100644
index 0000000000..07ece0d947
Binary files /dev/null and b/vector/src/main/res/drawable-mdpi/ill_app_layout_onboarding_rooms.webp differ
diff --git a/vector/src/main/res/drawable-mdpi/ill_app_layout_onboarding_spaces.webp b/vector/src/main/res/drawable-mdpi/ill_app_layout_onboarding_spaces.webp
new file mode 100644
index 0000000000..a77bce20e8
Binary files /dev/null and b/vector/src/main/res/drawable-mdpi/ill_app_layout_onboarding_spaces.webp differ
diff --git a/vector/src/main/res/drawable-mdpi/ill_empty_all_chats.webp b/vector/src/main/res/drawable-mdpi/ill_empty_all_chats.webp
new file mode 100644
index 0000000000..41c83c6b50
Binary files /dev/null and b/vector/src/main/res/drawable-mdpi/ill_empty_all_chats.webp differ
diff --git a/vector/src/main/res/drawable-mdpi/ill_empty_space.webp b/vector/src/main/res/drawable-mdpi/ill_empty_space.webp
new file mode 100644
index 0000000000..379bf44b63
Binary files /dev/null and b/vector/src/main/res/drawable-mdpi/ill_empty_space.webp differ
diff --git a/vector/src/main/res/drawable-mdpi/ill_empty_unreads.webp b/vector/src/main/res/drawable-mdpi/ill_empty_unreads.webp
new file mode 100644
index 0000000000..4209c0591d
Binary files /dev/null and b/vector/src/main/res/drawable-mdpi/ill_empty_unreads.webp differ
diff --git a/vector/src/main/res/drawable-xhdpi/ill_app_layout_onboarding_rooms.webp b/vector/src/main/res/drawable-xhdpi/ill_app_layout_onboarding_rooms.webp
new file mode 100644
index 0000000000..e86bd5fe6b
Binary files /dev/null and b/vector/src/main/res/drawable-xhdpi/ill_app_layout_onboarding_rooms.webp differ
diff --git a/vector/src/main/res/drawable-xhdpi/ill_app_layout_onboarding_spaces.webp b/vector/src/main/res/drawable-xhdpi/ill_app_layout_onboarding_spaces.webp
new file mode 100644
index 0000000000..9b5b9fa607
Binary files /dev/null and b/vector/src/main/res/drawable-xhdpi/ill_app_layout_onboarding_spaces.webp differ
diff --git a/vector/src/main/res/drawable-xhdpi/ill_empty_all_chats.webp b/vector/src/main/res/drawable-xhdpi/ill_empty_all_chats.webp
new file mode 100644
index 0000000000..c8374d1160
Binary files /dev/null and b/vector/src/main/res/drawable-xhdpi/ill_empty_all_chats.webp differ
diff --git a/vector/src/main/res/drawable-xhdpi/ill_empty_space.webp b/vector/src/main/res/drawable-xhdpi/ill_empty_space.webp
new file mode 100644
index 0000000000..c6d83f16c7
Binary files /dev/null and b/vector/src/main/res/drawable-xhdpi/ill_empty_space.webp differ
diff --git a/vector/src/main/res/drawable-xhdpi/ill_empty_unreads.webp b/vector/src/main/res/drawable-xhdpi/ill_empty_unreads.webp
new file mode 100644
index 0000000000..14d2dbdf9a
Binary files /dev/null and b/vector/src/main/res/drawable-xhdpi/ill_empty_unreads.webp differ
diff --git a/vector/src/main/res/drawable-xxhdpi/ill_app_layout_onboarding_rooms.webp b/vector/src/main/res/drawable-xxhdpi/ill_app_layout_onboarding_rooms.webp
new file mode 100644
index 0000000000..f95909eaae
Binary files /dev/null and b/vector/src/main/res/drawable-xxhdpi/ill_app_layout_onboarding_rooms.webp differ
diff --git a/vector/src/main/res/drawable-xxhdpi/ill_app_layout_onboarding_spaces.webp b/vector/src/main/res/drawable-xxhdpi/ill_app_layout_onboarding_spaces.webp
new file mode 100644
index 0000000000..03e31dc9d6
Binary files /dev/null and b/vector/src/main/res/drawable-xxhdpi/ill_app_layout_onboarding_spaces.webp differ
diff --git a/vector/src/main/res/drawable-xxhdpi/ill_empty_all_chats.webp b/vector/src/main/res/drawable-xxhdpi/ill_empty_all_chats.webp
new file mode 100644
index 0000000000..fc19311faf
Binary files /dev/null and b/vector/src/main/res/drawable-xxhdpi/ill_empty_all_chats.webp differ
diff --git a/vector/src/main/res/drawable-xxhdpi/ill_empty_space.webp b/vector/src/main/res/drawable-xxhdpi/ill_empty_space.webp
new file mode 100644
index 0000000000..18b26b82ff
Binary files /dev/null and b/vector/src/main/res/drawable-xxhdpi/ill_empty_space.webp differ
diff --git a/vector/src/main/res/drawable-xxhdpi/ill_empty_unreads.webp b/vector/src/main/res/drawable-xxhdpi/ill_empty_unreads.webp
new file mode 100644
index 0000000000..17127018ba
Binary files /dev/null and b/vector/src/main/res/drawable-xxhdpi/ill_empty_unreads.webp differ
diff --git a/vector/src/main/res/drawable-xxxhdpi/ill_app_layout_onboarding_rooms.webp b/vector/src/main/res/drawable-xxxhdpi/ill_app_layout_onboarding_rooms.webp
new file mode 100644
index 0000000000..cee3cf512c
Binary files /dev/null and b/vector/src/main/res/drawable-xxxhdpi/ill_app_layout_onboarding_rooms.webp differ
diff --git a/vector/src/main/res/drawable-xxxhdpi/ill_app_layout_onboarding_spaces.webp b/vector/src/main/res/drawable-xxxhdpi/ill_app_layout_onboarding_spaces.webp
new file mode 100644
index 0000000000..b4bf421be5
Binary files /dev/null and b/vector/src/main/res/drawable-xxxhdpi/ill_app_layout_onboarding_spaces.webp differ
diff --git a/vector/src/main/res/drawable-xxxhdpi/ill_empty_all_chats.webp b/vector/src/main/res/drawable-xxxhdpi/ill_empty_all_chats.webp
new file mode 100644
index 0000000000..e020c33543
Binary files /dev/null and b/vector/src/main/res/drawable-xxxhdpi/ill_empty_all_chats.webp differ
diff --git a/vector/src/main/res/drawable-xxxhdpi/ill_empty_space.webp b/vector/src/main/res/drawable-xxxhdpi/ill_empty_space.webp
new file mode 100644
index 0000000000..2c11bbafa0
Binary files /dev/null and b/vector/src/main/res/drawable-xxxhdpi/ill_empty_space.webp differ
diff --git a/vector/src/main/res/drawable-xxxhdpi/ill_empty_unreads.webp b/vector/src/main/res/drawable-xxxhdpi/ill_empty_unreads.webp
new file mode 100644
index 0000000000..278fbfac0b
Binary files /dev/null and b/vector/src/main/res/drawable-xxxhdpi/ill_empty_unreads.webp differ
diff --git a/vector/src/main/res/drawable/ic_invites_empty.xml b/vector/src/main/res/drawable/ic_invites_empty.xml
new file mode 100644
index 0000000000..79908ff380
--- /dev/null
+++ b/vector/src/main/res/drawable/ic_invites_empty.xml
@@ -0,0 +1,14 @@
+
+
+
+
diff --git a/vector/src/main/res/layout/activity_home.xml b/vector/src/main/res/layout/activity_home.xml
index 9899c15aa6..698aab2340 100644
--- a/vector/src/main/res/layout/activity_home.xml
+++ b/vector/src/main/res/layout/activity_home.xml
@@ -28,4 +28,4 @@
android:layout_height="match_parent"
android:layout_gravity="start" />
-
\ No newline at end of file
+
diff --git a/vector/src/main/res/layout/bottom_sheet_release_notes.xml b/vector/src/main/res/layout/bottom_sheet_release_notes.xml
new file mode 100644
index 0000000000..1d14c2c4a2
--- /dev/null
+++ b/vector/src/main/res/layout/bottom_sheet_release_notes.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vector/src/main/res/layout/fragment_invites.xml b/vector/src/main/res/layout/fragment_invites.xml
index 74226357c9..070cad5ec8 100644
--- a/vector/src/main/res/layout/fragment_invites.xml
+++ b/vector/src/main/res/layout/fragment_invites.xml
@@ -20,17 +20,24 @@
-
+ app:layout_constraintTop_toBottomOf="@id/appBarLayout">
+
+
+
+
+
diff --git a/vector/src/main/res/layout/fragment_new_home_detail.xml b/vector/src/main/res/layout/fragment_new_home_detail.xml
index 82bc6bb36e..7800752e75 100644
--- a/vector/src/main/res/layout/fragment_new_home_detail.xml
+++ b/vector/src/main/res/layout/fragment_new_home_detail.xml
@@ -39,7 +39,7 @@
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintTop_toBottomOf="@id/currentCallsView">
+ app:layout_constraintTop_toBottomOf="@id/syncStateView">
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vector/src/main/res/layout/item_invites_count.xml b/vector/src/main/res/layout/item_invites_count.xml
index 8a5c2b1222..8ec4eb0279 100644
--- a/vector/src/main/res/layout/item_invites_count.xml
+++ b/vector/src/main/res/layout/item_invites_count.xml
@@ -50,7 +50,7 @@
android:layout_height="1dp"
android:layout_marginStart="22dp"
android:layout_marginEnd="16dp"
- android:background="?vctr_list_separator_system"
+ android:background="?vctr_list_separator"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
diff --git a/vector/src/main/res/layout/item_recent_room.xml b/vector/src/main/res/layout/item_recent_room.xml
index 8e17707ff3..2cd8ff49c5 100644
--- a/vector/src/main/res/layout/item_recent_room.xml
+++ b/vector/src/main/res/layout/item_recent_room.xml
@@ -5,7 +5,7 @@
android:id="@+id/recentRoot"
android:layout_width="60dp"
android:layout_height="wrap_content"
- android:background="?android:colorBackground"
+ android:background="?vctr_toolbar_background"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground"
@@ -50,6 +50,7 @@
android:layout_marginTop="4dp"
android:layout_marginBottom="16dp"
android:ellipsize="end"
+ android:importantForAccessibility="no"
android:lines="1"
android:textColor="?vctr_content_primary"
app:layout_constraintBottom_toBottomOf="parent"
diff --git a/vector/src/main/res/layout/item_release_carousel.xml b/vector/src/main/res/layout/item_release_carousel.xml
new file mode 100644
index 0000000000..eeb999b187
--- /dev/null
+++ b/vector/src/main/res/layout/item_release_carousel.xml
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vector/src/main/res/layout/item_state_view.xml b/vector/src/main/res/layout/item_state_view.xml
new file mode 100644
index 0000000000..3cf2e3e6d2
--- /dev/null
+++ b/vector/src/main/res/layout/item_state_view.xml
@@ -0,0 +1,8 @@
+
+
+
+
diff --git a/vector/src/main/res/res/drawable-hdpi/app_layout_onboarding_feedback.webp b/vector/src/main/res/res/drawable-hdpi/app_layout_onboarding_feedback.webp
new file mode 100644
index 0000000000..5ac890e617
Binary files /dev/null and b/vector/src/main/res/res/drawable-hdpi/app_layout_onboarding_feedback.webp differ
diff --git a/vector/src/main/res/res/drawable-hdpi/app_layout_onboarding_spaces.webp b/vector/src/main/res/res/drawable-hdpi/app_layout_onboarding_spaces.webp
new file mode 100644
index 0000000000..35f2a04236
Binary files /dev/null and b/vector/src/main/res/res/drawable-hdpi/app_layout_onboarding_spaces.webp differ
diff --git a/vector/src/main/res/res/drawable-hdpi/app_layout_onboarding_welcome.webp b/vector/src/main/res/res/drawable-hdpi/app_layout_onboarding_welcome.webp
new file mode 100644
index 0000000000..5ac890e617
Binary files /dev/null and b/vector/src/main/res/res/drawable-hdpi/app_layout_onboarding_welcome.webp differ
diff --git a/vector/src/main/res/res/drawable-mdpi/app_layout_onboarding_feedback.webp b/vector/src/main/res/res/drawable-mdpi/app_layout_onboarding_feedback.webp
new file mode 100644
index 0000000000..07ece0d947
Binary files /dev/null and b/vector/src/main/res/res/drawable-mdpi/app_layout_onboarding_feedback.webp differ
diff --git a/vector/src/main/res/res/drawable-mdpi/app_layout_onboarding_spaces.webp b/vector/src/main/res/res/drawable-mdpi/app_layout_onboarding_spaces.webp
new file mode 100644
index 0000000000..a77bce20e8
Binary files /dev/null and b/vector/src/main/res/res/drawable-mdpi/app_layout_onboarding_spaces.webp differ
diff --git a/vector/src/main/res/res/drawable-mdpi/app_layout_onboarding_welcome.webp b/vector/src/main/res/res/drawable-mdpi/app_layout_onboarding_welcome.webp
new file mode 100644
index 0000000000..07ece0d947
Binary files /dev/null and b/vector/src/main/res/res/drawable-mdpi/app_layout_onboarding_welcome.webp differ
diff --git a/vector/src/main/res/res/drawable-xhdpi/app_layout_onboarding_feedback.webp b/vector/src/main/res/res/drawable-xhdpi/app_layout_onboarding_feedback.webp
new file mode 100644
index 0000000000..e86bd5fe6b
Binary files /dev/null and b/vector/src/main/res/res/drawable-xhdpi/app_layout_onboarding_feedback.webp differ
diff --git a/vector/src/main/res/res/drawable-xhdpi/app_layout_onboarding_spaces.webp b/vector/src/main/res/res/drawable-xhdpi/app_layout_onboarding_spaces.webp
new file mode 100644
index 0000000000..9b5b9fa607
Binary files /dev/null and b/vector/src/main/res/res/drawable-xhdpi/app_layout_onboarding_spaces.webp differ
diff --git a/vector/src/main/res/res/drawable-xhdpi/app_layout_onboarding_welcome.webp b/vector/src/main/res/res/drawable-xhdpi/app_layout_onboarding_welcome.webp
new file mode 100644
index 0000000000..e86bd5fe6b
Binary files /dev/null and b/vector/src/main/res/res/drawable-xhdpi/app_layout_onboarding_welcome.webp differ
diff --git a/vector/src/main/res/res/drawable-xxhdpi/app_layout_onboarding_feedback.webp b/vector/src/main/res/res/drawable-xxhdpi/app_layout_onboarding_feedback.webp
new file mode 100644
index 0000000000..f95909eaae
Binary files /dev/null and b/vector/src/main/res/res/drawable-xxhdpi/app_layout_onboarding_feedback.webp differ
diff --git a/vector/src/main/res/res/drawable-xxhdpi/app_layout_onboarding_spaces.webp b/vector/src/main/res/res/drawable-xxhdpi/app_layout_onboarding_spaces.webp
new file mode 100644
index 0000000000..03e31dc9d6
Binary files /dev/null and b/vector/src/main/res/res/drawable-xxhdpi/app_layout_onboarding_spaces.webp differ
diff --git a/vector/src/main/res/res/drawable-xxhdpi/app_layout_onboarding_welcome.webp b/vector/src/main/res/res/drawable-xxhdpi/app_layout_onboarding_welcome.webp
new file mode 100644
index 0000000000..f95909eaae
Binary files /dev/null and b/vector/src/main/res/res/drawable-xxhdpi/app_layout_onboarding_welcome.webp differ
diff --git a/vector/src/main/res/res/drawable-xxxhdpi/app_layout_onboarding_feedback.webp b/vector/src/main/res/res/drawable-xxxhdpi/app_layout_onboarding_feedback.webp
new file mode 100644
index 0000000000..cee3cf512c
Binary files /dev/null and b/vector/src/main/res/res/drawable-xxxhdpi/app_layout_onboarding_feedback.webp differ
diff --git a/vector/src/main/res/res/drawable-xxxhdpi/app_layout_onboarding_spaces.webp b/vector/src/main/res/res/drawable-xxxhdpi/app_layout_onboarding_spaces.webp
new file mode 100644
index 0000000000..b4bf421be5
Binary files /dev/null and b/vector/src/main/res/res/drawable-xxxhdpi/app_layout_onboarding_spaces.webp differ
diff --git a/vector/src/main/res/res/drawable-xxxhdpi/app_layout_onboarding_welcome.webp b/vector/src/main/res/res/drawable-xxxhdpi/app_layout_onboarding_welcome.webp
new file mode 100644
index 0000000000..cee3cf512c
Binary files /dev/null and b/vector/src/main/res/res/drawable-xxxhdpi/app_layout_onboarding_welcome.webp differ
diff --git a/vector/src/main/res/xml/vector_settings_preferences.xml b/vector/src/main/res/xml/vector_settings_preferences.xml
index a3b8a3476c..172fa5606c 100644
--- a/vector/src/main/res/xml/vector_settings_preferences.xml
+++ b/vector/src/main/res/xml/vector_settings_preferences.xml
@@ -30,7 +30,9 @@
-
+