diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/OnboardingActivity.kt b/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/OnboardingActivity.kt index acfbeaf735a150182887a3fb0503221e896e319c..6bf930936145b891229a5e50de702610ec5cd1cf 100644 --- a/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/OnboardingActivity.kt +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/OnboardingActivity.kt @@ -20,13 +20,44 @@ package org.briarproject.mailbox.android.ui import android.os.Bundle +import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity -import org.briarproject.mailbox.R +import androidx.fragment.app.Fragment +import androidx.viewpager2.adapter.FragmentStateAdapter +import androidx.viewpager2.widget.ViewPager2 +import org.briarproject.mailbox.databinding.ActivityOnboardingBinding class OnboardingActivity : AppCompatActivity() { + private val viewModel: OnboardingViewModel by viewModels() + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.activity_onboarding) + val ui = ActivityOnboardingBinding.inflate(layoutInflater) + setContentView(ui.root) + + ui.pager.adapter = OnboardingAdapter(this) + ui.pager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { + override fun onPageSelected(position: Int) { + super.onPageSelected(position) + viewModel.selectPage(position) + } + }) + viewModel.currentPage.observe(this) { position -> + ui.pager.setCurrentItem(position, true) + } } + + class OnboardingAdapter(activity: AppCompatActivity) : FragmentStateAdapter(activity) { + override fun getItemCount(): Int = 5 + override fun createFragment(position: Int): Fragment = when (position) { + 0 -> Onboarding0Fragment() + 1 -> Onboarding1Fragment() + 2 -> Onboarding2Fragment() + 3 -> Onboarding3Fragment() + 4 -> FinishFragment() + else -> error("Unexpected OnboardingFragment: $position") + } + } + } diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/OnboardingFragment.kt b/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/OnboardingFragment.kt index c332dede856610782c5d6701bbf812cd0c3815e6..2181addaef7125df6c17ddcdf994987fc0f0a015 100644 --- a/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/OnboardingFragment.kt +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/OnboardingFragment.kt @@ -28,22 +28,34 @@ import androidx.annotation.StringRes import androidx.core.content.ContextCompat.getColorStateList import androidx.core.widget.ImageViewCompat import androidx.fragment.app.Fragment -import androidx.navigation.NavController -import androidx.navigation.fragment.findNavController +import androidx.fragment.app.activityViewModels import org.briarproject.mailbox.R import org.briarproject.mailbox.databinding.FragmentOnboardingBinding +class Onboarding0Fragment : OnboardingFragment( + number = 0, + icon = R.mipmap.ic_launcher_round, + title = R.string.onboarding_0_title, + description = R.string.onboarding_0_description, + topButtonAction = { viewModel -> + viewModel.selectPage(1) + }, + bottomButtonText = R.string.button_skip_intro, + bottomButtonAction = { + requireActivity().supportFinishAfterTransition() + }, +) + class Onboarding1Fragment : OnboardingFragment( number = 1, icon = R.mipmap.ic_launcher_round, title = R.string.onboarding_1_title, description = R.string.onboarding_1_description, - topButtonAction = { nav -> - nav.navigate(R.id.action_onboarding_1_to_2) + topButtonAction = { viewModel -> + viewModel.selectPage(2) }, - bottomButtonText = R.string.button_skip_intro, - bottomButtonAction = { - requireActivity().supportFinishAfterTransition() + bottomButtonAction = { viewModel -> + viewModel.selectPage(0) }, ) @@ -52,11 +64,11 @@ class Onboarding2Fragment : OnboardingFragment( icon = R.mipmap.ic_launcher_round, title = R.string.onboarding_2_title, description = R.string.onboarding_2_description, - topButtonAction = { nav -> - nav.navigate(R.id.action_onboarding_2_to_3) + topButtonAction = { viewModel -> + viewModel.selectPage(3) }, - bottomButtonAction = { nav -> - nav.popBackStack() + bottomButtonAction = { viewModel -> + viewModel.selectPage(1) }, ) @@ -65,24 +77,11 @@ class Onboarding3Fragment : OnboardingFragment( icon = R.mipmap.ic_launcher_round, title = R.string.onboarding_3_title, description = R.string.onboarding_3_description, - topButtonAction = { nav -> - nav.navigate(R.id.action_onboarding_3_to_4) + topButtonAction = { viewModel -> + viewModel.selectPage(4) // finishes activity }, - bottomButtonAction = { nav -> - nav.popBackStack() - }, -) - -class Onboarding4Fragment : OnboardingFragment( - number = 4, - icon = R.mipmap.ic_launcher_round, - title = R.string.onboarding_4_title, - description = R.string.onboarding_4_description, - topButtonAction = { - requireActivity().supportFinishAfterTransition() - }, - bottomButtonAction = { nav -> - nav.popBackStack() + bottomButtonAction = { viewModel -> + viewModel.selectPage(2) }, ) @@ -96,10 +95,11 @@ abstract class OnboardingFragment( private val description: Int, @StringRes private val bottomButtonText: Int = R.string.button_back, - private val topButtonAction: Fragment.(NavController) -> Unit, - private val bottomButtonAction: Fragment.(NavController) -> Unit, + private val topButtonAction: Fragment.(OnboardingViewModel) -> Unit, + private val bottomButtonAction: Fragment.(OnboardingViewModel) -> Unit, ) : Fragment() { + private val viewModel: OnboardingViewModel by activityViewModels() private var _ui: FragmentOnboardingBinding? = null /** @@ -121,17 +121,16 @@ abstract class OnboardingFragment( ui.title.setText(title) ui.description.setText(description) listOf(ui.bullet1, ui.bullet2, ui.bullet3, ui.bullet4).forEachIndexed { i, imageView -> - val color = if (i + 1 <= number) R.color.briar_green else R.color.briar_night + val color = if (i <= number) R.color.briar_green else R.color.briar_night val tintList = getColorStateList(requireContext(), color) ImageViewCompat.setImageTintList(imageView, tintList) } - val nav = findNavController() ui.topButton.setOnClickListener { - topButtonAction(nav) + topButtonAction(viewModel) } ui.bottomButton.setText(bottomButtonText) ui.bottomButton.setOnClickListener { - bottomButtonAction(nav) + bottomButtonAction(viewModel) } } @@ -141,3 +140,10 @@ abstract class OnboardingFragment( } } + +class FinishFragment : Fragment() { + override fun onResume() { + super.onResume() + requireActivity().supportFinishAfterTransition() + } +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/OnboardingViewModel.kt b/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/OnboardingViewModel.kt new file mode 100644 index 0000000000000000000000000000000000000000..c33a87e5c4ce0132834947dd84c6aeff196aee15 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/OnboardingViewModel.kt @@ -0,0 +1,43 @@ +/* + * Briar Mailbox + * Copyright (C) 2021-2022 The Briar Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + */ + +package org.briarproject.mailbox.android.ui + +import android.app.Application +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.LiveData +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.distinctUntilChanged +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject + +@HiltViewModel +class OnboardingViewModel @Inject constructor( + app: Application, + handle: SavedStateHandle, +) : AndroidViewModel(app) { + + private val _currentPage = handle.getLiveData("currentPage", 0) + val currentPage: LiveData<Int> = _currentPage.distinctUntilChanged() // prevent infinite loop + + fun selectPage(position: Int) { + _currentPage.value = position + } + +} diff --git a/mailbox-android/src/main/res/anim/enter.xml b/mailbox-android/src/main/res/anim/enter.xml deleted file mode 100644 index 32b84f2dac6e4ebe6ccca6ace3a222d26d8ac7c4..0000000000000000000000000000000000000000 --- a/mailbox-android/src/main/res/anim/enter.xml +++ /dev/null @@ -1,11 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<set xmlns:android="http://schemas.android.com/apk/res/android"> - - <!-- slide in from right --> - <translate - android:duration="@integer/animationSpeed" - android:fromXDelta="100%p" - android:interpolator="@android:interpolator/decelerate_quad" - android:toXDelta="0" /> - -</set> diff --git a/mailbox-android/src/main/res/anim/exit.xml b/mailbox-android/src/main/res/anim/exit.xml deleted file mode 100644 index 205435cca6e4605e75c01af6f329e5174e9466f1..0000000000000000000000000000000000000000 --- a/mailbox-android/src/main/res/anim/exit.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<alpha xmlns:android="http://schemas.android.com/apk/res/android" - android:duration="@integer/animationSpeed" - android:fromAlpha="1.0" - android:interpolator="@android:interpolator/accelerate_quad" - android:toAlpha="0.0" /> diff --git a/mailbox-android/src/main/res/anim/pop_enter.xml b/mailbox-android/src/main/res/anim/pop_enter.xml deleted file mode 100644 index 4b3a8d6092c7ab619e25b0b1db87ffba0c78b183..0000000000000000000000000000000000000000 --- a/mailbox-android/src/main/res/anim/pop_enter.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<alpha xmlns:android="http://schemas.android.com/apk/res/android" - android:duration="@integer/animationSpeed" - android:fromAlpha="0.0" - android:interpolator="@android:interpolator/decelerate_quad" - android:toAlpha="1.0" /> diff --git a/mailbox-android/src/main/res/anim/pop_exit.xml b/mailbox-android/src/main/res/anim/pop_exit.xml deleted file mode 100644 index 2c514d824c58fad5c2a2738232c250403b56f238..0000000000000000000000000000000000000000 --- a/mailbox-android/src/main/res/anim/pop_exit.xml +++ /dev/null @@ -1,11 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<set xmlns:android="http://schemas.android.com/apk/res/android"> - - <!-- slide out to right --> - <translate - android:duration="@integer/animationSpeed" - android:fromXDelta="0" - android:interpolator="@android:interpolator/accelerate_quad" - android:toXDelta="100%p" /> - -</set> diff --git a/mailbox-android/src/main/res/layout-land/fragment_onboarding.xml b/mailbox-android/src/main/res/layout-land/fragment_onboarding.xml index e5492eece8ea25e46c29f05e6382a5d1dd8ea7d8..365e2e407ba9fedd15a2f91aacb8f2e9c3d3e0a3 100644 --- a/mailbox-android/src/main/res/layout-land/fragment_onboarding.xml +++ b/mailbox-android/src/main/res/layout-land/fragment_onboarding.xml @@ -37,7 +37,7 @@ app:layout_constraintStart_toStartOf="@+id/guideline" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_chainStyle="packed" - tools:text="@string/onboarding_1_title" /> + tools:text="@string/onboarding_0_title" /> <TextView android:id="@+id/description" @@ -49,7 +49,7 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="@+id/guideline" app:layout_constraintTop_toBottomOf="@+id/title" - tools:text="@string/onboarding_1_description" /> + tools:text="@string/onboarding_0_description" /> <ImageView android:id="@+id/bullet1" diff --git a/mailbox-android/src/main/res/layout/activity_onboarding.xml b/mailbox-android/src/main/res/layout/activity_onboarding.xml index 24bf07ef9e8889559358519445fa8267bcad5b79..4aca0f57cf228b9a7ccfae6e9a1859de751859f9 100644 --- a/mailbox-android/src/main/res/layout/activity_onboarding.xml +++ b/mailbox-android/src/main/res/layout/activity_onboarding.xml @@ -1,11 +1,7 @@ <?xml version="1.0" encoding="utf-8"?> -<androidx.fragment.app.FragmentContainerView xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" +<androidx.viewpager2.widget.ViewPager2 xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" - android:id="@+id/fragmentContainer" - android:name="androidx.navigation.fragment.NavHostFragment" + android:id="@+id/pager" android:layout_width="match_parent" android:layout_height="match_parent" - app:defaultNavHost="true" - app:navGraph="@navigation/nav_onboarding" tools:context=".android.ui.OnboardingActivity" /> diff --git a/mailbox-android/src/main/res/layout/fragment_onboarding.xml b/mailbox-android/src/main/res/layout/fragment_onboarding.xml index 2c07cce94fef67788a0a39c165678d589caa9038..af06d8249701814d2e94a48e5797f8a9c85ed0c3 100644 --- a/mailbox-android/src/main/res/layout/fragment_onboarding.xml +++ b/mailbox-android/src/main/res/layout/fragment_onboarding.xml @@ -30,7 +30,7 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/icon" app:layout_constraintVertical_chainStyle="packed" - tools:text="@string/onboarding_1_title" /> + tools:text="@string/onboarding_0_title" /> <TextView android:id="@+id/description" @@ -43,7 +43,7 @@ app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/title" - tools:text="@string/onboarding_1_description" /> + tools:text="@string/onboarding_0_description" /> <ImageView android:id="@+id/bullet1" diff --git a/mailbox-android/src/main/res/navigation/nav_onboarding.xml b/mailbox-android/src/main/res/navigation/nav_onboarding.xml deleted file mode 100644 index 74a5c75501577c7cd9aba3fb8bdb7f9f9b350cd6..0000000000000000000000000000000000000000 --- a/mailbox-android/src/main/res/navigation/nav_onboarding.xml +++ /dev/null @@ -1,51 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<navigation 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:id="@+id/nav_onboarding" - app:startDestination="@id/onboarding1Fragment"> - <fragment - android:id="@+id/onboarding1Fragment" - android:name="org.briarproject.mailbox.android.ui.Onboarding1Fragment" - android:label="Onboarding1Fragment" - tools:layout="@layout/fragment_onboarding"> - <action - android:id="@+id/action_onboarding_1_to_2" - app:destination="@id/onboarding2Fragment" - app:enterAnim="@anim/enter" - app:exitAnim="@anim/exit" - app:popEnterAnim="@anim/pop_enter" - app:popExitAnim="@anim/pop_exit" /> - </fragment> - <fragment - android:id="@+id/onboarding2Fragment" - android:name="org.briarproject.mailbox.android.ui.Onboarding2Fragment" - android:label="Onboarding2Fragment" - tools:layout="@layout/fragment_onboarding"> - <action - android:id="@+id/action_onboarding_2_to_3" - app:destination="@id/onboarding3Fragment" - app:enterAnim="@anim/enter" - app:exitAnim="@anim/exit" - app:popEnterAnim="@anim/pop_enter" - app:popExitAnim="@anim/pop_exit" /> - </fragment> - <fragment - android:id="@+id/onboarding3Fragment" - android:name="org.briarproject.mailbox.android.ui.Onboarding3Fragment" - android:label="Onboarding3Fragment" - tools:layout="@layout/fragment_onboarding"> - <action - android:id="@+id/action_onboarding_3_to_4" - app:destination="@id/onboarding4Fragment" - app:enterAnim="@anim/enter" - app:exitAnim="@anim/exit" - app:popEnterAnim="@anim/pop_enter" - app:popExitAnim="@anim/pop_exit" /> - </fragment> - <fragment - android:id="@+id/onboarding4Fragment" - android:name="org.briarproject.mailbox.android.ui.Onboarding4Fragment" - android:label="Onboarding4Fragment" - tools:layout="@layout/fragment_onboarding" /> -</navigation> diff --git a/mailbox-android/src/main/res/values/attrs.xml b/mailbox-android/src/main/res/values/attrs.xml deleted file mode 100644 index 3704cec434d97f94f6b416d1c20c0b9f4d4598bf..0000000000000000000000000000000000000000 --- a/mailbox-android/src/main/res/values/attrs.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<resources> - - <integer name="animationSpeed">@android:integer/config_mediumAnimTime</integer> - -</resources> diff --git a/mailbox-android/src/main/res/values/strings.xml b/mailbox-android/src/main/res/values/strings.xml index f895cbf16d6c4ef50fb3bcf8e54efd3c19e3041e..094a73711833657cb9952cb484e07b0f5cf1f9bd 100644 --- a/mailbox-android/src/main/res/values/strings.xml +++ b/mailbox-android/src/main/res/values/strings.xml @@ -10,14 +10,14 @@ <string name="button_skip_intro">Skip Intro</string> <string name="button_back">Back</string> - <string name="onboarding_1_title">Stay Reachable</string> - <string name="onboarding_1_description">The Mailbox helps you to stay in touch with your contacts and synchronizes your messages to Briar.</string> - <string name="onboarding_2_title">Use a spare device</string> - <string name="onboarding_2_description">Install the mailbox app on a spare phone or tablet and leave it connected to power and WiFi. </string> - <string name="onboarding_3_title">Message delivery</string> - <string name="onboarding_3_description">When your Mailbox is online, your contacts can leave messages for you even if your Briar is offline. The Mailbox will receive the messages, and forward them to Briar when you come online.</string> - <string name="onboarding_4_title">Encrypted</string> - <string name="onboarding_4_description">Messages going through the Mailbox are encrypted and cannot be read by the Mailbox or this phone. The Mailbox simply forwards them to your Briar Messenger phone.</string> + <string name="onboarding_0_title">Stay Reachable</string> + <string name="onboarding_0_description">The Mailbox helps you to stay in touch with your contacts and synchronizes your messages to Briar.</string> + <string name="onboarding_1_title">Use a spare device</string> + <string name="onboarding_1_description">Install the mailbox app on a spare phone or tablet and leave it connected to power and WiFi. </string> + <string name="onboarding_2_title">Message delivery</string> + <string name="onboarding_2_description">When your Mailbox is online, your contacts can leave messages for you even if your Briar is offline. The Mailbox will receive the messages, and forward them to Briar when you come online.</string> + <string name="onboarding_3_title">Encrypted</string> + <string name="onboarding_3_description">Messages going through the Mailbox are encrypted and cannot be read by the Mailbox or this phone. The Mailbox simply forwards them to your Briar Messenger phone.</string> <!-- TODO: We might want to copy string from don't kill me lib, so translation memory can auto-translate most of them. -->