Compare commits

...

32 Commits

Author SHA1 Message Date
ericdecanini 936d643ebb Merge remote-tracking branch 'origin/feature/nfe/invites_empty_state' into app-layout-testing-06-09 2022-09-06 11:15:25 +02:00
ericdecanini 311fb84ca0 Merge remote-tracking branch 'origin/feature/nfe/app_layout_release_experience' into app-layout-testing-06-09 2022-09-06 11:15:02 +02:00
ericdecanini 1911f2417d Merge remote-tracking branch 'origin/feature/nfe/app_layout_all_screens_test' into app-layout-testing-06-09 2022-09-06 11:14:51 +02:00
ericdecanini 96477ef47b Merge remote-tracking branch 'origin/feature/nfe/app_layout_settings_update' into app-layout-testing-06-09 2022-09-06 11:14:40 +02:00
ericdecanini 6cea400637 Merge remote-tracking branch 'origin/feature/nfe/app_layout_hide_filters_for_space' into app-layout-testing-06-09 2022-09-06 11:14:30 +02:00
ericdecanini 3bfcc69a37 Merge branch 'bugfix/eric/new-layout-recents-padding' into app-layout-testing-06-09 2022-09-06 11:14:11 +02:00
NIkita Fedrunov e0c455a476 update according to code review 2022-09-06 10:08:32 +02:00
Germain 36a221adf1
Merge pull request #6998 from vector-im/gsouquet-threads-automation 2022-09-06 08:51:12 +01:00
NIkita Fedrunov ee300cf11d hide favourites and dms filters for a selected space, if they are empty 2022-09-06 07:54:16 +02:00
NIkita Fedrunov 891173c04a compilation error fixed 2022-09-06 07:18:45 +02:00
NIkita Fedrunov eac9371a07 fixed tests to expect selected auth state after auth 2022-09-06 07:07:19 +02:00
NIkita Fedrunov 1b651f2aa5 fixed lint 2022-09-06 06:59:10 +02:00
NIkita Fedrunov 9a29c79233 obsolete settings are hidden when app layout flag is enabled 2022-09-06 00:41:58 +02:00
NIkita Fedrunov 67b7bc3d83 fixed allscreens sanity test for enabled app layout flag 2022-09-06 00:00:35 +02:00
NIkita Fedrunov 61cf3c3125 review changes 2022-09-05 18:56:43 +02:00
NIkita Fedrunov 1afd10e686 removed duplicated illustrations + illustrations renamed to have ill prefix 2022-09-05 18:26:20 +02:00
NIkita Fedrunov 1153297576 some lint fixes 2022-09-05 17:42:54 +02:00
NIkita Fedrunov 6a9284eae3 flag change is set before posting action to ensure it's executed properly 2022-09-05 17:41:41 +02:00
NIkita Fedrunov d18f3cfe06 remvoved +id reference 2022-09-05 17:40:49 +02:00
Nikita Fedrunov 7515b77f9d
Update vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt
Co-authored-by: Benoit Marty <benoitm@matrix.org>
2022-09-05 17:11:40 +02:00
Nikita Fedrunov 744d212b86
Update library/ui-strings/src/main/res/values/strings.xml
Co-authored-by: Benoit Marty <benoitm@matrix.org>
2022-09-05 17:11:35 +02:00
NIkita Fedrunov ecd1776085 removed obsolete cast 2022-09-05 16:51:38 +02:00
NIkita Fedrunov fc265dd480 don't show release notes if app layout isn't enabled 2022-09-05 16:45:20 +02:00
NIkita Fedrunov 108e1b874c tools sources added for carousel item layout 2022-09-05 16:38:19 +02:00
NIkita Fedrunov e29687fa49 logs removed 2022-09-05 16:37:48 +02:00
NIkita Fedrunov d986fede72 rtl comment for string 2022-09-05 16:29:28 +02:00
NIkita Fedrunov f2309af4c6 fixed missing auth description + flag screen as shown after auth flow 2022-09-05 16:27:10 +02:00
NIkita Fedrunov 74fd14af8f review fixes 2022-09-05 15:56:42 +02:00
NIkita Fedrunov 8314eb71c0 Merge branch 'develop' into feature/nfe/invites_empty_state 2022-09-05 12:32:01 +02:00
NIkita Fedrunov f8ec268145 applayout release experience 2022-09-05 09:35:18 +02:00
Germain ad360074bf
Remove threads board automation
The threads board has been closed and is now replaced by the Delight board
2022-09-02 15:54:08 +01:00
NIkita Fedrunov 5003459962 empty state for new invites screen 2022-09-01 14:39:13 +02:00
69 changed files with 910 additions and 110 deletions

View File

@ -142,32 +142,6 @@ jobs:
env: env:
PROJECT_ID: "PN_kwDOAM0swc2KCw" PROJECT_ID: "PN_kwDOAM0swc2KCw"
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} 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: move_message_bubbles_issues:
name: A-Message-Bubbles to Message bubbles board name: A-Message-Bubbles to Message bubbles board
runs-on: ubuntu-latest runs-on: ubuntu-latest

1
changelog.d/6646.misc Normal file
View File

@ -0,0 +1 @@
[App Layout] Obsolete settings are not shown when App Layout flag is enabled

1
changelog.d/6876.feature Normal file
View File

@ -0,0 +1 @@
[App Layout] - Invites now show empty screen after you reject last invite

View File

@ -451,6 +451,9 @@
<!-- Invites fragment --> <!-- Invites fragment -->
<string name="invites_title">Invites</string> <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 --> <!-- People fragment -->
<string name="direct_chats_header">Conversations</string> <string name="direct_chats_header">Conversations</string>
<string name="matrix_only_filter">Matrix contacts only</string> <string name="matrix_only_filter">Matrix contacts only</string>
@ -3256,4 +3259,15 @@
<string name="home_empty_no_unreads_title">Nothing to report.</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="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>
</resources> </resources>

View File

@ -2,4 +2,8 @@
<resources> <resources>
<item name="ftue_auth_carousel_item_spacing" format="float" type="dimen">0.05</item> <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> <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>

View File

@ -74,4 +74,9 @@
<!-- Material 3 --> <!-- Material 3 -->
<dimen name="collapsing_toolbar_layout_medium_size">112dp</dimen> <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> </resources>

View File

@ -101,11 +101,11 @@ class UiAllScreensSanityTest {
val spaceName = UUID.randomUUID().toString() val spaceName = UUID.randomUUID().toString()
elementRobot.space { elementRobot.space {
createSpace { createSpace(true) {
createAndCrawl(spaceName) createAndCrawl(spaceName)
} }
val publicSpaceName = UUID.randomUUID().toString() val publicSpaceName = UUID.randomUUID().toString()
createSpace { createSpace(false) {
createPublicSpace(publicSpaceName) createPublicSpace(publicSpaceName)
} }

View File

@ -84,33 +84,56 @@ class ElementRobot {
} }
fun settings(shouldGoBack: Boolean = true, block: SettingsRobot.() -> Unit) { fun settings(shouldGoBack: Boolean = true, block: SettingsRobot.() -> Unit) {
openDrawer() if (features.isNewAppLayoutEnabled()) {
clickOn(R.id.homeDrawerHeaderSettingsView) onView(withId((R.id.avatar))).perform(click())
} else {
openDrawer()
clickOn(R.id.homeDrawerHeaderSettingsView)
}
block(SettingsRobot()) block(SettingsRobot())
if (shouldGoBack) pressBack() if (shouldGoBack) pressBack()
waitUntilViewVisible(withId(R.id.roomListContainer)) waitUntilViewVisible(withId(R.id.roomListContainer))
} }
fun newDirectMessage(block: NewDirectMessageRobot.() -> Unit) { fun newDirectMessage(block: NewDirectMessageRobot.() -> Unit) {
clickOn(R.id.bottom_action_people) if (features.isNewAppLayoutEnabled()) {
clickOn(R.id.createChatRoomButton) clickOn(R.id.newLayoutCreateChatButton)
waitUntilDialogVisible(withId(R.id.start_chat))
clickOn(R.id.start_chat)
} else {
clickOn(R.id.bottom_action_people)
clickOn(R.id.createChatRoomButton)
}
waitUntilActivityVisible<CreateDirectRoomActivity> { waitUntilActivityVisible<CreateDirectRoomActivity> {
waitUntilViewVisible(withId(R.id.userListSearch)) waitUntilViewVisible(withId(R.id.userListSearch))
} }
closeSoftKeyboard() closeSoftKeyboard()
block(NewDirectMessageRobot()) block(NewDirectMessageRobot())
pressBack() pressBack()
if (features.isNewAppLayoutEnabled()) {
pressBack() // close create dialog
}
waitUntilViewVisible(withId(R.id.roomListContainer)) waitUntilViewVisible(withId(R.id.roomListContainer))
} }
fun newRoom(block: NewRoomRobot.() -> Unit) { fun newRoom(block: NewRoomRobot.() -> Unit) {
clickOn(R.id.bottom_action_rooms) if (!features.isNewAppLayoutEnabled()) {
clickOn(R.id.bottom_action_rooms)
}
RoomListRobot().newRoom { block() } RoomListRobot().newRoom { block() }
if (features.isNewAppLayoutEnabled()) {
pressBack() // close create dialog
}
waitUntilViewVisible(withId(R.id.roomListContainer)) waitUntilViewVisible(withId(R.id.roomListContainer))
} }
fun roomList(block: RoomListRobot.() -> Unit) { fun roomList(block: RoomListRobot.() -> Unit) {
clickOn(R.id.bottom_action_rooms) if (!features.isNewAppLayoutEnabled()) {
clickOn(R.id.bottom_action_rooms)
}
block(RoomListRobot()) block(RoomListRobot())
waitUntilViewVisible(withId(R.id.roomListContainer)) waitUntilViewVisible(withId(R.id.roomListContainer))
} }

View File

@ -21,13 +21,19 @@ import androidx.test.espresso.matcher.ViewMatchers.withId
import com.adevinta.android.barista.interaction.BaristaClickInteractions.clickOn import com.adevinta.android.barista.interaction.BaristaClickInteractions.clickOn
import im.vector.app.R import im.vector.app.R
import im.vector.app.espresso.tools.waitUntilViewVisible import im.vector.app.espresso.tools.waitUntilViewVisible
import im.vector.app.features.DefaultVectorFeatures
import im.vector.app.features.VectorFeatures
class NewRoomRobot( class NewRoomRobot(
var createdRoom: Boolean = false var createdRoom: Boolean = false
) { ) {
var features: VectorFeatures = DefaultVectorFeatures()
fun createNewRoom(block: CreateNewRoomRobot.() -> Unit) { fun createNewRoom(block: CreateNewRoomRobot.() -> Unit) {
clickOn(R.string.create_new_room) if (features.isNewAppLayoutEnabled()) {
clickOn(R.string.create_new_room)
}
waitUntilViewVisible(withId(R.id.createRoomForm)) waitUntilViewVisible(withId(R.id.createRoomForm))
val createNewRoomRobot = CreateNewRoomRobot() val createNewRoomRobot = CreateNewRoomRobot()
block(createNewRoomRobot) block(createNewRoomRobot)

View File

@ -27,10 +27,15 @@ import com.adevinta.android.barista.assertion.BaristaVisibilityAssertions
import com.adevinta.android.barista.interaction.BaristaClickInteractions.clickOn import com.adevinta.android.barista.interaction.BaristaClickInteractions.clickOn
import im.vector.app.R import im.vector.app.R
import im.vector.app.espresso.tools.waitUntilActivityVisible import im.vector.app.espresso.tools.waitUntilActivityVisible
import im.vector.app.espresso.tools.waitUntilDialogVisible
import im.vector.app.features.DefaultVectorFeatures
import im.vector.app.features.VectorFeatures
import im.vector.app.features.roomdirectory.RoomDirectoryActivity import im.vector.app.features.roomdirectory.RoomDirectoryActivity
class RoomListRobot { class RoomListRobot {
var features: VectorFeatures = DefaultVectorFeatures()
fun openRoom(roomName: String, block: RoomDetailRobot.() -> Unit) { fun openRoom(roomName: String, block: RoomDetailRobot.() -> Unit) {
clickOn(roomName) clickOn(roomName)
block(RoomDetailRobot()) block(RoomDetailRobot())
@ -49,9 +54,15 @@ class RoomListRobot {
} }
fun newRoom(block: NewRoomRobot.() -> Unit) { fun newRoom(block: NewRoomRobot.() -> Unit) {
clickOn(R.id.createGroupRoomButton) if (features.isNewAppLayoutEnabled()) {
waitUntilActivityVisible<RoomDirectoryActivity> { clickOn(R.id.newLayoutCreateChatButton)
BaristaVisibilityAssertions.assertDisplayed(R.id.publicRoomsList) waitUntilDialogVisible(ViewMatchers.withId(R.id.create_room))
clickOn(R.id.create_room)
} else {
clickOn(R.id.createGroupRoomButton)
waitUntilActivityVisible<RoomDirectoryActivity> {
BaristaVisibilityAssertions.assertDisplayed(R.id.publicRoomsList)
}
} }
val newRoomRobot = NewRoomRobot() val newRoomRobot = NewRoomRobot()
block(newRoomRobot) block(newRoomRobot)

View File

@ -31,6 +31,7 @@ import im.vector.app.espresso.tools.waitUntilActivityVisible
import im.vector.app.espresso.tools.waitUntilDialogVisible import im.vector.app.espresso.tools.waitUntilDialogVisible
import im.vector.app.espresso.tools.waitUntilViewVisible import im.vector.app.espresso.tools.waitUntilViewVisible
import im.vector.app.features.home.HomeActivity import im.vector.app.features.home.HomeActivity
import im.vector.app.features.home.room.detail.RoomDetailActivity
import im.vector.app.features.spaces.manage.SpaceManageActivity import im.vector.app.features.spaces.manage.SpaceManageActivity
class SpaceCreateRobot { class SpaceCreateRobot {
@ -85,7 +86,9 @@ class SpaceCreateRobot {
clickOn(R.id.nextButton) clickOn(R.id.nextButton)
waitUntilViewVisible(withId(R.id.recyclerView)) waitUntilViewVisible(withId(R.id.recyclerView))
clickOn(R.id.nextButton) clickOn(R.id.nextButton)
waitUntilDialogVisible(withId(R.id.inviteByMxidButton)) waitUntilActivityVisible<RoomDetailActivity> {
waitUntilDialogVisible(withId(R.id.inviteByMxidButton))
}
// close invite dialog // close invite dialog
pressBack() pressBack()
waitUntilViewVisible(withId(R.id.timelineRecyclerView)) waitUntilViewVisible(withId(R.id.timelineRecyclerView))

View File

@ -18,6 +18,8 @@ package im.vector.app.ui.robot.space
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.test.espresso.Espresso import androidx.test.espresso.Espresso
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.longClick
import androidx.test.espresso.contrib.RecyclerViewActions import androidx.test.espresso.contrib.RecyclerViewActions
import androidx.test.espresso.matcher.ViewMatchers import androidx.test.espresso.matcher.ViewMatchers
import com.adevinta.android.barista.interaction.BaristaClickInteractions.clickOn import com.adevinta.android.barista.interaction.BaristaClickInteractions.clickOn
@ -26,18 +28,44 @@ import com.adevinta.android.barista.internal.viewaction.ClickChildAction
import im.vector.app.R import im.vector.app.R
import im.vector.app.espresso.tools.waitUntilDialogVisible import im.vector.app.espresso.tools.waitUntilDialogVisible
import im.vector.app.espresso.tools.waitUntilViewVisible import im.vector.app.espresso.tools.waitUntilViewVisible
import im.vector.app.features.DefaultVectorFeatures
import im.vector.app.features.VectorFeatures
import org.hamcrest.Matchers import org.hamcrest.Matchers
class SpaceRobot { class SpaceRobot {
fun createSpace(block: SpaceCreateRobot.() -> Unit) { var features: VectorFeatures = DefaultVectorFeatures()
openDrawer()
clickOn(R.string.create_space) fun createSpace(isFirstSpace: Boolean, block: SpaceCreateRobot.() -> Unit) {
if (features.isNewAppLayoutEnabled()) {
clickOn(R.id.newLayoutOpenSpacesButton)
if (isFirstSpace) {
waitUntilDialogVisible(ViewMatchers.withId(R.id.spaces_empty_group))
clickOn(R.id.spaces_empty_button)
} else {
waitUntilDialogVisible(ViewMatchers.withId(R.id.groupListView))
Espresso.onView(ViewMatchers.withId(R.id.groupListView))
.perform(
RecyclerViewActions.actionOnItem<RecyclerView.ViewHolder>(
ViewMatchers.hasDescendant(ViewMatchers.withId(R.id.plus)),
click()
).atPosition(0)
)
}
} else {
openDrawer()
clickOn(R.string.create_space)
}
block(SpaceCreateRobot()) block(SpaceCreateRobot())
} }
fun spaceMenu(spaceName: String, block: SpaceMenuRobot.() -> Unit) { fun spaceMenu(spaceName: String, block: SpaceMenuRobot.() -> Unit) {
openDrawer() if (features.isNewAppLayoutEnabled()) {
clickOn(R.id.newLayoutOpenSpacesButton)
waitUntilDialogVisible(ViewMatchers.withId(R.id.groupListView))
} else {
openDrawer()
}
with(SpaceMenuRobot()) { with(SpaceMenuRobot()) {
openMenu(spaceName) openMenu(spaceName)
block() block()
@ -46,19 +74,32 @@ class SpaceRobot {
fun openMenu(spaceName: String) { fun openMenu(spaceName: String) {
waitUntilViewVisible(ViewMatchers.withId(R.id.groupListView)) waitUntilViewVisible(ViewMatchers.withId(R.id.groupListView))
Espresso.onView(ViewMatchers.withId(R.id.groupListView)) if (features.isNewAppLayoutEnabled()) {
.perform( Espresso.onView(ViewMatchers.withId(R.id.groupListView))
RecyclerViewActions.actionOnItem<RecyclerView.ViewHolder>( .perform(
ViewMatchers.hasDescendant(Matchers.allOf(ViewMatchers.withId(R.id.groupNameView), ViewMatchers.withText(spaceName))), RecyclerViewActions.actionOnItem<RecyclerView.ViewHolder>(
ClickChildAction.clickChildWithId(R.id.groupTmpLeave) ViewMatchers.hasDescendant(Matchers.allOf(ViewMatchers.withId(R.id.name), ViewMatchers.withText(spaceName))),
).atPosition(0) longClick()
) ).atPosition(0)
)
} else {
Espresso.onView(ViewMatchers.withId(R.id.groupListView))
.perform(
RecyclerViewActions.actionOnItem<RecyclerView.ViewHolder>(
ViewMatchers.hasDescendant(Matchers.allOf(ViewMatchers.withId(R.id.groupNameView), ViewMatchers.withText(spaceName))),
ClickChildAction.clickChildWithId(R.id.groupTmpLeave)
).atPosition(0)
)
}
waitUntilDialogVisible(ViewMatchers.withId(R.id.spaceNameView)) waitUntilDialogVisible(ViewMatchers.withId(R.id.spaceNameView))
} }
fun selectSpace(spaceName: String) { fun selectSpace(spaceName: String) {
openDrawer() if (!features.isNewAppLayoutEnabled()) {
waitUntilViewVisible(ViewMatchers.withId(R.id.groupListView)) openDrawer()
waitUntilViewVisible(ViewMatchers.withId(R.id.groupListView))
}
clickOn(spaceName) clickOn(spaceName)
} }
} }

View File

@ -338,6 +338,7 @@
<activity android:name=".features.settings.font.FontScaleSettingActivity"/> <activity android:name=".features.settings.font.FontScaleSettingActivity"/>
<activity android:name=".features.call.dialpad.PstnDialActivity" /> <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.invites.InvitesActivity"/>
<activity android:name=".features.home.room.list.home.release.ReleaseNotesActivity"/>
<!-- Services --> <!-- Services -->

View File

@ -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.RoomListViewModel
import im.vector.app.features.home.room.list.home.HomeRoomListViewModel 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.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.homeserver.HomeServerCapabilitiesViewModel
import im.vector.app.features.invite.InviteUsersToRoomViewModel import im.vector.app.features.invite.InviteUsersToRoomViewModel
import im.vector.app.features.location.LocationSharingViewModel import im.vector.app.features.location.LocationSharingViewModel
@ -624,4 +625,9 @@ interface MavericksViewModelModule {
@IntoMap @IntoMap
@MavericksViewModelKey(InvitesViewModel::class) @MavericksViewModelKey(InvitesViewModel::class)
fun invitesViewModel(factory: InvitesViewModel.Factory): MavericksAssistedViewModelFactory<*, *> fun invitesViewModel(factory: InvitesViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
@Binds
@IntoMap
@MavericksViewModelKey(ReleaseNotesViewModel::class)
fun releaseNotesViewModel(factory: ReleaseNotesViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
} }

View File

@ -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.RoomListSharedAction
import im.vector.app.features.home.room.list.actions.RoomListSharedActionViewModel 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.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.MatrixToBottomSheet
import im.vector.app.features.matrixto.OriginOfMatrixTo import im.vector.app.features.matrixto.OriginOfMatrixTo
import im.vector.app.features.navigation.Navigator import im.vector.app.features.navigation.Navigator
@ -268,6 +269,7 @@ class HomeActivity :
} }
is HomeActivityViewEvents.OnCrossSignedInvalidated -> handleCrossSigningInvalidated(it) is HomeActivityViewEvents.OnCrossSignedInvalidated -> handleCrossSigningInvalidated(it)
HomeActivityViewEvents.ShowAnalyticsOptIn -> handleShowAnalyticsOptIn() HomeActivityViewEvents.ShowAnalyticsOptIn -> handleShowAnalyticsOptIn()
HomeActivityViewEvents.ShowReleaseNotes -> handleShowReleaseNotes()
HomeActivityViewEvents.NotifyUserForThreadsMigration -> handleNotifyUserForThreadsMigration() HomeActivityViewEvents.NotifyUserForThreadsMigration -> handleNotifyUserForThreadsMigration()
is HomeActivityViewEvents.MigrateThreads -> migrateThreadsIfNeeded(it.checkSession) is HomeActivityViewEvents.MigrateThreads -> migrateThreadsIfNeeded(it.checkSession)
} }
@ -282,6 +284,10 @@ class HomeActivity :
homeActivityViewModel.handle(HomeActivityViewActions.ViewStarted) homeActivityViewModel.handle(HomeActivityViewActions.ViewStarted)
} }
private fun handleShowReleaseNotes() {
startActivity(Intent(this, ReleaseNotesActivity::class.java))
}
private fun showSpaceSettings(spaceId: String) { private fun showSpaceSettings(spaceId: String) {
// open bottom sheet // open bottom sheet
SpaceSettingsMenuBottomSheet SpaceSettingsMenuBottomSheet

View File

@ -31,6 +31,7 @@ sealed interface HomeActivityViewEvents : VectorViewEvents {
data class OnCrossSignedInvalidated(val userItem: MatrixItem.UserItem) : HomeActivityViewEvents data class OnCrossSignedInvalidated(val userItem: MatrixItem.UserItem) : HomeActivityViewEvents
object PromptToEnableSessionPush : HomeActivityViewEvents object PromptToEnableSessionPush : HomeActivityViewEvents
object ShowAnalyticsOptIn : HomeActivityViewEvents object ShowAnalyticsOptIn : HomeActivityViewEvents
object ShowReleaseNotes : HomeActivityViewEvents
object NotifyUserForThreadsMigration : HomeActivityViewEvents object NotifyUserForThreadsMigration : HomeActivityViewEvents
data class MigrateThreads(val checkSession: Boolean) : HomeActivityViewEvents data class MigrateThreads(val checkSession: Boolean) : HomeActivityViewEvents
object StartRecoverySetupFlow : HomeActivityViewEvents object StartRecoverySetupFlow : HomeActivityViewEvents

View File

@ -26,11 +26,13 @@ import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.MavericksAssistedViewModelFactory
import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.platform.VectorViewModel 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.AnalyticsConfig
import im.vector.app.features.analytics.AnalyticsTracker import im.vector.app.features.analytics.AnalyticsTracker
import im.vector.app.features.analytics.extensions.toAnalyticsType import im.vector.app.features.analytics.extensions.toAnalyticsType
import im.vector.app.features.analytics.plan.Signup import im.vector.app.features.analytics.plan.Signup
import im.vector.app.features.analytics.store.AnalyticsStore 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.login.ReAuthHelper
import im.vector.app.features.onboarding.AuthenticationDescription import im.vector.app.features.onboarding.AuthenticationDescription
import im.vector.app.features.raw.wellknown.ElementWellKnown import im.vector.app.features.raw.wellknown.ElementWellKnown
@ -82,6 +84,8 @@ class HomeActivityViewModel @AssistedInject constructor(
private val vectorPreferences: VectorPreferences, private val vectorPreferences: VectorPreferences,
private val analyticsTracker: AnalyticsTracker, private val analyticsTracker: AnalyticsTracker,
private val analyticsConfig: AnalyticsConfig, private val analyticsConfig: AnalyticsConfig,
private val releaseNotesPreferencesStore: ReleaseNotesPreferencesStore,
private val vectorFeatures: VectorFeatures,
) : VectorViewModel<HomeActivityViewState, HomeActivityViewActions, HomeActivityViewEvents>(initialState) { ) : VectorViewModel<HomeActivityViewState, HomeActivityViewActions, HomeActivityViewEvents>(initialState) {
@AssistedFactory @AssistedFactory
@ -110,9 +114,27 @@ class HomeActivityViewModel @AssistedInject constructor(
checkSessionPushIsOn() checkSessionPushIsOn()
observeCrossSigningReset() observeCrossSigningReset()
observeAnalytics() observeAnalytics()
observeReleaseNotes()
initThreadsMigration() 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() { private fun observeAnalytics() {
if (analyticsConfig.isEnabled) { if (analyticsConfig.isEnabled) {
analyticsStore.didAskUserConsentFlow analyticsStore.didAskUserConsentFlow

View File

@ -40,6 +40,7 @@ import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
@ -202,24 +203,38 @@ class HomeRoomListViewModel @AssistedInject constructor(
private fun getFiltersDataFlow(): SharedFlow<Optional<List<HomeRoomFilter>>> { private fun getFiltersDataFlow(): SharedFlow<Optional<List<HomeRoomFilter>>> {
val flow = MutableSharedFlow<Optional<List<HomeRoomFilter>>>(replay = 1) val flow = MutableSharedFlow<Optional<List<HomeRoomFilter>>>(replay = 1)
val favouritesFlow = session.flow() val spaceFLow = spaceStateHandler.getSelectedSpaceFlow()
.liveRoomSummaries(
RoomSummaryQueryParams.Builder().also { builder ->
builder.roomTagQueryFilter = RoomTagQueryFilter(true, null, null)
}.build()
)
.map { it.isNotEmpty() }
.distinctUntilChanged() .distinctUntilChanged()
.onStart {
emit(spaceStateHandler.getCurrentSpace().toOption())
}
val dmsFLow = session.flow() val favouritesFlow =
.liveRoomSummaries( spaceFLow.flatMapLatest { selectedSpace ->
RoomSummaryQueryParams.Builder().also { builder -> session.flow()
builder.memberships = listOf(Membership.JOIN) .liveRoomSummaries(
builder.roomCategoryFilter = RoomCategoryFilter.ONLY_DM RoomSummaryQueryParams.Builder().also { builder ->
}.build() builder.spaceFilter = selectedSpace.orNull()?.roomId.toActiveSpaceOrNoFilter()
) builder.roomTagQueryFilter = RoomTagQueryFilter(true, null, null)
.map { it.isNotEmpty() } }.build()
.distinctUntilChanged() )
}
.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 -> combine(favouritesFlow, dmsFLow, preferencesStore.areFiltersEnabledFlow) { hasFavourite, hasDm, areFiltersEnabled ->
Triple(hasFavourite, hasDm, areFiltersEnabled) Triple(hasFavourite, hasDm, areFiltersEnabled)

View File

@ -20,15 +20,18 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.lifecycle.lifecycleScope
import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.core.extensions.configureWith 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.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentInvitesBinding import im.vector.app.databinding.FragmentInvitesBinding
import im.vector.app.features.analytics.plan.ViewRoom import im.vector.app.features.analytics.plan.ViewRoom
import im.vector.app.features.home.room.list.RoomListListener import im.vector.app.features.home.room.list.RoomListListener
import im.vector.app.features.notifications.NotificationDrawerManager 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.RoomSummary
import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
import javax.inject.Inject import javax.inject.Inject
@ -51,6 +54,8 @@ class InvitesFragment : VectorBaseFragment<FragmentInvitesBinding>(), RoomListLi
setupToolbar(views.invitesToolbar) setupToolbar(views.invitesToolbar)
.allowBack() .allowBack()
views.invitesStateView.contentView = views.invitesRecycler
views.invitesRecycler.configureWith(controller) views.invitesRecycler.configureWith(controller)
controller.listener = this controller.listener = this
@ -62,13 +67,31 @@ class InvitesFragment : VectorBaseFragment<FragmentInvitesBinding>(), RoomListLi
when (it) { when (it) {
is InvitesViewEvents.Failure -> showFailure(it.throwable) is InvitesViewEvents.Failure -> showFailure(it.throwable)
is InvitesViewEvents.OpenRoom -> handleOpenRoom(it.roomSummary, it.shouldCloseInviteView) is InvitesViewEvents.OpenRoom -> handleOpenRoom(it.roomSummary, it.shouldCloseInviteView)
InvitesViewEvents.Close -> handleClose()
} }
} }
}
private fun handleClose() { viewModel.invites.onEach {
requireActivity().finish() 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) { 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) { override fun onRejectRoomInvitation(room: RoomSummary) {
notificationDrawerManager.updateEvents { it.clearMemberShipNotificationForRoom(room.roomId) } notificationDrawerManager.updateEvents { it.clearMemberShipNotificationForRoom(room.roomId) }
viewModel.handle(InvitesAction.RejectInvitation(room)) viewModel.handle(InvitesAction.RejectInvitation(room))

View File

@ -22,5 +22,4 @@ import org.matrix.android.sdk.api.session.room.model.RoomSummary
sealed class InvitesViewEvents : VectorViewEvents { sealed class InvitesViewEvents : VectorViewEvents {
data class Failure(val throwable: Throwable) : InvitesViewEvents() data class Failure(val throwable: Throwable) : InvitesViewEvents()
data class OpenRoom(val roomSummary: RoomSummary, val shouldCloseInviteView: Boolean) : InvitesViewEvents() data class OpenRoom(val roomSummary: RoomSummary, val shouldCloseInviteView: Boolean) : InvitesViewEvents()
object Close : InvitesViewEvents()
} }

View File

@ -16,14 +16,25 @@
package im.vector.app.features.home.room.list.home.invites package im.vector.app.features.home.room.list.home.invites
import androidx.lifecycle.asFlow
import androidx.paging.PagedList import androidx.paging.PagedList
import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.MavericksViewModelFactory
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import im.vector.app.R
import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.MavericksAssistedViewModelFactory
import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.platform.VectorViewModel 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 kotlinx.coroutines.launch
import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
@ -36,6 +47,8 @@ import timber.log.Timber
class InvitesViewModel @AssistedInject constructor( class InvitesViewModel @AssistedInject constructor(
@Assisted val initialState: InvitesViewState, @Assisted val initialState: InvitesViewState,
private val session: Session, private val session: Session,
private val stringProvider: StringProvider,
private val drawableProvider: DrawableProvider
) : VectorViewModel<InvitesViewState, InvitesAction, InvitesViewEvents>(initialState) { ) : VectorViewModel<InvitesViewState, InvitesAction, InvitesViewEvents>(initialState) {
private val pagedListConfig = PagedList.Config.Builder() private val pagedListConfig = PagedList.Config.Builder()
@ -52,6 +65,11 @@ class InvitesViewModel @AssistedInject constructor(
companion object : MavericksViewModelFactory<InvitesViewModel, InvitesViewState> by hiltMavericksViewModelFactory() companion object : MavericksViewModelFactory<InvitesViewModel, InvitesViewState> by hiltMavericksViewModelFactory()
private val _invites = MutableSharedFlow<InvitesContentState>(replay = 1)
val invites = _invites.asSharedFlow()
private var invitesCount = -1
init { init {
observeInvites() observeInvites()
} }
@ -72,8 +90,6 @@ class InvitesViewModel @AssistedInject constructor(
return@withState return@withState
} }
val shouldCloseInviteView = state.pagedList?.value?.size == 1
viewModelScope.launch { viewModelScope.launch {
try { try {
session.roomService().leaveRoom(roomId) session.roomService().leaveRoom(roomId)
@ -81,9 +97,6 @@ class InvitesViewModel @AssistedInject constructor(
// Instead, we wait for the room to be rejected // 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. // 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 we update the state, the button will be displayed again, so it's not ideal...
if (shouldCloseInviteView) {
_viewEvents.post(InvitesViewEvents.Close)
}
} catch (failure: Throwable) { } catch (failure: Throwable) {
// Notify the user // Notify the user
_viewEvents.post(InvitesViewEvents.Failure(failure)) _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 // close invites view when navigate to a room from the last one invite
val shouldCloseInviteView = state.pagedList?.value?.size == 1 val shouldCloseInviteView = invitesCount == 1
_viewEvents.post(InvitesViewEvents.OpenRoom(action.roomSummary, shouldCloseInviteView))
// quick echo // quick echo
setState { setState {
@ -117,6 +128,8 @@ class InvitesViewModel @AssistedInject constructor(
} }
) )
} }
_viewEvents.post(InvitesViewEvents.OpenRoom(action.roomSummary, shouldCloseInviteView))
} }
private fun observeInvites() { private fun observeInvites() {
@ -129,8 +142,26 @@ class InvitesViewModel @AssistedInject constructor(
sortOrder = RoomSortOrder.ACTIVITY sortOrder = RoomSortOrder.ACTIVITY
) )
setState { pagedList.asFlow()
copy(pagedList = pagedList) .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)
} }
} }

View File

@ -16,13 +16,24 @@
package im.vector.app.features.home.room.list.home.invites package im.vector.app.features.home.room.list.home.invites
import androidx.lifecycle.LiveData import android.graphics.drawable.Drawable
import androidx.paging.PagedList import androidx.paging.PagedList
import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.MavericksState
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary
data class InvitesViewState( data class InvitesViewState(
val pagedList: LiveData<PagedList<RoomSummary>>? = null,
val roomMembershipChanges: Map<String, ChangeMembershipState> = emptyMap(), val roomMembershipChanges: Map<String, ChangeMembershipState> = emptyMap(),
) : MavericksState ) : 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
}

View File

@ -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,
)
}

View File

@ -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)
}
}

View File

@ -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()
}

View File

@ -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)
}
}
}

View File

@ -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)
}
}
}
}

View File

@ -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()
}
}

View File

@ -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
}
}
}

View File

@ -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()
}

View File

@ -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))
}
}
}

View File

@ -627,7 +627,7 @@ class OnboardingViewModel @AssistedInject constructor(
_viewEvents.post(OnboardingViewEvents.OnAccountCreated) _viewEvents.post(OnboardingViewEvents.OnAccountCreated)
} }
AuthenticationDescription.Login -> { AuthenticationDescription.Login -> {
setState { copy(isLoading = false) } setState { copy(isLoading = false, selectedAuthenticationState = SelectedAuthenticationState(authenticationDescription)) }
_viewEvents.post(OnboardingViewEvents.OnAccountSignedIn) _viewEvents.post(OnboardingViewEvents.OnAccountSignedIn)
} }
} }

View File

@ -28,6 +28,7 @@ import im.vector.app.core.time.Clock
import im.vector.app.core.utils.isAnimationEnabled import im.vector.app.core.utils.isAnimationEnabled
import im.vector.app.features.MainActivity import im.vector.app.features.MainActivity
import im.vector.app.features.analytics.ui.consent.AnalyticsOptInActivity 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.pin.PinActivity
import im.vector.app.features.signout.hard.SignedOutActivity import im.vector.app.features.signout.hard.SignedOutActivity
import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.themes.ThemeUtils
@ -307,6 +308,7 @@ class PopupAlertManager @Inject constructor(
activity !is PinActivity && activity !is PinActivity &&
activity !is SignedOutActivity && activity !is SignedOutActivity &&
activity !is AnalyticsOptInActivity && activity !is AnalyticsOptInActivity &&
activity !is ReleaseNotesActivity &&
activity is VectorBaseActivity<*> && activity is VectorBaseActivity<*> &&
alert.shouldBeDisplayedIn.invoke(activity) alert.shouldBeDisplayedIn.invoke(activity)
} }

View File

@ -165,6 +165,11 @@ class VectorPreferences @Inject constructor(
const val SETTINGS_LABS_AUTO_REPORT_UISI = "SETTINGS_LABS_AUTO_REPORT_UISI" 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" 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_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_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" private const val SETTINGS_LABS_ENABLE_SWIPE_TO_REPLY = "SETTINGS_LABS_ENABLE_SWIPE_TO_REPLY"

View File

@ -27,6 +27,7 @@ import im.vector.app.R
import im.vector.app.core.preference.VectorSwitchPreference import im.vector.app.core.preference.VectorSwitchPreference
import im.vector.app.features.MainActivity import im.vector.app.features.MainActivity
import im.vector.app.features.MainActivityArgs 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.analytics.plan.MobileScreen
import im.vector.app.features.home.room.threads.ThreadsManager import im.vector.app.features.home.room.threads.ThreadsManager
import org.matrix.android.sdk.api.settings.LightweightSettingsStorage import org.matrix.android.sdk.api.settings.LightweightSettingsStorage
@ -39,6 +40,7 @@ class VectorSettingsLabsFragment :
@Inject lateinit var vectorPreferences: VectorPreferences @Inject lateinit var vectorPreferences: VectorPreferences
@Inject lateinit var lightweightSettingsStorage: LightweightSettingsStorage @Inject lateinit var lightweightSettingsStorage: LightweightSettingsStorage
@Inject lateinit var threadsManager: ThreadsManager @Inject lateinit var threadsManager: ThreadsManager
@Inject lateinit var vectorFeatures: VectorFeatures
override var titleRes = R.string.room_settings_labs_pref_title override var titleRes = R.string.room_settings_labs_pref_title
override val preferenceXmlRes = R.xml.vector_settings_labs override val preferenceXmlRes = R.xml.vector_settings_labs
@ -72,6 +74,10 @@ class VectorSettingsLabsFragment :
true true
} }
} }
findPreference<VectorSwitchPreference>(VectorPreferences.SETTINGS_LABS_UNREAD_NOTIFICATIONS_AS_TAB)!!.let {
it.isVisible = !vectorFeatures.isNewAppLayoutEnabled()
}
} }
/** /**

View File

@ -31,6 +31,7 @@ import im.vector.app.core.preference.VectorPreference
import im.vector.app.core.preference.VectorSwitchPreference import im.vector.app.core.preference.VectorSwitchPreference
import im.vector.app.features.MainActivity import im.vector.app.features.MainActivity
import im.vector.app.features.MainActivityArgs 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.analytics.plan.MobileScreen
import im.vector.app.features.settings.font.FontScaleSettingActivity import im.vector.app.features.settings.font.FontScaleSettingActivity
import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.themes.ThemeUtils
@ -44,6 +45,7 @@ class VectorSettingsPreferencesFragment :
@Inject lateinit var vectorPreferences: VectorPreferences @Inject lateinit var vectorPreferences: VectorPreferences
@Inject lateinit var fontScalePreferences: FontScalePreferences @Inject lateinit var fontScalePreferences: FontScalePreferences
@Inject lateinit var vectorFeatures: VectorFeatures
override var titleRes = R.string.settings_preferences override var titleRes = R.string.settings_preferences
override val preferenceXmlRes = R.xml.vector_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 // Url preview
/* /*
TODO Note: we keep the setting client side for now TODO Note: we keep the setting client side for now

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

View 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>

View File

@ -28,4 +28,4 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="start" /> android:layout_gravity="start" />
</androidx.drawerlayout.widget.DrawerLayout> </androidx.drawerlayout.widget.DrawerLayout>

View 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>

View File

@ -20,17 +20,24 @@
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView <im.vector.app.core.platform.StateView
android:id="@+id/invites_recycler" android:id="@+id/invites_state_view"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="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_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="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> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -9,6 +9,7 @@
android:id="@+id/groupListView" android:id="@+id/groupListView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:minHeight="195dp"
android:overScrollMode="always" android:overScrollMode="always"
tools:listitem="@layout/item_space" /> tools:listitem="@layout/item_space" />

View 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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View File

@ -30,7 +30,9 @@
</im.vector.app.core.preference.VectorPreferenceCategory> </im.vector.app.core.preference.VectorPreferenceCategory>
<im.vector.app.core.preference.VectorPreferenceCategory android:title="@string/spaces"> <im.vector.app.core.preference.VectorPreferenceCategory
android:title="@string/spaces"
android:key="SETTINGS_PREF_SPACE_CATEGORY">
<im.vector.app.core.preference.VectorSwitchPreference <im.vector.app.core.preference.VectorSwitchPreference
android:defaultValue="false" android:defaultValue="false"

View File

@ -170,7 +170,7 @@ class OnboardingViewModelTest {
.assertStatesChanges( .assertStatesChanges(
initialState, initialState,
{ copy(isLoading = true) }, { copy(isLoading = true) },
{ copy(isLoading = false) } { copy(isLoading = false, selectedAuthenticationState = SelectedAuthenticationState(description = AuthenticationDescription.Login)) }
) )
.assertEvents(OnboardingViewEvents.OnAccountSignedIn) .assertEvents(OnboardingViewEvents.OnAccountSignedIn)
.finish() .finish()
@ -189,7 +189,7 @@ class OnboardingViewModelTest {
.assertStatesChanges( .assertStatesChanges(
initialState, initialState,
{ copy(isLoading = true) }, { copy(isLoading = true) },
{ copy(isLoading = false) } { copy(isLoading = false, selectedAuthenticationState = SelectedAuthenticationState(description = AuthenticationDescription.Login)) }
) )
.assertEvents(OnboardingViewEvents.OnAccountSignedIn) .assertEvents(OnboardingViewEvents.OnAccountSignedIn)
.finish() .finish()
@ -284,7 +284,7 @@ class OnboardingViewModelTest {
.assertStatesChanges( .assertStatesChanges(
initialState, initialState,
{ copy(isLoading = true) }, { copy(isLoading = true) },
{ copy(isLoading = false) } { copy(isLoading = false, selectedAuthenticationState = SelectedAuthenticationState(description = AuthenticationDescription.Login)) }
) )
.assertEvents(OnboardingViewEvents.OnAccountSignedIn) .assertEvents(OnboardingViewEvents.OnAccountSignedIn)
.finish() .finish()
@ -870,7 +870,7 @@ class OnboardingViewModelTest {
.assertStatesChanges( .assertStatesChanges(
initialState, initialState,
{ copy(isLoading = true) }, { copy(isLoading = true) },
{ copy(isLoading = false) } { copy(isLoading = false, selectedAuthenticationState = SelectedAuthenticationState(description = AuthenticationDescription.Login)) }
) )
.assertEvents(OnboardingViewEvents.OnAccountSignedIn) .assertEvents(OnboardingViewEvents.OnAccountSignedIn)
.finish() .finish()