applayout release experience (#7003)
|
@ -3256,4 +3256,15 @@
|
|||
<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>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"/>
|
||||
|
||||
<!-- Services -->
|
||||
|
||||
|
|
|
@ -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
|
||||
|
@ -624,4 +625,9 @@ interface MavericksViewModelModule {
|
|||
@IntoMap
|
||||
@MavericksViewModelKey(InvitesViewModel::class)
|
||||
fun invitesViewModel(factory: InvitesViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@MavericksViewModelKey(ReleaseNotesViewModel::class)
|
||||
fun releaseNotesViewModel(factory: ReleaseNotesViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 5.3 KiB |
After Width: | Height: | Size: 7.2 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 32 KiB |
After Width: | Height: | Size: 52 KiB |
|
@ -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>
|
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>
|
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 |
After Width: | Height: | Size: 5.3 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 32 KiB |
After Width: | Height: | Size: 52 KiB |
After Width: | Height: | Size: 32 KiB |
|
@ -170,7 +170,7 @@ class OnboardingViewModelTest {
|
|||
.assertStatesChanges(
|
||||
initialState,
|
||||
{ copy(isLoading = true) },
|
||||
{ copy(isLoading = false) }
|
||||
{ copy(isLoading = false, selectedAuthenticationState = SelectedAuthenticationState(description = AuthenticationDescription.Login)) }
|
||||
)
|
||||
.assertEvents(OnboardingViewEvents.OnAccountSignedIn)
|
||||
.finish()
|
||||
|
@ -189,7 +189,7 @@ class OnboardingViewModelTest {
|
|||
.assertStatesChanges(
|
||||
initialState,
|
||||
{ copy(isLoading = true) },
|
||||
{ copy(isLoading = false) }
|
||||
{ copy(isLoading = false, selectedAuthenticationState = SelectedAuthenticationState(description = AuthenticationDescription.Login)) }
|
||||
)
|
||||
.assertEvents(OnboardingViewEvents.OnAccountSignedIn)
|
||||
.finish()
|
||||
|
@ -284,7 +284,7 @@ class OnboardingViewModelTest {
|
|||
.assertStatesChanges(
|
||||
initialState,
|
||||
{ copy(isLoading = true) },
|
||||
{ copy(isLoading = false) }
|
||||
{ copy(isLoading = false, selectedAuthenticationState = SelectedAuthenticationState(description = AuthenticationDescription.Login)) }
|
||||
)
|
||||
.assertEvents(OnboardingViewEvents.OnAccountSignedIn)
|
||||
.finish()
|
||||
|
@ -870,7 +870,7 @@ class OnboardingViewModelTest {
|
|||
.assertStatesChanges(
|
||||
initialState,
|
||||
{ copy(isLoading = true) },
|
||||
{ copy(isLoading = false) }
|
||||
{ copy(isLoading = false, selectedAuthenticationState = SelectedAuthenticationState(description = AuthenticationDescription.Login)) }
|
||||
)
|
||||
.assertEvents(OnboardingViewEvents.OnAccountSignedIn)
|
||||
.finish()
|
||||
|
|