diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index aedeec4d413db8a361220286391be1d47180e7c4..576d39ba7bf6b38b89898c8b64348ceeee89a36a 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -36,9 +36,6 @@ <option name="ALLOW_TRAILING_COMMA" value="true" /> <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> </JetCodeStyleSettings> - <XML> - <option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" /> - </XML> <codeStyleSettings language="Groovy"> <indentOptions> <option name="SMART_TABS" value="true" /> diff --git a/build.gradle b/build.gradle index 6950593039b5b565412059ce848d5f059f58fd4e..536485f0f25ff50270b4a685e6269cdaf92e1240 100644 --- a/build.gradle +++ b/build.gradle @@ -1,21 +1,21 @@ buildscript { - ext.kotlin_version = '1.5.31' - ext.hilt_version = '2.38.1' + ext.kotlin_version = '1.6.10' + ext.hilt_version = '2.40' ext.tor_version = '0.3.5.15' ext.obfs4_version = '0.0.12-dev-40245c4a' ext.junit_version = '5.7.2' ext.mockk_version = '1.10.4' - ext.ktlint_plugin_version = '10.1.0' + ext.ktlint_plugin_version = '10.2.1' - ext.androidx_fragment_version = '1.3.6' - ext.androidx_constraintlayout_version = '2.1.1' - ext.google_material_version = '1.4.0' + ext.androidx_fragment_version = '1.4.1' + ext.androidx_constraintlayout_version = '2.1.3' + ext.google_material_version = '1.5.0' repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.0.3' + classpath 'com.android.tools.build:gradle:7.0.4' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" } diff --git a/gradle/ktlint.gradle b/gradle/ktlint.gradle index 22e03eae400e982e4a69ff8db4ca7cb3899c89c3..60fada8f6389189b1efa5a56e37d30fff0efc477 100644 --- a/gradle/ktlint.gradle +++ b/gradle/ktlint.gradle @@ -1,5 +1,5 @@ ktlint { - version = "0.42.1" + version = "0.43.2" android = true enableExperimentalRules = false verbose = true diff --git a/mailbox-android/build.gradle b/mailbox-android/build.gradle index 5c8c200ed187f4d5d463576f01675d1038a7fc76..744978cd48c4a17c8b4ca50a2abe1ac1d443f6c7 100644 --- a/mailbox-android/build.gradle +++ b/mailbox-android/build.gradle @@ -6,7 +6,7 @@ plugins { id 'kotlin-kapt' id 'dagger.hilt.android.plugin' id "org.jlleitschuh.gradle.ktlint" version "$ktlint_plugin_version" - id 'checkstyle' + id 'checkstyle' // only needed for Java code } android { @@ -16,7 +16,7 @@ android { defaultConfig { applicationId "org.briarproject.mailbox" minSdkVersion 16 - targetSdkVersion 30 + targetSdkVersion 31 versionCode 1 versionName "1.0" multiDexEnabled true // only needed when minSdkVersion < 21 @@ -30,6 +30,9 @@ android { proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } + buildFeatures { + viewBinding true + } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 @@ -55,17 +58,21 @@ dependencies { implementation project(path: ':dont-kill-me-lib') implementation 'com.github.tony19:logback-android:2.0.0' - implementation 'androidx.appcompat:appcompat:1.3.1' - implementation "androidx.activity:activity-ktx:1.3.1" + implementation 'androidx.appcompat:appcompat:1.4.1' + implementation "androidx.activity:activity-ktx:1.4.0" implementation "androidx.fragment:fragment-ktx:$androidx_fragment_version" - def lifecycle_version = "2.4.0-rc01" + def lifecycle_version = "2.4.0" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version" + def nav_version = "2.4.0" + implementation "androidx.navigation:navigation-fragment-ktx:$nav_version" + implementation "androidx.navigation:navigation-ui-ktx:$nav_version" + implementation "androidx.constraintlayout:constraintlayout:$androidx_constraintlayout_version" implementation "com.google.android.material:material:$google_material_version" implementation "com.google.dagger:hilt-android:$hilt_version" diff --git a/mailbox-android/src/main/AndroidManifest.xml b/mailbox-android/src/main/AndroidManifest.xml index ee9849f4d42f338fdbba1853db241dc546aeca6b..4466f33c0ae170dc3a788afb1c808dce5cbeb4ac 100644 --- a/mailbox-android/src/main/AndroidManifest.xml +++ b/mailbox-android/src/main/AndroidManifest.xml @@ -20,7 +20,7 @@ android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" - android:theme="@style/Theme.Briarmailbox"> + android:theme="@style/Theme.BriarMailbox"> <service android:name=".android.MailboxService" /> @@ -33,6 +33,7 @@ <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> + <activity android:name=".android.ui.OnboardingActivity" /> <receiver android:name=".core.system.AlarmReceiver" /> diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxNotificationManager.kt b/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxNotificationManager.kt index fa67a900c267f9c6c5aca638f52469ed4c759efe..2673e52e8beb121e19d18276954a3d6e29694e47 100644 --- a/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxNotificationManager.kt +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxNotificationManager.kt @@ -24,9 +24,10 @@ import android.app.NotificationChannel import android.app.NotificationManager import android.app.NotificationManager.IMPORTANCE_LOW import android.app.PendingIntent +import android.app.PendingIntent.FLAG_IMMUTABLE import android.content.Context import android.content.Intent -import android.os.Build +import android.os.Build.VERSION.SDK_INT import androidx.annotation.RequiresApi import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat.PRIORITY_MIN @@ -50,7 +51,7 @@ class MailboxNotificationManager @Inject constructor( private val nm = getSystemService(ctx, NotificationManager::class.java)!! init { - if (Build.VERSION.SDK_INT >= 26) createNotificationChannels() + if (SDK_INT >= 26) createNotificationChannels() } @RequiresApi(26) @@ -68,8 +69,9 @@ class MailboxNotificationManager @Inject constructor( val serviceNotification: Notification get() { val notificationIntent = Intent(ctx, MainActivity::class.java) + val flags = if (SDK_INT >= 23) FLAG_IMMUTABLE else 0 val pendingIntent = PendingIntent.getActivity( - ctx, 0, notificationIntent, 0 + ctx, 0, notificationIntent, flags ) return NotificationCompat.Builder(ctx, CHANNEL_ID) .setContentTitle(ctx.getString(R.string.notification_mailbox_title)) diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxViewModel.kt b/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxViewModel.kt index 7cdf4dfa16a8b100f4af24653a055ceaae941278..48661e9defc72c33c03b49f600c9d892530d7d0b 100644 --- a/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxViewModel.kt +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxViewModel.kt @@ -30,6 +30,7 @@ import kotlinx.coroutines.flow.StateFlow import org.briarproject.android.dontkillmelib.DozeHelper import org.briarproject.mailbox.core.lifecycle.LifecycleManager import org.briarproject.mailbox.core.lifecycle.LifecycleManager.LifecycleState +import org.briarproject.mailbox.core.setup.SetupManager import org.briarproject.mailbox.core.system.DozeWatchdog import javax.inject.Inject import kotlin.concurrent.thread @@ -41,6 +42,7 @@ class MailboxViewModel @Inject constructor( private val dozeWatchdog: DozeWatchdog, handle: SavedStateHandle, private val lifecycleManager: LifecycleManager, + private val setupManager: SetupManager, ) : AndroidViewModel(app) { val needToShowDoNotKillMeFragment get() = dozeHelper.needToShowDoNotKillMeFragment(app) @@ -53,6 +55,8 @@ class MailboxViewModel @Inject constructor( val lifecycleState: StateFlow<LifecycleState> = lifecycleManager.lifecycleStateFlow + val isSetUp: Boolean get() = setupManager.hasDb + @UiThread fun onDoNotKillComplete() { _doNotKillComplete.value = true diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/MainActivity.kt b/mailbox-android/src/main/java/org/briarproject/mailbox/android/MainActivity.kt index 11932673a90c7c8fa643eaf4c379f5ebb0ccdd15..1aad5c7f10dd0a913033919c3ab4eef732e32d22 100644 --- a/mailbox-android/src/main/java/org/briarproject/mailbox/android/MainActivity.kt +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/android/MainActivity.kt @@ -19,35 +19,45 @@ package org.briarproject.mailbox.android +import android.content.Intent import android.os.Bundle import androidx.activity.viewModels import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity -import androidx.fragment.app.Fragment +import androidx.navigation.NavController +import androidx.navigation.fragment.NavHostFragment import dagger.hilt.android.AndroidEntryPoint import org.briarproject.android.dontkillmelib.PowerUtils.needsDozeWhitelisting import org.briarproject.mailbox.R +import org.briarproject.mailbox.android.ui.OnboardingActivity @AndroidEntryPoint class MainActivity : AppCompatActivity() { private val viewModel: MailboxViewModel by viewModels() + private val nav: NavController by lazy { + val navHostFragment = + supportFragmentManager.findFragmentById(R.id.fragmentContainer) as NavHostFragment + navHostFragment.navController + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) viewModel.doNotKillComplete.observe(this) { complete -> - if (complete) showFragment(MainFragment()) + if (complete) nav.popBackStack() } if (savedInstanceState == null) { - val f = if (viewModel.needToShowDoNotKillMeFragment) { - DoNotKillMeFragment() - } else { - MainFragment() + if (viewModel.needToShowDoNotKillMeFragment) { + nav.navigate(R.id.action_mainFragment_to_doNotKillMeFragment) + } + if (!viewModel.isSetUp) { + Intent(this, OnboardingActivity::class.java).also { i -> + startActivity(i) + } } - showFragment(f) } } @@ -58,16 +68,10 @@ class MainActivity : AppCompatActivity() { } } - private fun showFragment(f: Fragment) { - supportFragmentManager.beginTransaction() - .replace(R.id.fragmentContainer, f) - .commitNow() - } - private fun showDozeDialog() = AlertDialog.Builder(this) .setMessage(R.string.warning_dozed) .setPositiveButton(R.string.fix) { dialog, _ -> - showFragment(DoNotKillMeFragment()) + nav.navigate(R.id.action_mainFragment_to_doNotKillMeFragment) dialog.dismiss() } .setNegativeButton(R.string.cancel) { dialog, _ -> dialog.dismiss() } 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 new file mode 100644 index 0000000000000000000000000000000000000000..6bf930936145b891229a5e50de702610ec5cd1cf --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/OnboardingActivity.kt @@ -0,0 +1,63 @@ +/* + * 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.os.Bundle +import androidx.activity.viewModels +import androidx.appcompat.app.AppCompatActivity +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) + 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 new file mode 100644 index 0000000000000000000000000000000000000000..6d5b409f9ef9a718e546d5f16a27fea6e688e5e5 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/OnboardingFragment.kt @@ -0,0 +1,151 @@ +/* + * 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.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.activity.OnBackPressedCallback +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import androidx.core.content.ContextCompat.getColorStateList +import androidx.core.widget.ImageViewCompat +import androidx.fragment.app.Fragment +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, + 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, +) + +class Onboarding2Fragment : OnboardingFragment( + number = 2, + icon = R.mipmap.ic_launcher_round, + title = R.string.onboarding_2_title, + description = R.string.onboarding_2_description, +) + +class Onboarding3Fragment : OnboardingFragment( + number = 3, + icon = R.mipmap.ic_launcher_round, + title = R.string.onboarding_3_title, + description = R.string.onboarding_3_description, +) + +abstract class OnboardingFragment( + private val number: Int, + @DrawableRes + private val icon: Int, + @StringRes + private val title: Int, + @StringRes + private val description: Int, + @StringRes + private val bottomButtonText: Int = R.string.button_back, + private val topButtonAction: OnboardingFragment.(OnboardingViewModel) -> Unit = { viewModel -> + viewModel.selectPage(number + 1) + }, + private val bottomButtonAction: OnboardingFragment.(OnboardingViewModel) -> Unit = { model -> + model.selectPage(number - 1) + }, +) : Fragment() { + + private val viewModel: OnboardingViewModel by activityViewModels() + private var _ui: FragmentOnboardingBinding? = null + + /** + * This property is only valid between [onCreateView] and [onDestroyView]. + */ + private val ui get() = _ui!! + + // This callback will only be called when this Fragment is at least Resumed, not Started. + private val callback = object : OnBackPressedCallback(false) { + override fun handleOnBackPressed() { + bottomButtonAction(viewModel) + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View? { + _ui = FragmentOnboardingBinding.inflate(inflater, container, false) + requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, callback) + return ui.root + } + + override fun onViewCreated(v: View, savedInstanceState: Bundle?) { + ui.icon.setImageResource(icon) + ui.title.setText(title) + ui.description.setText(description) + listOf(ui.bullet1, ui.bullet2, ui.bullet3, ui.bullet4).forEachIndexed { i, imageView -> + val color = if (i == number) R.color.briar_green else R.color.briar_night + val tintList = getColorStateList(requireContext(), color) + ImageViewCompat.setImageTintList(imageView, tintList) + } + ui.topButton.setOnClickListener { + topButtonAction(viewModel) + } + ui.bottomButton.setText(bottomButtonText) + ui.bottomButton.setOnClickListener { + bottomButtonAction(viewModel) + } + } + + override fun onResume() { + super.onResume() + callback.isEnabled = true + } + + override fun onPause() { + super.onPause() + callback.isEnabled = false + } + + override fun onDestroyView() { + super.onDestroyView() + _ui = null + } + +} + +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/java/org/briarproject/mailbox/core/system/AndroidTaskScheduler.java b/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidTaskScheduler.java index 7788735c0f21f59d6a29343128c83a996e0cf644..e5d6c29e38dafbb9bddc0195d21577766b5d828b 100644 --- a/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidTaskScheduler.java +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidTaskScheduler.java @@ -46,6 +46,7 @@ import javax.annotation.concurrent.ThreadSafe; import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP; import static android.app.AlarmManager.INTERVAL_FIFTEEN_MINUTES; import static android.app.PendingIntent.FLAG_CANCEL_CURRENT; +import static android.app.PendingIntent.FLAG_IMMUTABLE; import static android.content.Context.ALARM_SERVICE; import static android.os.Build.VERSION.SDK_INT; import static java.util.Objects.requireNonNull; @@ -194,8 +195,9 @@ public class AndroidTaskScheduler implements TaskScheduler, Service { private PendingIntent getAlarmPendingIntent() { Intent i = new Intent(app, AlarmReceiver.class); i.putExtra(EXTRA_PID, Process.myPid()); - return PendingIntent - .getBroadcast(app, REQUEST_ALARM, i, FLAG_CANCEL_CURRENT); + int flags = SDK_INT >= 23 ? FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE : + FLAG_CANCEL_CURRENT; + return PendingIntent.getBroadcast(app, REQUEST_ALARM, i, flags); } private class ScheduledTask diff --git a/mailbox-android/src/main/res/drawable/ic_circle.xml b/mailbox-android/src/main/res/drawable/ic_circle.xml new file mode 100644 index 0000000000000000000000000000000000000000..bf54d3e4994316b105c938b66738ec29df605a59 --- /dev/null +++ b/mailbox-android/src/main/res/drawable/ic_circle.xml @@ -0,0 +1,10 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?attr/colorControlNormal" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:fillColor="@android:color/white" + android:pathData="M12,2C6.47,2 2,6.47 2,12s4.47,10 10,10 10,-4.47 10,-10S17.53,2 12,2z" /> +</vector> diff --git a/mailbox-android/src/main/res/layout-land/fragment_onboarding.xml b/mailbox-android/src/main/res/layout-land/fragment_onboarding.xml new file mode 100644 index 0000000000000000000000000000000000000000..5faf30bc95743a2ac513b06a7978b48ccccb6705 --- /dev/null +++ b/mailbox-android/src/main/res/layout-land/fragment_onboarding.xml @@ -0,0 +1,159 @@ +<?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"> + + <ImageView + android:id="@+id/icon" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_margin="32dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintDimensionRatio="1:1" + app:layout_constraintEnd_toStartOf="@+id/guideline" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:srcCompat="@mipmap/ic_launcher" + tools:ignore="ContentDescription" /> + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/guideline" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + app:layout_constraintGuide_percent="0.5" /> + + <TextView + android:id="@+id/title" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_margin="16dp" + android:gravity="center" + android:textAppearance="@style/TextAppearance.Material3.HeadlineMedium" + app:layout_constraintBottom_toTopOf="@+id/description" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="@+id/guideline" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_chainStyle="packed" + tools:text="@string/onboarding_0_title" /> + + <TextView + android:id="@+id/description" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_margin="16dp" + android:textAppearance="@style/TextAppearance.Material3.BodyLarge" + app:layout_constraintBottom_toTopOf="@+id/bullet1" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="@+id/guideline" + app:layout_constraintTop_toBottomOf="@+id/title" + tools:text="@string/onboarding_0_description" /> + + <ImageView + android:id="@+id/bullet1" + android:layout_width="16dp" + android:layout_height="16dp" + android:layout_marginTop="16dp" + android:layout_marginEnd="4dp" + android:layout_marginRight="4dp" + android:layout_marginBottom="16dp" + app:layout_constraintBottom_toTopOf="@+id/topButton" + app:layout_constraintEnd_toStartOf="@id/bullet2" + app:layout_constraintHorizontal_chainStyle="packed" + app:layout_constraintStart_toStartOf="@+id/guideline" + app:layout_constraintTop_toBottomOf="@+id/description" + app:layout_constraintVertical_bias="1.0" + app:layout_constraintVertical_chainStyle="packed" + app:srcCompat="@drawable/ic_circle" + app:tint="?attr/colorSecondary" /> + + <ImageView + android:id="@+id/bullet2" + android:layout_width="16dp" + android:layout_height="16dp" + android:layout_marginStart="4dp" + android:layout_marginLeft="4dp" + android:layout_marginTop="16dp" + android:layout_marginEnd="4dp" + android:layout_marginRight="4dp" + android:layout_marginBottom="16dp" + app:layout_constraintBottom_toTopOf="@+id/topButton" + app:layout_constraintEnd_toStartOf="@id/bullet3" + app:layout_constraintStart_toEndOf="@+id/bullet1" + app:layout_constraintTop_toBottomOf="@+id/description" + app:layout_constraintVertical_bias="1.0" + app:layout_constraintVertical_chainStyle="packed" + app:srcCompat="@drawable/ic_circle" + app:tint="?attr/colorPrimary" /> + + <ImageView + android:id="@+id/bullet3" + android:layout_width="16dp" + android:layout_height="16dp" + android:layout_marginStart="4dp" + android:layout_marginLeft="4dp" + android:layout_marginTop="16dp" + android:layout_marginEnd="4dp" + android:layout_marginRight="4dp" + android:layout_marginBottom="16dp" + app:layout_constraintBottom_toTopOf="@+id/topButton" + app:layout_constraintEnd_toStartOf="@id/bullet4" + app:layout_constraintStart_toEndOf="@+id/bullet2" + app:layout_constraintTop_toBottomOf="@+id/description" + app:layout_constraintVertical_bias="1.0" + app:layout_constraintVertical_chainStyle="packed" + app:srcCompat="@drawable/ic_circle" + app:tint="?attr/colorPrimary" /> + + <ImageView + android:id="@+id/bullet4" + android:layout_width="16dp" + android:layout_height="16dp" + android:layout_marginStart="4dp" + android:layout_marginLeft="4dp" + android:layout_marginTop="16dp" + android:layout_marginEnd="16dp" + android:layout_marginRight="16dp" + android:layout_marginBottom="16dp" + app:layout_constraintBottom_toTopOf="@+id/topButton" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@+id/bullet3" + app:layout_constraintTop_toBottomOf="@+id/description" + app:layout_constraintVertical_bias="1.0" + app:layout_constraintVertical_chainStyle="packed" + app:srcCompat="@drawable/ic_circle" + app:tint="?attr/colorPrimary" /> + + <com.google.android.material.button.MaterialButton + android:id="@+id/topButton" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:layout_marginLeft="16dp" + android:layout_marginEnd="16dp" + android:layout_marginRight="16dp" + android:text="@string/button_continue" + app:layout_constraintBottom_toTopOf="@+id/bottomButton" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="@+id/guideline" + app:layout_constraintTop_toBottomOf="@+id/bullet1" + app:layout_constraintVertical_bias="1.0" /> + + <com.google.android.material.button.MaterialButton + android:id="@+id/bottomButton" + style="@style/TextButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:layout_marginLeft="16dp" + android:layout_marginEnd="16dp" + android:layout_marginRight="16dp" + android:text="@string/button_skip_intro" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="@+id/guideline" + app:layout_constraintTop_toBottomOf="@+id/topButton" /> + +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/mailbox-android/src/main/res/layout/activity_main.xml b/mailbox-android/src/main/res/layout/activity_main.xml index 0e985efea7541f7fc4564c0403128f4b0d668dbe..d8d6ae61cc5fef38779d2d922b3e48d915bd9833 100644 --- a/mailbox-android/src/main/res/layout/activity_main.xml +++ b/mailbox-android/src/main/res/layout/activity_main.xml @@ -1,7 +1,11 @@ <?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" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/fragmentContainer" + android:name="androidx.navigation.fragment.NavHostFragment" android:layout_width="match_parent" android:layout_height="match_parent" + app:defaultNavHost="true" + app:navGraph="@navigation/nav_main" tools:context=".android.MainActivity" /> diff --git a/mailbox-android/src/main/res/layout/activity_onboarding.xml b/mailbox-android/src/main/res/layout/activity_onboarding.xml new file mode 100644 index 0000000000000000000000000000000000000000..4aca0f57cf228b9a7ccfae6e9a1859de751859f9 --- /dev/null +++ b/mailbox-android/src/main/res/layout/activity_onboarding.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.viewpager2.widget.ViewPager2 xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/pager" + android:layout_width="match_parent" + android:layout_height="match_parent" + 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 new file mode 100644 index 0000000000000000000000000000000000000000..47da6fef422a4b9d91dd619e7e334900cc2fc928 --- /dev/null +++ b/mailbox-android/src/main/res/layout/fragment_onboarding.xml @@ -0,0 +1,138 @@ +<?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"> + + <ImageView + android:id="@+id/icon" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_margin="32dp" + app:layout_constraintBottom_toTopOf="@+id/title" + app:layout_constraintDimensionRatio="1:1" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:srcCompat="@mipmap/ic_launcher" + tools:ignore="ContentDescription" /> + + <TextView + android:id="@+id/title" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_margin="16dp" + android:gravity="center" + android:textAppearance="@style/TextAppearance.Material3.HeadlineMedium" + app:layout_constraintBottom_toTopOf="@+id/description" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/icon" + app:layout_constraintVertical_chainStyle="packed" + tools:text="@string/onboarding_0_title" /> + + <TextView + android:id="@+id/description" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_margin="16dp" + android:textAppearance="@style/TextAppearance.Material3.BodyLarge" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/title" + tools:text="@string/onboarding_0_description" /> + + <ImageView + android:id="@+id/bullet1" + android:layout_width="16dp" + android:layout_height="16dp" + android:layout_marginStart="16dp" + android:layout_marginLeft="16dp" + android:layout_marginTop="16dp" + android:layout_marginEnd="4dp" + android:layout_marginRight="4dp" + android:layout_marginBottom="16dp" + app:layout_constraintBottom_toTopOf="@+id/topButton" + app:layout_constraintEnd_toStartOf="@id/bullet2" + app:layout_constraintHorizontal_chainStyle="packed" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/description" + app:layout_constraintVertical_bias="1.0" + app:layout_constraintVertical_chainStyle="packed" + app:srcCompat="@drawable/ic_circle" + app:tint="?attr/colorSecondary" /> + + <ImageView + android:id="@+id/bullet2" + android:layout_width="16dp" + android:layout_height="16dp" + android:layout_marginStart="4dp" + android:layout_marginLeft="4dp" + android:layout_marginEnd="4dp" + android:layout_marginRight="4dp" + android:layout_marginBottom="16dp" + app:layout_constraintBottom_toTopOf="@+id/topButton" + app:layout_constraintEnd_toStartOf="@id/bullet3" + app:layout_constraintStart_toEndOf="@+id/bullet1" + app:layout_constraintTop_toTopOf="@+id/bullet1" + app:srcCompat="@drawable/ic_circle" + app:tint="?attr/colorPrimary" /> + + <ImageView + android:id="@+id/bullet3" + android:layout_width="16dp" + android:layout_height="16dp" + android:layout_marginStart="4dp" + android:layout_marginLeft="4dp" + android:layout_marginEnd="4dp" + android:layout_marginRight="4dp" + android:layout_marginBottom="16dp" + app:layout_constraintBottom_toTopOf="@+id/topButton" + app:layout_constraintEnd_toStartOf="@id/bullet4" + app:layout_constraintStart_toEndOf="@+id/bullet2" + app:layout_constraintTop_toTopOf="@+id/bullet1" + app:srcCompat="@drawable/ic_circle" + app:tint="?attr/colorPrimary" /> + + <ImageView + android:id="@+id/bullet4" + android:layout_width="16dp" + android:layout_height="16dp" + android:layout_marginStart="4dp" + android:layout_marginLeft="4dp" + android:layout_marginEnd="16dp" + android:layout_marginRight="16dp" + android:layout_marginBottom="16dp" + app:layout_constraintBottom_toTopOf="@+id/topButton" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@+id/bullet3" + app:layout_constraintTop_toTopOf="@+id/bullet1" + app:srcCompat="@drawable/ic_circle" + app:tint="?attr/colorPrimary" /> + + <com.google.android.material.button.MaterialButton + android:id="@+id/topButton" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:layout_marginEnd="16dp" + android:text="@string/button_continue" + app:layout_constraintBottom_toTopOf="@+id/bottomButton" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/bullet1" /> + + <com.google.android.material.button.MaterialButton + android:id="@+id/bottomButton" + style="@style/TextButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/button_skip_intro" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/topButton" /> + +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/mailbox-android/src/main/res/navigation/nav_main.xml b/mailbox-android/src/main/res/navigation/nav_main.xml new file mode 100644 index 0000000000000000000000000000000000000000..2a91515276cb7f5c93beb2eefed9db87642dba9e --- /dev/null +++ b/mailbox-android/src/main/res/navigation/nav_main.xml @@ -0,0 +1,19 @@ +<?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" + android:id="@+id/nav_onboarding" + app:startDestination="@id/mainFragment"> + + <fragment + android:id="@+id/mainFragment" + android:name="org.briarproject.mailbox.android.MainFragment" + android:label="MainFragment"> + <action + android:id="@+id/action_mainFragment_to_doNotKillMeFragment" + app:destination="@id/doNotKillMeFragment" /> + </fragment> + <fragment + android:id="@+id/doNotKillMeFragment" + android:name="org.briarproject.mailbox.android.DoNotKillMeFragment" + android:label="DoNotKillMeFragment" /> +</navigation> diff --git a/mailbox-android/src/main/res/values-night/themes.xml b/mailbox-android/src/main/res/values-night/themes.xml index 8b6f2d272f57a825e5e3f2bbf67c1443a2df7b40..c26ed2644ebb71c59260eccde6672d4140837209 100644 --- a/mailbox-android/src/main/res/values-night/themes.xml +++ b/mailbox-android/src/main/res/values-night/themes.xml @@ -1,10 +1,13 @@ -<resources> +<resources xmlns:tools="http://schemas.android.com/tools"> <!-- Base application theme. --> - <style name="Theme.Briarmailbox" parent="Theme.AppCompat.Light.DarkActionBar"> + <style name="Theme.BriarMailbox" parent="Theme.Material3.DayNight.NoActionBar"> <!-- Primary brand color. --> - <item name="colorPrimary">@color/purple_200</item> - <item name="colorPrimaryDark">@color/purple_700</item> - <item name="colorAccent">@color/teal_200</item> + <item name="colorPrimary">@color/briar_night</item> + <item name="colorPrimaryDark">#1F78D1</item> + <item name="colorSecondary">@color/briar_green</item> + <item name="colorOnPrimary">@color/white</item> <!-- Customize your theme here. --> + <item name="android:background">@color/black</item> + <item name="android:statusBarColor" tools:targetApi="21">?attr/background</item> </style> -</resources> \ No newline at end of file +</resources> diff --git a/mailbox-android/src/main/res/values/colors.xml b/mailbox-android/src/main/res/values/colors.xml index f8c6127d327620c93d2b2d00342a68e97b98a48d..a0aca040da2567448f5db9852c16e3baed8963e7 100644 --- a/mailbox-android/src/main/res/values/colors.xml +++ b/mailbox-android/src/main/res/values/colors.xml @@ -1,10 +1,10 @@ <?xml version="1.0" encoding="utf-8"?> <resources> - <color name="purple_200">#FFBB86FC</color> - <color name="purple_500">#FF6200EE</color> - <color name="purple_700">#FF3700B3</color> - <color name="teal_200">#FF03DAC5</color> - <color name="teal_700">#FF018786</color> + <color name="briar_green">#74B816</color> + <color name="briar_night">#435B77</color> + <color name="briar_blue">#1F78D1</color> + <color name="briar_blue_dark">#1A222D</color> + <color name="black">#FF000000</color> <color name="white">#FFFFFFFF</color> -</resources> \ No newline at end of file +</resources> diff --git a/mailbox-android/src/main/res/values/strings.xml b/mailbox-android/src/main/res/values/strings.xml index 3622cbb1e26cd8a1e3057fbae1e02afedf0c257c..094a73711833657cb9952cb484e07b0f5cf1f9bd 100644 --- a/mailbox-android/src/main/res/values/strings.xml +++ b/mailbox-android/src/main/res/values/strings.xml @@ -6,6 +6,18 @@ <string name="start">Start mailbox</string> <string name="stop">Stop mailbox</string> <string name="wipe">Wipe mailbox</string> + <string name="button_continue">Continue</string> + <string name="button_skip_intro">Skip Intro</string> + <string name="button_back">Back</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. --> diff --git a/mailbox-android/src/main/res/values/styles.xml b/mailbox-android/src/main/res/values/styles.xml new file mode 100644 index 0000000000000000000000000000000000000000..e547d159db5ad01f164b7431e754dadc57e4ab0b --- /dev/null +++ b/mailbox-android/src/main/res/values/styles.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + + <style name="TextButton" parent="Widget.Material3.Button.TextButton"> + <item name="android:textColor">?attr/colorSecondary</item> + </style> + +</resources> \ No newline at end of file diff --git a/mailbox-android/src/main/res/values/themes.xml b/mailbox-android/src/main/res/values/themes.xml index 9d0dfedbc6008d4403b648f4fb6c3393cba66d18..50018ee4c92df82f18f0f26ac5e818cbef161d79 100644 --- a/mailbox-android/src/main/res/values/themes.xml +++ b/mailbox-android/src/main/res/values/themes.xml @@ -1,10 +1,12 @@ -<resources> +<resources xmlns:tools="http://schemas.android.com/tools"> <!-- Base application theme. --> - <style name="Theme.Briarmailbox" parent="Theme.AppCompat.Light.DarkActionBar"> + <style name="Theme.BriarMailbox" parent="Theme.Material3.DayNight.NoActionBar"> <!-- Primary brand color. --> - <item name="colorPrimary">@color/purple_500</item> - <item name="colorPrimaryDark">@color/purple_700</item> - <item name="colorAccent">@color/teal_200</item> + <item name="colorPrimary">@color/briar_night</item> + <item name="colorPrimaryDark">@color/briar_blue</item> + <item name="colorSecondary">@color/briar_green</item> <!-- Customize your theme here. --> + <item name="android:statusBarColor" tools:targetApi="21">@color/white</item> + <item name="android:windowLightStatusBar" tools:targetApi="23">true</item> </style> -</resources> \ No newline at end of file +</resources> diff --git a/mailbox-core/build.gradle b/mailbox-core/build.gradle index 685b418bf0b719f095559836a5cf4bf1b04b64bf..55b5959e0a1785703ebf16a0fbe6e4aad0ee8f63 100644 --- a/mailbox-core/build.gradle +++ b/mailbox-core/build.gradle @@ -14,6 +14,7 @@ dependencies { api "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" api 'com.google.code.findbugs:jsr305:3.0.2' api 'javax.inject:javax.inject:1' // required for @Qualifier in @Wakeful + implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" // used by jackson implementation "com.google.dagger:hilt-core:$hilt_version" kapt "com.google.dagger:dagger-compiler:$hilt_version"