Merge branch 'feature/mna/session-overview-screen' into feature/ons/device_manager_filter
* feature/mna/session-overview-screen: (57 commits) Fix missing mapper in CryptoStoreHelper for tests Fix unused string warning Update unit tests Rendering inactive status in SessionInfoView Adding comment with examples of some parametrized strings Fix post rebase Fixing wrong copyright title Adding last seen details + fix observation of wrong deviceId in ViewModel Adding learn more link in verification status details Unit tests for computing trust level of device Unit tests for GetCurrentSessionCrossSigningInfoUseCase Updating existing unit tests Navigation from other session item Show info in overview screen Renaming CurrentSessionView into SessionInfoView to be more generic Introducing some reusable usecases Adding unit tests for viewModel Adding unit tests for mapper Adding unit tests for the new use case Adding use case to get full device info for a given device id ... # Conflicts: # library/ui-strings/src/main/res/values/strings.xml # vector/src/main/AndroidManifest.xml # vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt # vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt # vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesViewNavigator.kt
26
.github/workflows/triage-labelled.yml
vendored
|
@ -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
|
||||
|
|
1
changelog.d/6646.misc
Normal file
|
@ -0,0 +1 @@
|
|||
[App Layout] Obsolete settings are not shown when App Layout flag is enabled
|
1
changelog.d/6754.bugfix
Normal file
|
@ -0,0 +1 @@
|
|||
[App Layout] - space switcher now has empty state
|
1
changelog.d/6835.feature
Normal file
|
@ -0,0 +1 @@
|
|||
[App Layout] New empty states for home screen
|
1
changelog.d/6876.feature
Normal file
|
@ -0,0 +1 @@
|
|||
[App Layout] - Invites now show empty screen after you reject last invite
|
1
changelog.d/6989.bugfix
Normal file
|
@ -0,0 +1 @@
|
|||
Catch race condition crash in voice recording
|
1
changelog.d/7010.feature
Normal file
|
@ -0,0 +1 @@
|
|||
Try to detect devices that lack Opus encoder support, use bundled libopus library for those.
|
1
changelog.d/7016.wip
Normal file
|
@ -0,0 +1 @@
|
|||
[New Layout] Improves talkback accessibility
|
|
@ -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
|
||||
```
|
||||
|
||||
|
|
|
@ -140,8 +140,10 @@
|
|||
<string name="start_chat">Start Chat</string>
|
||||
<string name="create_room">Create Room</string>
|
||||
<string name="explore_rooms">Explore Rooms</string>
|
||||
<string name="a11y_expand_space_children">Expand space children</string>
|
||||
<string name="a11y_collapse_space_children">Collapse space children</string>
|
||||
<!-- Note to translators: %s refers to the space whose children is being expanded -->
|
||||
<string name="a11y_expand_space_children">Expand %s children</string>
|
||||
<!-- Note to translators: %s refers to the space whose children is being collapsed -->
|
||||
<string name="a11y_collapse_space_children">Collapse %s children</string>
|
||||
|
||||
<!-- Last seen time -->
|
||||
|
||||
|
@ -442,9 +444,16 @@
|
|||
<string name="system_alerts_header">"System Alerts"</string>
|
||||
<string name="suggested_header">Suggested Rooms</string>
|
||||
|
||||
<!-- Space List fragment -->
|
||||
<string name="space_list_empty_title">No spaces yet.</string>
|
||||
<string name="space_list_empty_message">Spaces are a new way to group rooms and people. Create a space to get started.</string>
|
||||
|
||||
<!-- Invites fragment -->
|
||||
<string name="invites_title">Invites</string>
|
||||
|
||||
<string name="invites_empty_title">Nothing new.</string>
|
||||
<string name="invites_empty_message">This is where your new requests and invites will be.</string>
|
||||
|
||||
<!-- People fragment -->
|
||||
<string name="direct_chats_header">Conversations</string>
|
||||
<string name="matrix_only_filter">Matrix contacts only</string>
|
||||
|
@ -3252,6 +3261,27 @@
|
|||
<string name="device_manager_session_title">Session</string>
|
||||
<!-- Examples: Last activity Yesterday at 6PM, Last activity Aug 31 at 5:47PM -->
|
||||
<string name="device_manager_session_last_activity">Last activity %1$s</string>
|
||||
<!-- Note to translators: %s will be replaces with selected space name -->
|
||||
<string name="home_empty_space_no_rooms_title">%s\nis looking a little empty.</string>
|
||||
<!-- Note to translators: for RTL languages, Spaces will be at the bottom left. Please translate "bottom-left" instead of "bottom-right". Thanks!-->
|
||||
<string name="home_empty_space_no_rooms_message">Spaces are a new way to group rooms and people. Add an existing room, or create a new one, using the bottom-right button.</string>
|
||||
<!-- Note to translators: %s will be replaces with current user displayname -->
|
||||
<string name="home_empty_no_rooms_title">Welcome to ${app_name},\n%s.</string>
|
||||
<string name="home_empty_no_rooms_message">The all-in-one secure chat app for teams, friends and organisations. Create a chat, or join an existing room, to get started.</string>
|
||||
<string name="home_empty_no_unreads_title">Nothing to report.</string>
|
||||
<string name="home_empty_no_unreads_message">This is where your unread messages will show up, when you have some.</string>
|
||||
|
||||
<string name="onboarding_new_app_layout_welcome_title">Welcome to a new view!</string>
|
||||
<!-- Note to translators: for RTL languages, menu will be at the bottom left. Please translate "bottom-left" instead of "bottom-right". Thanks!-->
|
||||
<string name="onboarding_new_app_layout_welcome_message">To simplify your ${app_name}, tabs are now optional. Manage them using the top-right menu.</string>
|
||||
<string name="onboarding_new_app_layout_spaces_title">Access Spaces</string>
|
||||
<!-- Note to translators: for RTL languages, Spaces will be at the bottom left. Please translate "bottom-left" instead of "bottom-right". Thanks!-->
|
||||
<string name="onboarding_new_app_layout_spaces_message">Access your Spaces (bottom-right) faster and easier than ever before.</string>
|
||||
<string name="onboarding_new_app_layout_feedback_title">Give Feedback</string>
|
||||
<!-- Note to translators: for RTL languages, context menu will be at top left corner instead of top right corner. Thanks!-->
|
||||
<string name="onboarding_new_app_layout_feedback_message">Tap top right to see the option to feedback.</string>
|
||||
<string name="onboarding_new_app_layout_button_try">Try it out</string>
|
||||
|
||||
<string name="device_manager_filter_bottom_sheet_title">Filter</string>
|
||||
<string name="device_manager_filter_option_all_sessions">All session</string>
|
||||
<string name="device_manager_filter_option_verified">Verified</string>
|
||||
|
|
|
@ -2,4 +2,8 @@
|
|||
<resources>
|
||||
<item name="ftue_auth_carousel_item_spacing" format="float" type="dimen">0.05</item>
|
||||
<item name="ftue_auth_carousel_item_image_height" format="float" type="dimen">0.40</item>
|
||||
</resources>
|
||||
|
||||
<dimen name="release_notes_vertical_margin_small">16dp</dimen>
|
||||
<dimen name="release_notes_vertical_margin">40dp</dimen>
|
||||
<dimen name="release_notes_vertical_margin_large">46dp</dimen>
|
||||
</resources>
|
||||
|
|
|
@ -74,4 +74,9 @@
|
|||
|
||||
<!-- Material 3 -->
|
||||
<dimen name="collapsing_toolbar_layout_medium_size">112dp</dimen>
|
||||
|
||||
|
||||
<dimen name="release_notes_vertical_margin_small">8dp</dimen>
|
||||
<dimen name="release_notes_vertical_margin">16dp</dimen>
|
||||
<dimen name="release_notes_vertical_margin_large">28dp</dimen>
|
||||
</resources>
|
||||
|
|
|
@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -340,10 +340,6 @@ android {
|
|||
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
|
||||
]
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
viewBinding true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<application android:name="im.vector.app.VectorApplication">
|
||||
<application>
|
||||
|
||||
<!-- Firebase components -->
|
||||
<meta-data
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
package="im.vector.application">
|
||||
|
||||
<application
|
||||
android:name="im.vector.app.VectorApplication"
|
||||
android:allowBackup="false"
|
||||
android:hasFragileUserData="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
|
|
|
@ -78,17 +78,10 @@ android {
|
|||
productFlavors {
|
||||
gplay {
|
||||
dimension "store"
|
||||
isDefault = true
|
||||
|
||||
buildConfigField "String", "SHORT_FLAVOR_DESCRIPTION", "\"G\""
|
||||
buildConfigField "String", "FLAVOR_DESCRIPTION", "\"GooglePlay\""
|
||||
}
|
||||
|
||||
fdroid {
|
||||
dimension "store"
|
||||
|
||||
buildConfigField "String", "SHORT_FLAVOR_DESCRIPTION", "\"F\""
|
||||
buildConfigField "String", "FLAVOR_DESCRIPTION", "\"FDroid\""
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -264,7 +257,7 @@ dependencies {
|
|||
// UnifiedPush
|
||||
implementation 'com.github.UnifiedPush:android-connector:2.0.1'
|
||||
// UnifiedPush gplay flavor only
|
||||
gplayImplementation('com.github.UnifiedPush:android-embedded_fcm_distributor:2.1.2') {
|
||||
gplayImplementation('com.github.UnifiedPush:android-embedded_fcm_distributor:2.1.3') {
|
||||
exclude group: 'com.google.firebase', module: 'firebase-core'
|
||||
exclude group: 'com.google.firebase', module: 'firebase-analytics'
|
||||
exclude group: 'com.google.firebase', module: 'firebase-measurement-connector'
|
||||
|
|
|
@ -20,6 +20,8 @@ import android.os.Build
|
|||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import im.vector.app.AndroidVersionTestOverrider
|
||||
import im.vector.app.features.DefaultVectorFeatures
|
||||
import io.mockk.every
|
||||
import io.mockk.spyk
|
||||
import org.amshove.kluent.shouldBeInstanceOf
|
||||
import org.junit.After
|
||||
import org.junit.Test
|
||||
|
@ -27,7 +29,7 @@ import org.junit.Test
|
|||
class VoiceRecorderProviderTests {
|
||||
|
||||
private val context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
private val provider = VoiceRecorderProvider(context, DefaultVectorFeatures())
|
||||
private val provider = spyk(VoiceRecorderProvider(context, DefaultVectorFeatures()))
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
|
@ -35,11 +37,19 @@ class VoiceRecorderProviderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun provideVoiceRecorderOnAndroidQReturnsQRecorder() {
|
||||
fun provideVoiceRecorderOnAndroidQAndCodecReturnsQRecorder() {
|
||||
AndroidVersionTestOverrider.override(Build.VERSION_CODES.Q)
|
||||
every { provider.hasOpusEncoder() } returns true
|
||||
provider.provideVoiceRecorder().shouldBeInstanceOf(VoiceRecorderQ::class)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun provideVoiceRecorderOnAndroidQButNoCodecReturnsLRecorder() {
|
||||
AndroidVersionTestOverrider.override(Build.VERSION_CODES.Q)
|
||||
every { provider.hasOpusEncoder() } returns false
|
||||
provider.provideVoiceRecorder().shouldBeInstanceOf(VoiceRecorderL::class)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun provideVoiceRecorderOnOlderAndroidVersionReturnsLRecorder() {
|
||||
AndroidVersionTestOverrider.override(Build.VERSION_CODES.LOLLIPOP)
|
||||
|
|
|
@ -338,6 +338,7 @@
|
|||
<activity android:name=".features.settings.font.FontScaleSettingActivity"/>
|
||||
<activity android:name=".features.call.dialpad.PstnDialActivity" />
|
||||
<activity android:name=".features.home.room.list.home.invites.InvitesActivity"/>
|
||||
<activity android:name=".features.home.room.list.home.release.ReleaseNotesActivity"/>
|
||||
<activity android:name=".features.settings.devices.v2.overview.SessionOverviewActivity"/>
|
||||
<activity android:name=".features.settings.devices.v2.othersessions.OtherSessionsActivity" />
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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<String, Any>? {
|
||||
return mutableMapOf<String, Any>().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) }
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<HomeActivityViewState, HomeActivityViewActions, HomeActivityViewEvents>(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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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<Int>()
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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<HomeRoomListViewState, HomeRoomListAction, HomeRoomListViewEvents>(initialState) {
|
||||
|
||||
@AssistedFactory
|
||||
|
@ -82,6 +90,10 @@ class HomeRoomListViewModel @AssistedInject constructor(
|
|||
private val _sections = MutableSharedFlow<Set<HomeRoomSection>>(replay = 1)
|
||||
val sections = _sections.asSharedFlow()
|
||||
|
||||
private var currentFilter: HomeRoomFilter = HomeRoomFilter.ALL
|
||||
private val _emptyStateFlow = MutableSharedFlow<Optional<StateView.State.Empty>>(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<Optional<List<HomeRoomFilter>>> {
|
||||
val flow = MutableSharedFlow<Optional<List<HomeRoomFilter>>>(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 {
|
||||
|
|
|
@ -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<RoomListEmptyItem.Holder>(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<StateView>(R.id.stateView)
|
||||
}
|
||||
}
|
|
@ -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<HomeRoomFilter>? = null
|
||||
private var emptyStateData: StateView.State.Empty? = null
|
||||
private var currentState: StateView.State = StateView.State.Content
|
||||
|
||||
override fun addModels(models: List<EpoxyModel<*>>) {
|
||||
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<HomeRoomFilter>?) {
|
||||
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)
|
||||
|
|
|
@ -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<FragmentInvitesBinding>(), RoomListLi
|
|||
setupToolbar(views.invitesToolbar)
|
||||
.allowBack()
|
||||
|
||||
views.invitesStateView.contentView = views.invitesRecycler
|
||||
|
||||
views.invitesRecycler.configureWith(controller)
|
||||
controller.listener = this
|
||||
|
||||
|
@ -62,13 +67,31 @@ class InvitesFragment : VectorBaseFragment<FragmentInvitesBinding>(), 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<FragmentInvitesBinding>(), 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))
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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<InvitesViewState, InvitesAction, InvitesViewEvents>(initialState) {
|
||||
|
||||
private val pagedListConfig = PagedList.Config.Builder()
|
||||
|
@ -52,6 +65,11 @@ class InvitesViewModel @AssistedInject constructor(
|
|||
|
||||
companion object : MavericksViewModelFactory<InvitesViewModel, InvitesViewState> by hiltMavericksViewModelFactory()
|
||||
|
||||
private val _invites = MutableSharedFlow<InvitesContentState>(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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<PagedList<RoomSummary>>? = null,
|
||||
val roomMembershipChanges: Map<String, ChangeMembershipState> = 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<RoomSummary>) : InvitesContentState
|
||||
data class Error(val throwable: Throwable) : InvitesContentState
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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<Item>
|
||||
) {
|
||||
data class Item(
|
||||
@StringRes val title: Int,
|
||||
@StringRes val body: Int,
|
||||
@DrawableRes val image: Int,
|
||||
)
|
||||
}
|
|
@ -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<ReleaseCarouselItem.Holder>(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<ImageView>(R.id.carousel_item_image)
|
||||
val title by bind<TextView>(R.id.carousel_item_title)
|
||||
val body by bind<TextView>(R.id.carousel_item_body)
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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<ActivitySimpleBinding>() {
|
||||
|
||||
@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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<ReleaseCarouselData>() {
|
||||
override fun buildModels(data: ReleaseCarouselData) {
|
||||
data.items.forEachIndexed { index, item ->
|
||||
releaseCarouselItem {
|
||||
id(index)
|
||||
item(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<BottomSheetReleaseNotesBinding>() {
|
||||
|
||||
@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()
|
||||
}
|
||||
}
|
|
@ -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<Preferences> 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<Boolean> = context.dataStore.data
|
||||
.map { preferences -> preferences[isAppLayoutOnboardingShown].orFalse() }
|
||||
.distinctUntilChanged()
|
||||
|
||||
suspend fun setAppLayoutOnboardingShown(isShown: Boolean) {
|
||||
context.dataStore.edit { settings ->
|
||||
settings[isAppLayoutOnboardingShown] = isShown
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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<VectorDummyViewState, ReleaseNotesAction, ReleaseNotesViewEvents>(initialState) {
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : MavericksAssistedViewModelFactory<ReleaseNotesViewModel, VectorDummyViewState> {
|
||||
override fun create(initialState: VectorDummyViewState): ReleaseNotesViewModel
|
||||
}
|
||||
|
||||
companion object : MavericksViewModelFactory<ReleaseNotesViewModel, VectorDummyViewState> 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))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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<VectorSwitchPreference>(VectorPreferences.SETTINGS_LABS_UNREAD_NOTIFICATIONS_AS_TAB)!!.let {
|
||||
it.isVisible = !vectorFeatures.isNewAppLayoutEnabled()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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<Preference>(VectorPreferences.SETTINGS_PREF_SPACE_CATEGORY)!!.let { pref ->
|
||||
pref.isVisible = !vectorFeatures.isNewAppLayoutEnabled()
|
||||
}
|
||||
|
||||
// Url preview
|
||||
/*
|
||||
TODO Note: we keep the setting client side for now
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,7 +58,10 @@ abstract class NewSpaceSummaryItem : VectorEpoxyModel<NewSpaceSummaryItem.Holder
|
|||
holder.chevron.setOnClickListener(onToggleExpandListener)
|
||||
holder.chevron.isVisible = hasChildren
|
||||
holder.chevron.setImageResource(if (expanded) R.drawable.ic_expand_more else R.drawable.ic_arrow_right)
|
||||
holder.chevron.contentDescription = context.getString(if (expanded) R.string.a11y_collapse_space_children else R.string.a11y_expand_space_children)
|
||||
holder.chevron.contentDescription = context.getString(
|
||||
if (expanded) R.string.a11y_collapse_space_children else R.string.a11y_expand_space_children,
|
||||
matrixItem.displayName,
|
||||
)
|
||||
|
||||
avatarRenderer.render(matrixItem, holder.avatar)
|
||||
holder.unreadCounter.render(countState)
|
||||
|
|
|
@ -50,6 +50,7 @@ abstract class NewSubSpaceSummaryItem : VectorEpoxyModel<NewSubSpaceSummaryItem.
|
|||
|
||||
override fun bind(holder: Holder) {
|
||||
super.bind(holder)
|
||||
val context = holder.root.context
|
||||
holder.root.onClick(onSubSpaceSelectedListener)
|
||||
holder.name.text = matrixItem.displayName
|
||||
holder.root.isChecked = selected
|
||||
|
@ -63,6 +64,10 @@ abstract class NewSubSpaceSummaryItem : VectorEpoxyModel<NewSubSpaceSummaryItem.
|
|||
)
|
||||
holder.chevron.onClick(onToggleExpandListener)
|
||||
holder.chevron.isVisible = hasChildren
|
||||
holder.chevron.contentDescription = context.getString(
|
||||
if (expanded) R.string.a11y_collapse_space_children else R.string.a11y_expand_space_children,
|
||||
matrixItem.displayName,
|
||||
)
|
||||
|
||||
holder.indent.isVisible = indent > 0
|
||||
holder.indent.updateLayoutParams {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 14 KiB |
BIN
vector/src/main/res/drawable-hdpi/ill_empty_all_chats.webp
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
vector/src/main/res/drawable-hdpi/ill_empty_space.webp
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
vector/src/main/res/drawable-hdpi/ill_empty_unreads.webp
Normal file
After Width: | Height: | Size: 794 B |
After Width: | Height: | Size: 5.3 KiB |
After Width: | Height: | Size: 7.2 KiB |
BIN
vector/src/main/res/drawable-mdpi/ill_empty_all_chats.webp
Normal file
After Width: | Height: | Size: 2.5 KiB |
BIN
vector/src/main/res/drawable-mdpi/ill_empty_space.webp
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
vector/src/main/res/drawable-mdpi/ill_empty_unreads.webp
Normal file
After Width: | Height: | Size: 556 B |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 19 KiB |
BIN
vector/src/main/res/drawable-xhdpi/ill_empty_all_chats.webp
Normal file
After Width: | Height: | Size: 5.2 KiB |
BIN
vector/src/main/res/drawable-xhdpi/ill_empty_space.webp
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
vector/src/main/res/drawable-xhdpi/ill_empty_unreads.webp
Normal file
After Width: | Height: | Size: 1 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 34 KiB |
BIN
vector/src/main/res/drawable-xxhdpi/ill_empty_all_chats.webp
Normal file
After Width: | Height: | Size: 8.7 KiB |
BIN
vector/src/main/res/drawable-xxhdpi/ill_empty_space.webp
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
vector/src/main/res/drawable-xxhdpi/ill_empty_unreads.webp
Normal file
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 32 KiB |
After Width: | Height: | Size: 52 KiB |
BIN
vector/src/main/res/drawable-xxxhdpi/ill_empty_all_chats.webp
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
vector/src/main/res/drawable-xxxhdpi/ill_empty_space.webp
Normal file
After Width: | Height: | Size: 6.2 KiB |
BIN
vector/src/main/res/drawable-xxxhdpi/ill_empty_unreads.webp
Normal file
After Width: | Height: | Size: 2 KiB |
14
vector/src/main/res/drawable/ic_invites_empty.xml
Normal file
|
@ -0,0 +1,14 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="60dp"
|
||||
android:height="60dp"
|
||||
android:viewportWidth="60"
|
||||
android:viewportHeight="60">
|
||||
<path
|
||||
android:pathData="M30,30m-30,0a30,30 0,1 1,60 0a30,30 0,1 1,-60 0"
|
||||
android:fillColor="#E3E8F0"/>
|
||||
<path
|
||||
android:pathData="M25.665,33.544L15.229,23.209L29.236,13.398C29.993,12.868 31.007,12.868 31.764,13.398L45.771,23.209L35.247,33.631L33.851,32.446C31.93,30.816 29.11,30.778 27.145,32.355L25.665,33.544ZM22.439,36.134L14,42.91V27.777L22.439,36.134ZM47,27.777V43.606L38.393,36.301L47,27.777ZM31.177,35.566L43.47,46H16.714L29.733,35.546C30.156,35.208 30.765,35.216 31.177,35.566Z"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#737D8C"
|
||||
android:strokeColor="#737D8C"/>
|
||||
</vector>
|
|
@ -28,4 +28,4 @@
|
|||
android:layout_height="match_parent"
|
||||
android:layout_gravity="start" />
|
||||
|
||||
</androidx.drawerlayout.widget.DrawerLayout>
|
||||
</androidx.drawerlayout.widget.DrawerLayout>
|
||||
|
|
55
vector/src/main/res/layout/bottom_sheet_release_notes.xml
Normal file
|
@ -0,0 +1,55 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="bottom"
|
||||
android:background="?colorSurface">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageButton
|
||||
android:id="@+id/release_notes_btn_close"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:background="@null"
|
||||
android:src="@drawable/ic_close_24dp"
|
||||
android:tint="?vctr_content_secondary"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"/>
|
||||
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/release_notes_carousel_indicator"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="36dp"
|
||||
android:layout_marginBottom="@dimen/release_notes_vertical_margin_small"
|
||||
android:background="@null"
|
||||
app:layout_constraintBottom_toTopOf="@id/releaseNotesButtonNext"
|
||||
app:tabBackground="@drawable/indicator_onboarding_carousel_selector"
|
||||
app:tabGravity="center"
|
||||
app:tabIndicatorHeight="0dp"
|
||||
app:tabPaddingEnd="8dp"
|
||||
app:tabPaddingStart="8dp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/releaseNotesButtonNext"
|
||||
style="@style/Widget.Vector.Button.Login"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="24dp"
|
||||
android:layout_marginBottom="@dimen/release_notes_vertical_margin"
|
||||
android:textAllCaps="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
tools:text="@string/action_next" />
|
||||
|
||||
<androidx.viewpager2.widget.ViewPager2
|
||||
android:id="@+id/release_notes_carousel"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="@dimen/release_notes_vertical_margin_small"
|
||||
android:layout_marginBottom="@dimen/release_notes_vertical_margin"
|
||||
app:layout_constraintBottom_toTopOf="@id/release_notes_carousel_indicator"
|
||||
app:layout_constraintTop_toBottomOf="@id/release_notes_btn_close" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -20,17 +20,24 @@
|
|||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/invites_recycler"
|
||||
<im.vector.app.core.platform.StateView
|
||||
android:id="@+id/invites_state_view"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:fastScrollEnabled="true"
|
||||
android:overScrollMode="always"
|
||||
android:scrollbars="vertical"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/appBarLayout" />
|
||||
app:layout_constraintTop_toBottomOf="@id/appBarLayout">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/invites_recycler"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fastScrollEnabled="true"
|
||||
android:overScrollMode="always"
|
||||
android:scrollbars="vertical" />
|
||||
|
||||
</im.vector.app.core.platform.StateView>
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
|
@ -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">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appBarLayout"
|
||||
|
@ -127,7 +127,6 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:accessibilityTraversalBefore="@id/roomListView"
|
||||
android:contentDescription="@string/a11y_create_message"
|
||||
android:src="@drawable/ic_new_chat"
|
||||
android:visibility="gone"
|
||||
|
|
|
@ -12,4 +12,48 @@
|
|||
android:overScrollMode="always"
|
||||
tools:listitem="@layout/item_space" />
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/spaces_empty_group"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/layout_horizontal_margin"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/spaces_empty_title"
|
||||
style="@style/Widget.Vector.TextView.Subtitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:gravity="center"
|
||||
android:text="@string/space_list_empty_title"
|
||||
android:textColor="?vctr_content_primary"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/spaces_empty_message"
|
||||
style="@style/Widget.Vector.TextView.Body"
|
||||
android:layout_width="220dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="20dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/space_list_empty_message"
|
||||
android:textColor="?vctr_content_secondary" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/spaces_empty_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="8dp"
|
||||
android:minWidth="190dp"
|
||||
android:text="@string/create_space" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</im.vector.app.core.platform.StateView>
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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"
|
||||
|
|
63
vector/src/main/res/layout/item_release_carousel.xml
Normal file
|
@ -0,0 +1,63 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="bottom">
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/splashCarouselGutterStart"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintGuide_percent="@dimen/ftue_auth_gutter_start_percent" />
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/splashCarouselGutterEnd"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintGuide_percent="@dimen/ftue_auth_gutter_end_percent" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/carousel_item_image"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:scaleType="centerInside"
|
||||
android:layout_marginBottom="@dimen/release_notes_vertical_margin_large"
|
||||
android:contentDescription="@null"
|
||||
app:layout_constraintStart_toStartOf="@id/splashCarouselGutterStart"
|
||||
app:layout_constraintEnd_toEndOf="@id/splashCarouselGutterStart"
|
||||
app:layout_constraintBottom_toTopOf="@id/carousel_item_title"
|
||||
tools:src="@drawable/ill_app_layout_onboarding_rooms"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/carousel_item_title"
|
||||
style="@style/Widget.Vector.TextView.Title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:gravity="center"
|
||||
android:textColor="?vctr_content_primary"
|
||||
android:maxLines="2"
|
||||
app:layout_constraintBottom_toTopOf="@id/carousel_item_body"
|
||||
app:layout_constraintEnd_toEndOf="@id/splashCarouselGutterEnd"
|
||||
app:layout_constraintStart_toStartOf="@id/splashCarouselGutterStart"
|
||||
tools:text="@string/onboarding_new_app_layout_welcome_title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/carousel_item_body"
|
||||
style="@style/Widget.Vector.TextView.Subtitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:textColor="?vctr_content_secondary"
|
||||
android:maxLines="3"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="@id/splashCarouselGutterEnd"
|
||||
app:layout_constraintStart_toStartOf="@id/splashCarouselGutterStart"
|
||||
tools:text="@string/onboarding_new_app_layout_welcome_message" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
8
vector/src/main/res/layout/item_state_view.xml
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<im.vector.app.core.platform.StateView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/stateView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:colorBackground">
|
||||
|
||||
</im.vector.app.core.platform.StateView>
|
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 5.3 KiB |
After Width: | Height: | Size: 7.2 KiB |