diff --git a/build.gradle b/build.gradle index 536485f0f25ff50270b4a685e6269cdaf92e1240..57f585334b459cbaadb3af645fc1368e4f8cdb57 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,7 @@ buildscript { ext.kotlin_version = '1.6.10' ext.hilt_version = '2.40' + ext.nav_version = '2.4.0' ext.tor_version = '0.3.5.15' ext.obfs4_version = '0.0.12-dev-40245c4a' ext.junit_version = '5.7.2' @@ -18,6 +19,7 @@ buildscript { 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" + classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version" } } diff --git a/mailbox-android/build.gradle b/mailbox-android/build.gradle index 744978cd48c4a17c8b4ca50a2abe1ac1d443f6c7..b692d5392654f4d934b7f2d2dd8a9f47cd2bb069 100644 --- a/mailbox-android/build.gradle +++ b/mailbox-android/build.gradle @@ -2,6 +2,7 @@ import com.android.build.gradle.tasks.MergeResources plugins { id 'com.android.application' + id 'androidx.navigation.safeargs' id 'kotlin-android' id 'kotlin-kapt' id 'dagger.hilt.android.plugin' @@ -69,7 +70,6 @@ dependencies { 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" diff --git a/mailbox-android/src/main/AndroidManifest.xml b/mailbox-android/src/main/AndroidManifest.xml index 4466f33c0ae170dc3a788afb1c808dce5cbeb4ac..c763be4838e818e3cc332f4d537e67503c833a37 100644 --- a/mailbox-android/src/main/AndroidManifest.xml +++ b/mailbox-android/src/main/AndroidManifest.xml @@ -25,7 +25,7 @@ <service android:name=".android.MailboxService" /> <activity - android:name=".android.MainActivity" + android:name=".android.ui.MainActivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/ApplicationComponent.kt b/mailbox-android/src/main/java/org/briarproject/mailbox/android/ApplicationComponent.kt index b03d4581b6a129cc44ce68d41b13263e6faac2ba..820f0a1a3fbf57369a7a7e3f9936b46072361f68 100644 --- a/mailbox-android/src/main/java/org/briarproject/mailbox/android/ApplicationComponent.kt +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/android/ApplicationComponent.kt @@ -20,6 +20,7 @@ package org.briarproject.mailbox.android import dagger.Component +import org.briarproject.mailbox.android.ui.MainActivity @Component( modules = [ 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 2673e52e8beb121e19d18276954a3d6e29694e47..6d8eda6b9ea5d5ecaa45e5c72dea991dd95400a2 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 @@ -34,6 +34,7 @@ import androidx.core.app.NotificationCompat.PRIORITY_MIN import androidx.core.content.ContextCompat.getSystemService import dagger.hilt.android.qualifiers.ApplicationContext import org.briarproject.mailbox.R +import org.briarproject.mailbox.android.ui.MainActivity import javax.inject.Inject import javax.inject.Singleton diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/QrCodeUtils.kt b/mailbox-android/src/main/java/org/briarproject/mailbox/android/QrCodeUtils.kt new file mode 100644 index 0000000000000000000000000000000000000000..c8448c8c33f94364a8789c005f1c6cd940fc1379 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/android/QrCodeUtils.kt @@ -0,0 +1,39 @@ +/* + * 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 + +import com.google.zxing.common.BitMatrix +import android.graphics.Bitmap +import android.graphics.Color + +object QrCodeUtils { + fun renderQrCode(matrix: BitMatrix): Bitmap { + val width = matrix.width + val height = matrix.height + val pixels = IntArray(width * height) + for (x in 0 until width) { + for (y in 0 until height) { + pixels[y * width + x] = if (matrix[x, y]) Color.BLACK else Color.WHITE + } + } + val qr = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) + qr.setPixels(pixels, 0, width, 0, 0, width, height) + return qr + } +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/dontkillme/DoNotKillMeFragment.kt b/mailbox-android/src/main/java/org/briarproject/mailbox/android/dontkillme/DoNotKillMeFragment.kt index 5c64d6b7059290f1bbf3af76c71d3db4b41bb2b5..06a81ccf19b60bd18388d5a9152424a85f7c2bc6 100644 --- a/mailbox-android/src/main/java/org/briarproject/mailbox/android/dontkillme/DoNotKillMeFragment.kt +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/android/dontkillme/DoNotKillMeFragment.kt @@ -21,7 +21,7 @@ package org.briarproject.mailbox.android.dontkillme import androidx.fragment.app.activityViewModels import dagger.hilt.android.AndroidEntryPoint -import org.briarproject.mailbox.android.MailboxViewModel +import org.briarproject.mailbox.android.ui.MailboxViewModel @AndroidEntryPoint class DoNotKillMeFragment : AbstractDoNotKillMeFragment() { diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/InitFragment.kt b/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/InitFragment.kt new file mode 100644 index 0000000000000000000000000000000000000000..0c6962e14f99e649f75ceca6458fc478d3dbb93c --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/InitFragment.kt @@ -0,0 +1,58 @@ +/* + * 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 android.view.animation.AlphaAnimation +import android.widget.ImageView +import android.widget.TextView +import androidx.fragment.app.Fragment +import dagger.hilt.android.AndroidEntryPoint +import org.briarproject.mailbox.R + +@AndroidEntryPoint +class InitFragment : Fragment() { + + private lateinit var logo: ImageView + private lateinit var text: TextView + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View? { + return inflater.inflate(R.layout.fragment_init, container, false) + } + + override fun onViewCreated(v: View, savedInstanceState: Bundle?) { + logo = v.findViewById(R.id.logo) + text = v.findViewById(R.id.text) + + val fadeIn = AlphaAnimation(0f, 1f) + fadeIn.duration = 2000 + fadeIn.fillAfter = true + logo.startAnimation(fadeIn) + text.startAnimation(fadeIn) + } + +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxViewModel.kt b/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/MailboxViewModel.kt similarity index 52% rename from mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxViewModel.kt rename to mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/MailboxViewModel.kt index 48661e9defc72c33c03b49f600c9d892530d7d0b..921c1b84b270999e5ad8b413b58a9672afa58201 100644 --- a/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxViewModel.kt +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/MailboxViewModel.kt @@ -17,32 +17,44 @@ * */ -package org.briarproject.mailbox.android +package org.briarproject.mailbox.android.ui import android.app.Application +import android.content.res.Resources +import android.graphics.Bitmap import androidx.annotation.UiThread import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.liveData import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flowOn import org.briarproject.android.dontkillmelib.DozeHelper +import org.briarproject.mailbox.android.MailboxService +import org.briarproject.mailbox.android.QrCodeUtils import org.briarproject.mailbox.core.lifecycle.LifecycleManager import org.briarproject.mailbox.core.lifecycle.LifecycleManager.LifecycleState +import org.briarproject.mailbox.core.setup.QrCodeEncoder +import org.briarproject.mailbox.core.setup.SetupComplete import org.briarproject.mailbox.core.setup.SetupManager import org.briarproject.mailbox.core.system.DozeWatchdog +import org.briarproject.mailbox.core.tor.TorPlugin import javax.inject.Inject import kotlin.concurrent.thread +import kotlin.math.min @HiltViewModel class MailboxViewModel @Inject constructor( private val app: Application, private val dozeHelper: DozeHelper, private val dozeWatchdog: DozeWatchdog, - handle: SavedStateHandle, private val lifecycleManager: LifecycleManager, private val setupManager: SetupManager, + private val qrCodeEncoder: QrCodeEncoder, + torPlugin: TorPlugin, ) : AndroidViewModel(app) { val needToShowDoNotKillMeFragment get() = dozeHelper.needToShowDoNotKillMeFragment(app) @@ -50,12 +62,41 @@ class MailboxViewModel @Inject constructor( private val _doNotKillComplete = MutableLiveData<Boolean>() val doNotKillComplete: LiveData<Boolean> = _doNotKillComplete - private val _text = handle.getLiveData("text", "Hello Mailbox") - val text: LiveData<String> = _text + private val lifecycleState: StateFlow<LifecycleState> = lifecycleManager.lifecycleStateFlow + private val torPluginState: StateFlow<TorPlugin.State> = torPlugin.state - val lifecycleState: StateFlow<LifecycleState> = lifecycleManager.lifecycleStateFlow + val hasDb: LiveData<Boolean> = liveData(Dispatchers.IO) { emit(setupManager.hasDb) } - val isSetUp: Boolean get() = setupManager.hasDb + /** + * Possible values for [setupState] + */ + sealed interface MailboxStartupProgress + class Starting(val status: String) : MailboxStartupProgress + class StartedSettingUp(val qrCode: Bitmap) : MailboxStartupProgress + object StartedSetupComplete : MailboxStartupProgress + + val setupState = combine( + lifecycleState, torPluginState, setupManager.setupComplete + ) { ls, ts, sc -> + when { + ls != LifecycleState.RUNNING -> Starting(ls.name) + // TODO waiting for ACTIVE is better than not doing it but to fix #90 we need to listen for + // upload events to the hsdirs + ts != TorPlugin.State.ACTIVE -> Starting(ts.name + " TOR") + sc == SetupComplete.FALSE -> { + val dm = Resources.getSystem().displayMetrics + val size = min(dm.widthPixels, dm.heightPixels) + val bitMatrix = qrCodeEncoder.getQrCodeBitMatrix(size) + StartedSettingUp( + bitMatrix?.let { it -> QrCodeUtils.renderQrCode(it) } + ?: error("The QR code bit matrix is expected to be non-null here") + ) + } + sc == SetupComplete.TRUE -> StartedSetupComplete + // else means sc == SetupComplete.UNKNOWN + else -> error("Expected setup completion to be known at this point") + } + }.flowOn(Dispatchers.IO) @UiThread fun onDoNotKillComplete() { @@ -80,8 +121,4 @@ class MailboxViewModel @Inject constructor( fun getAndResetDozeFlag() = dozeWatchdog.andResetDozeFlag - fun updateText(str: String) { - _text.value = str - } - } diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/MainActivity.kt b/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/MainActivity.kt similarity index 61% rename from mailbox-android/src/main/java/org/briarproject/mailbox/android/MainActivity.kt rename to mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/MainActivity.kt index 1aad5c7f10dd0a913033919c3ab4eef732e32d22..88cabeb693100c67aac7243cbc15d2503936849c 100644 --- a/mailbox-android/src/main/java/org/briarproject/mailbox/android/MainActivity.kt +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/MainActivity.kt @@ -17,10 +17,13 @@ * */ -package org.briarproject.mailbox.android +package org.briarproject.mailbox.android.ui import android.content.Intent import android.os.Bundle +import androidx.activity.result.ActivityResult +import androidx.activity.result.ActivityResultCallback +import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult import androidx.activity.viewModels import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity @@ -29,10 +32,12 @@ 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 +import org.briarproject.mailbox.android.dontkillme.DoNotKillMeFragmentDirections.actionDoNotKillMeFragmentToStartupFragment +import org.briarproject.mailbox.android.ui.InitFragmentDirections.actionInitFragmentToDoNotKillMeFragment +import org.briarproject.mailbox.android.ui.InitFragmentDirections.actionInitFragmentToStartupFragment @AndroidEntryPoint -class MainActivity : AppCompatActivity() { +class MainActivity : AppCompatActivity(), ActivityResultCallback<ActivityResult> { private val viewModel: MailboxViewModel by viewModels() private val nav: NavController by lazy { @@ -41,26 +46,35 @@ class MainActivity : AppCompatActivity() { navHostFragment.navController } + private val startForResult = registerForActivityResult(StartActivityForResult(), this) + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) viewModel.doNotKillComplete.observe(this) { complete -> - if (complete) nav.popBackStack() + if (complete) nav.navigate(actionDoNotKillMeFragmentToStartupFragment()) } if (savedInstanceState == null) { - if (viewModel.needToShowDoNotKillMeFragment) { - nav.navigate(R.id.action_mainFragment_to_doNotKillMeFragment) - } - if (!viewModel.isSetUp) { - Intent(this, OnboardingActivity::class.java).also { i -> - startActivity(i) + viewModel.hasDb.observe(this) { hasDb -> + if (!hasDb) { + startForResult.launch(Intent(this, OnboardingActivity::class.java)) + } else { + nav.navigate(actionInitFragmentToStartupFragment()) } } } } + override fun onActivityResult(result: ActivityResult?) { + if (viewModel.needToShowDoNotKillMeFragment) { + nav.navigate(actionInitFragmentToDoNotKillMeFragment()) + } else { + nav.navigate(actionInitFragmentToStartupFragment()) + } + } + override fun onResume() { super.onResume() if (needsDozeWhitelisting(this) && viewModel.getAndResetDozeFlag()) { @@ -71,7 +85,7 @@ class MainActivity : AppCompatActivity() { private fun showDozeDialog() = AlertDialog.Builder(this) .setMessage(R.string.warning_dozed) .setPositiveButton(R.string.fix) { dialog, _ -> - nav.navigate(R.id.action_mainFragment_to_doNotKillMeFragment) + nav.navigate(actionInitFragmentToDoNotKillMeFragment()) dialog.dismiss() } .setNegativeButton(R.string.cancel) { dialog, _ -> dialog.dismiss() } 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 6d5b409f9ef9a718e546d5f16a27fea6e688e5e5..5bebab18ab67029f8390cc729dd0427bc666887c 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 @@ -42,6 +42,9 @@ class Onboarding0Fragment : OnboardingFragment( bottomButtonAction = { requireActivity().supportFinishAfterTransition() }, + backButtonAction = { + requireActivity().finishAffinity() + }, ) class Onboarding1Fragment : OnboardingFragment( @@ -81,6 +84,8 @@ abstract class OnboardingFragment( private val bottomButtonAction: OnboardingFragment.(OnboardingViewModel) -> Unit = { model -> model.selectPage(number - 1) }, + private val backButtonAction: OnboardingFragment.(OnboardingViewModel) -> Unit = + bottomButtonAction, ) : Fragment() { private val viewModel: OnboardingViewModel by activityViewModels() @@ -94,7 +99,7 @@ abstract class OnboardingFragment( // 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) + backButtonAction(viewModel) } } diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/QrCodeFragment.kt b/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/QrCodeFragment.kt new file mode 100644 index 0000000000000000000000000000000000000000..42060545dde1558b766bc589d0f8b54d1d27138f --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/QrCodeFragment.kt @@ -0,0 +1,90 @@ +/* + * 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 android.widget.Button +import android.widget.ImageView +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import androidx.navigation.fragment.findNavController +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch +import org.briarproject.mailbox.R +import org.briarproject.mailbox.android.ui.MailboxViewModel.MailboxStartupProgress +import org.briarproject.mailbox.android.ui.MailboxViewModel.StartedSettingUp +import org.briarproject.mailbox.android.ui.MailboxViewModel.StartedSetupComplete +import org.briarproject.mailbox.android.ui.QrCodeFragmentDirections.actionQrCodeFragmentToSetupCompleteFragment + +@AndroidEntryPoint +class QrCodeFragment : Fragment() { + + private val viewModel: MailboxViewModel by activityViewModels() + private lateinit var qrCodeView: ImageView + private lateinit var buttonCancel: Button + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View? { + return inflater.inflate(R.layout.fragment_qr, container, false) + } + + override fun onViewCreated(v: View, savedInstanceState: Bundle?) { + qrCodeView = v.findViewById(R.id.qrcode) + buttonCancel = v.findViewById(R.id.buttonCancel) + + buttonCancel.setOnClickListener { + viewModel.stopLifecycle() + requireActivity().finishAffinity() + } + + // Start a coroutine in the lifecycle scope + viewLifecycleOwner.lifecycleScope.launch { + // repeatOnLifecycle launches the block in a new coroutine every time the + // lifecycle is in the STARTED state (or above) and cancels it when it's STOPPED. + viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { + // Trigger the flow and start listening for values. + // Note that this happens when lifecycle is STARTED and stops + // collecting when the lifecycle is STOPPED + viewModel.setupState.collect { onSetupStateChanged(it) } + } + } + } + + private fun onSetupStateChanged(setupComplete: MailboxStartupProgress) { + when (setupComplete) { + is StartedSettingUp -> qrCodeView.setImageBitmap(setupComplete.qrCode) + is StartedSetupComplete -> findNavController().navigate( + actionQrCodeFragmentToSetupCompleteFragment() + ) + else -> error("Unexpected setup state: $setupComplete") + } + } + +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/SetupCompleteFragment.kt b/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/SetupCompleteFragment.kt new file mode 100644 index 0000000000000000000000000000000000000000..c5867be9a787403a125b63d26e43c50c4bc850ed --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/SetupCompleteFragment.kt @@ -0,0 +1,55 @@ +/* + * 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 android.widget.Button +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.navigation.fragment.findNavController +import dagger.hilt.android.AndroidEntryPoint +import org.briarproject.mailbox.R +import org.briarproject.mailbox.android.ui.SetupCompleteFragmentDirections.actionSetupCompleteFragmentToStatusFragment + +@AndroidEntryPoint +class SetupCompleteFragment : Fragment() { + + private val viewModel: MailboxViewModel by activityViewModels() + private lateinit var button: Button + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View? { + return inflater.inflate(R.layout.fragment_setup_complete, container, false) + } + + override fun onViewCreated(v: View, savedInstanceState: Bundle?) { + button = v.findViewById(R.id.button) + button.setOnClickListener { + findNavController().navigate(actionSetupCompleteFragmentToStatusFragment()) + } + } + +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/MainFragment.kt b/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/StartupFragment.kt similarity index 54% rename from mailbox-android/src/main/java/org/briarproject/mailbox/android/MainFragment.kt rename to mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/StartupFragment.kt index 6b5dab13b79a37f93aa46363e5f9f922b30209e4..a707919ad61d5b03afe8e9afe7566ac7a198c454 100644 --- a/mailbox-android/src/main/java/org/briarproject/mailbox/android/MainFragment.kt +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/StartupFragment.kt @@ -17,88 +17,70 @@ * */ -package org.briarproject.mailbox.android +package org.briarproject.mailbox.android.ui import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.Button import android.widget.TextView import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle +import androidx.navigation.fragment.findNavController import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch import org.briarproject.mailbox.R -import org.briarproject.mailbox.core.lifecycle.LifecycleManager +import org.briarproject.mailbox.android.ui.MailboxViewModel.MailboxStartupProgress +import org.briarproject.mailbox.android.ui.MailboxViewModel.StartedSettingUp +import org.briarproject.mailbox.android.ui.MailboxViewModel.Starting +import org.briarproject.mailbox.android.ui.StartupFragmentDirections.actionStartupFragmentToQrCodeFragment +import org.briarproject.mailbox.android.ui.StartupFragmentDirections.actionStartupFragmentToStatusFragment @AndroidEntryPoint -class MainFragment : Fragment() { +class StartupFragment : Fragment() { private val viewModel: MailboxViewModel by activityViewModels() private lateinit var statusTextView: TextView - private lateinit var startStopButton: Button - private lateinit var wipeButton: Button override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?, ): View? { - return inflater.inflate(R.layout.fragment_main, container, false) + return inflater.inflate(R.layout.fragment_startup, container, false) } override fun onViewCreated(v: View, savedInstanceState: Bundle?) { - val textView = v.findViewById<TextView>(R.id.text) - val button = v.findViewById<Button>(R.id.button) statusTextView = v.findViewById(R.id.statusTextView) - startStopButton = v.findViewById(R.id.startStopButton) - wipeButton = v.findViewById(R.id.wipeButton) - - button.setOnClickListener { - viewModel.updateText("Tested") - } // Start a coroutine in the lifecycle scope - lifecycleScope.launch { + viewLifecycleOwner.lifecycleScope.launch { // repeatOnLifecycle launches the block in a new coroutine every time the // lifecycle is in the STARTED state (or above) and cancels it when it's STOPPED. - repeatOnLifecycle(Lifecycle.State.STARTED) { + viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { // Trigger the flow and start listening for values. // Note that this happens when lifecycle is STARTED and stops // collecting when the lifecycle is STOPPED - viewModel.lifecycleState.collect { onLifecycleStateChanged(it) } + viewModel.setupState.collect { onSetupStateChanged(it) } } } - viewModel.text.observe(viewLifecycleOwner, { text -> - textView.text = text - }) + viewModel.startLifecycle() } - private fun onLifecycleStateChanged(state: LifecycleManager.LifecycleState) = when (state) { - LifecycleManager.LifecycleState.NOT_STARTED -> { - statusTextView.text = state.name - startStopButton.setText(R.string.start) - startStopButton.setOnClickListener { viewModel.startLifecycle() } - startStopButton.isEnabled = true - } - LifecycleManager.LifecycleState.RUNNING -> { - statusTextView.text = state.name - startStopButton.setText(R.string.stop) - startStopButton.setOnClickListener { viewModel.stopLifecycle() } - wipeButton.setOnClickListener { viewModel.wipe() } - startStopButton.isEnabled = true - wipeButton.isEnabled = true - } - else -> { - statusTextView.text = state.name - startStopButton.isEnabled = false - wipeButton.isEnabled = false + private fun onSetupStateChanged(state: MailboxStartupProgress) { + when (state) { + is Starting -> statusTextView.text = state.status + is StartedSettingUp -> findNavController().navigate( + actionStartupFragmentToQrCodeFragment() + ) + is MailboxViewModel.StartedSetupComplete -> findNavController().navigate( + actionStartupFragmentToStatusFragment() + ) } } diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/StatusFragment.kt b/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/StatusFragment.kt new file mode 100644 index 0000000000000000000000000000000000000000..3aa070662bc24cb51290c8bcc78647b5fc4982ad --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/StatusFragment.kt @@ -0,0 +1,53 @@ +/* + * 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 android.widget.Button +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import dagger.hilt.android.AndroidEntryPoint +import org.briarproject.mailbox.R + +@AndroidEntryPoint +class StatusFragment : Fragment() { + + private val viewModel: MailboxViewModel by activityViewModels() + private lateinit var buttonStop: Button + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View? { + return inflater.inflate(R.layout.fragment_status, container, false) + } + + override fun onViewCreated(v: View, savedInstanceState: Bundle?) { + buttonStop = v.findViewById(R.id.buttonStop) + buttonStop.setOnClickListener { + viewModel.stopLifecycle() + } + } + +} diff --git a/mailbox-android/src/main/res/drawable/ic_mailbox.xml b/mailbox-android/src/main/res/drawable/ic_mailbox.xml new file mode 100644 index 0000000000000000000000000000000000000000..12df38ef8607c88bb61628f6aa2a53e2ba8f8af9 --- /dev/null +++ b/mailbox-android/src/main/res/drawable/ic_mailbox.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="30dp" + android:height="34dp" + android:viewportWidth="30" + android:viewportHeight="34"> + <path + android:pathData="M3.334,0C2.4499,-0 1.6017,0.3579 0.9766,0.9941C0.3514,1.6304 0,2.4928 0,3.3926L0,25.1055C0,26.0053 0.3514,26.8696 0.9766,27.5059C1.6017,28.1421 2.4499,28.498 3.334,28.498L21.4902,28.498L27.5391,33.6426C28.5157,34.4732 30,33.766 30,32.4707L30,3.3926C30,2.4928 29.6485,1.6304 29.0234,0.9941C28.3983,0.3579 27.55,0 26.666,0L3.334,0zM5.334,5.4277L24.666,5.4277L24.666,15.0332L17.9121,15.0332L17.9121,18.3555L20.3828,18.3555C20.8283,18.3555 21.0512,18.904 20.7363,19.2246L15.4102,24.6445C15.2149,24.8432 14.8984,24.8432 14.7031,24.6445L9.377,19.2246C9.062,18.904 9.285,18.3555 9.7305,18.3555L12.2012,18.3555L12.2012,15.0332L5.334,15.0332L5.334,5.4277z" + android:fillColor="#ffffff"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/ic_square.xml b/mailbox-android/src/main/res/drawable/ic_square.xml new file mode 100644 index 0000000000000000000000000000000000000000..07e62993e919890cb5481604b5891c6a72d95f42 --- /dev/null +++ b/mailbox-android/src/main/res/drawable/ic_square.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="M0,0L0,24 24,24 24,0z" /> +</vector> diff --git a/mailbox-android/src/main/res/layout-land/fragment_qr.xml b/mailbox-android/src/main/res/layout-land/fragment_qr.xml new file mode 100644 index 0000000000000000000000000000000000000000..b4b86f48001e51a30aaa62192c271b13c9435758 --- /dev/null +++ b/mailbox-android/src/main/res/layout-land/fragment_qr.xml @@ -0,0 +1,69 @@ +<?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" + tools:context=".android.ui.MainActivity"> + + <TextView + android:id="@+id/headline" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:paddingHorizontal="16dp" + android:gravity="center_horizontal" + android:text="@string/link_title" + android:textAlignment="center" + android:textSize="32sp" + app:layout_constraintBottom_toTopOf="@id/description" + app:layout_constraintEnd_toStartOf="@id/card" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <TextView + android:id="@+id/description" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:paddingHorizontal="16dp" + android:gravity="center_horizontal" + android:text="@string/link_description" + android:textAlignment="center" + android:textSize="24sp" + app:layout_constraintBottom_toTopOf="@id/buttonCancel" + app:layout_constraintEnd_toStartOf="@id/card" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/headline" /> + + <com.google.android.material.button.MaterialButton + android:id="@+id/buttonCancel" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginHorizontal="16dp" + android:layout_marginBottom="16dp" + android:text="@string/link_cancel" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toStartOf="@id/card" + app:layout_constraintStart_toStartOf="parent" /> + + <androidx.cardview.widget.CardView + android:id="@+id/card" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_margin="16dp" + app:cardCornerRadius="32dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@id/headline" + app:layout_constraintTop_toTopOf="parent" + app:srcCompat="@drawable/ic_square"> + + <ImageView + android:id="@+id/qrcode" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:adjustViewBounds="true" + app:srcCompat="@drawable/ic_square" /> + + </androidx.cardview.widget.CardView> + +</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 d8d6ae61cc5fef38779d2d922b3e48d915bd9833..40a9283dad798a8204bef92907b566978e8c1119 100644 --- a/mailbox-android/src/main/res/layout/activity_main.xml +++ b/mailbox-android/src/main/res/layout/activity_main.xml @@ -8,4 +8,4 @@ android:layout_height="match_parent" app:defaultNavHost="true" app:navGraph="@navigation/nav_main" - tools:context=".android.MainActivity" /> + tools:context=".android.ui.MainActivity" /> diff --git a/mailbox-android/src/main/res/layout/fragment_init.xml b/mailbox-android/src/main/res/layout/fragment_init.xml new file mode 100644 index 0000000000000000000000000000000000000000..3ca0fe5e6cd8ae20079f2d146573a90a40ec41c0 --- /dev/null +++ b/mailbox-android/src/main/res/layout/fragment_init.xml @@ -0,0 +1,40 @@ +<?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" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <ImageView + android:id="@+id/logo" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_margin="32dp" + android:visibility="visible" + app:layout_constraintBottom_toTopOf="@+id/text" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:srcCompat="@drawable/ic_mailbox" + app:tint="@color/briar_green" /> + + <TextView + android:id="@+id/text" + android:layout_width="wrap_content" + android:layout_height="0dp" + android:text="@string/app_name" + android:textSize="32sp" + app:layout_constraintBottom_toTopOf="@+id/barrier" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/logo" /> + + <androidx.constraintlayout.widget.Barrier + android:id="@+id/barrier" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + app:barrierDirection="top" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_bias="0.8" /> + +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/mailbox-android/src/main/res/layout/fragment_main.xml b/mailbox-android/src/main/res/layout/fragment_main.xml deleted file mode 100644 index b931cb7101102808ebe76d929eb5a70c41df5910..0000000000000000000000000000000000000000 --- a/mailbox-android/src/main/res/layout/fragment_main.xml +++ /dev/null @@ -1,64 +0,0 @@ -<?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" - tools:context=".android.MainActivity"> - - <TextView - android:id="@+id/text" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="Hello World!" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintLeft_toLeftOf="parent" - app:layout_constraintRight_toRightOf="parent" - app:layout_constraintTop_toTopOf="parent" /> - - <Button - android:id="@+id/button" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="16dp" - android:text="Test" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/text" /> - - <TextView - android:id="@+id/statusTextView" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_margin="16dp" - android:gravity="center" - app:layout_constraintBottom_toTopOf="@+id/startStopButton" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/button" - app:layout_constraintVertical_bias="1.0" - tools:text="STOPPED" /> - - <Button - android:id="@+id/startStopButton" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_margin="16dp" - android:text="@string/start" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintBottom_toTopOf="@+id/wipeButton" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" /> - - <Button - android:id="@+id/wipeButton" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_margin="16dp" - android:enabled="false" - android:text="@string/wipe" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" /> - -</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/mailbox-android/src/main/res/layout/fragment_qr.xml b/mailbox-android/src/main/res/layout/fragment_qr.xml new file mode 100644 index 0000000000000000000000000000000000000000..35cb339058b7ecab93eb11ef20292a8904cbe7ef --- /dev/null +++ b/mailbox-android/src/main/res/layout/fragment_qr.xml @@ -0,0 +1,66 @@ +<?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" + tools:context=".android.ui.MainActivity"> + + <TextView + android:id="@+id/headline" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:paddingHorizontal="16dp" + android:text="@string/link_title" + android:textSize="32sp" + app:layout_constraintBottom_toTopOf="@id/description" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <TextView + android:id="@+id/description" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center_horizontal" + android:paddingHorizontal="16dp" + android:text="@string/link_description" + android:textAlignment="center" + android:textSize="24sp" + app:layout_constraintBottom_toTopOf="@id/card" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="@id/headline" /> + + <androidx.cardview.widget.CardView + android:id="@+id/card" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="16dp" + app:cardCornerRadius="32dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="@id/buttonCancel" + app:layout_constraintTop_toBottomOf="@id/description" + app:layout_constraintVertical_bias="1.0" + app:srcCompat="@drawable/ic_square"> + + <ImageView + android:id="@+id/qrcode" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:adjustViewBounds="true" + app:srcCompat="@drawable/ic_square" /> + + </androidx.cardview.widget.CardView> + + <com.google.android.material.button.MaterialButton + android:id="@+id/buttonCancel" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginHorizontal="16dp" + android:layout_marginBottom="16dp" + android:text="@string/link_cancel" + app:layout_constraintBottom_toBottomOf="parent" /> + +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/mailbox-android/src/main/res/layout/fragment_setup_complete.xml b/mailbox-android/src/main/res/layout/fragment_setup_complete.xml new file mode 100644 index 0000000000000000000000000000000000000000..b18b334eb30afd2552e67d743422197dd5f8d6a3 --- /dev/null +++ b/mailbox-android/src/main/res/layout/fragment_setup_complete.xml @@ -0,0 +1,31 @@ +<?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" + tools:context=".android.ui.MainActivity"> + + <TextView + android:id="@+id/headline" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_margin="16dp" + android:text="Connected" + android:textSize="32dp" + app:layout_constraintBottom_toTopOf="@+id/button" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <Button + android:id="@+id/button" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="16dp" + android:text="Finish" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" /> + +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/mailbox-android/src/main/res/layout/fragment_startup.xml b/mailbox-android/src/main/res/layout/fragment_startup.xml new file mode 100644 index 0000000000000000000000000000000000000000..379e4d9d1f58eb342eb2008e9bff04e5df5015f4 --- /dev/null +++ b/mailbox-android/src/main/res/layout/fragment_startup.xml @@ -0,0 +1,33 @@ +<?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" + tools:context=".android.ui.MainActivity"> + + <com.google.android.material.progressindicator.CircularProgressIndicator + android:id="@+id/progress" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:indeterminate="true" + app:indicatorColor="?colorSecondary" + app:indicatorSize="256dp" + app:layout_constraintBottom_toTopOf="@id/statusTextView" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:trackThickness="12dp" /> + + <TextView + android:id="@+id/statusTextView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_margin="16dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/progress" + tools:text="STOPPED" /> + +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/mailbox-android/src/main/res/layout/fragment_status.xml b/mailbox-android/src/main/res/layout/fragment_status.xml new file mode 100644 index 0000000000000000000000000000000000000000..3a9c852f9b1cd48ddf363c40f842ec1629a705fe --- /dev/null +++ b/mailbox-android/src/main/res/layout/fragment_status.xml @@ -0,0 +1,31 @@ +<?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" + tools:context=".android.ui.MainActivity"> + + <TextView + android:id="@+id/headline" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_margin="16dp" + android:text="Mailbox is running" + android:textSize="32dp" + app:layout_constraintBottom_toTopOf="@+id/buttonStop" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <Button + android:text="Stop" + android:id="@+id/buttonStop" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/headline" + app:layout_constraintBottom_toBottomOf="parent" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + +</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 index 3f7bee7ff9790e6e282d9838aff503277fe21527..45e8871b281b48a3325aca97c869c5e5284815a4 100644 --- a/mailbox-android/src/main/res/navigation/nav_main.xml +++ b/mailbox-android/src/main/res/navigation/nav_main.xml @@ -2,18 +2,70 @@ <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"> + app:startDestination="@id/initFragment"> <fragment - android:id="@+id/mainFragment" - android:name="org.briarproject.mailbox.android.MainFragment" - android:label="MainFragment"> + android:id="@+id/initFragment" + android:name="org.briarproject.mailbox.android.ui.InitFragment" + android:label="InitFragment"> <action - android:id="@+id/action_mainFragment_to_doNotKillMeFragment" - app:destination="@id/doNotKillMeFragment" /> + android:id="@+id/action_initFragment_to_doNotKillMeFragment" + app:destination="@id/doNotKillMeFragment" + app:popUpTo="@id/initFragment" + app:popUpToInclusive="true" /> + <action + android:id="@+id/action_initFragment_to_startupFragment" + app:destination="@id/startupFragment" + app:popUpTo="@id/initFragment" + app:popUpToInclusive="true" /> </fragment> <fragment android:id="@+id/doNotKillMeFragment" android:name="org.briarproject.mailbox.android.dontkillme.DoNotKillMeFragment" - android:label="DoNotKillMeFragment" /> + android:label="DoNotKillMeFragment"> + <action + android:id="@+id/action_doNotKillMeFragment_to_startupFragment" + app:destination="@id/startupFragment" + app:popUpTo="@id/doNotKillMeFragment" + app:popUpToInclusive="true" /> + </fragment> + <fragment + android:id="@+id/startupFragment" + android:name="org.briarproject.mailbox.android.ui.StartupFragment" + android:label="StartupFragment"> + <action + android:id="@+id/action_startupFragment_to_qrCodeFragment" + app:destination="@id/qrCodeFragment" + app:popUpTo="@id/startupFragment" + app:popUpToInclusive="true" /> + <action + android:id="@+id/action_startupFragment_to_statusFragment" + app:destination="@id/statusFragment" + app:popUpTo="@id/startupFragment" + app:popUpToInclusive="true" /> + </fragment> + <fragment + android:id="@+id/qrCodeFragment" + android:name="org.briarproject.mailbox.android.ui.QrCodeFragment" + android:label="QrCodeFragment"> + <action + android:id="@+id/action_qrCodeFragment_to_setupCompleteFragment" + app:destination="@id/setupCompleteFragment" + app:popUpTo="@id/qrCodeFragment" + app:popUpToInclusive="true" /> + </fragment> + <fragment + android:id="@+id/setupCompleteFragment" + android:name="org.briarproject.mailbox.android.ui.SetupCompleteFragment" + android:label="SetupCompleteFragment"> + <action + android:id="@+id/action_setupCompleteFragment_to_statusFragment" + app:destination="@id/statusFragment" + app:popUpTo="@id/setupCompleteFragment" + app:popUpToInclusive="true" /> + </fragment> + <fragment + android:id="@+id/statusFragment" + android:name="org.briarproject.mailbox.android.ui.StatusFragment" + android:label="StatusFragment" /> </navigation> diff --git a/mailbox-android/src/main/res/values/strings.xml b/mailbox-android/src/main/res/values/strings.xml index 094a73711833657cb9952cb484e07b0f5cf1f9bd..f3b092575b8950603796f73b313a2c1c56059edb 100644 --- a/mailbox-android/src/main/res/values/strings.xml +++ b/mailbox-android/src/main/res/values/strings.xml @@ -24,4 +24,8 @@ <string name="warning_dozed">Briar Mailbox was unable to run in the background</string> <string name="fix">Fix</string> <string name="cancel">Cancel</string> + + <string name="link_title">Link via QR code</string> + <string name="link_description">Scan this QR code with Briar</string> + <string name="link_cancel">Cancel Setup</string> </resources> diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/setup/SetupManager.kt b/mailbox-core/src/main/java/org/briarproject/mailbox/core/setup/SetupManager.kt index faccb6fdde94d89dff9cfccee8c0e0901dc98006..693c3577a701d047de0b4da87c4782680db40636 100644 --- a/mailbox-core/src/main/java/org/briarproject/mailbox/core/setup/SetupManager.kt +++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/setup/SetupManager.kt @@ -23,14 +23,21 @@ import io.ktor.application.ApplicationCall import io.ktor.auth.principal import io.ktor.http.HttpStatusCode import io.ktor.response.respond +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import org.briarproject.mailbox.core.db.DbException import org.briarproject.mailbox.core.db.Transaction import org.briarproject.mailbox.core.files.FileManager +import org.briarproject.mailbox.core.lifecycle.LifecycleManager.LifecycleState +import org.briarproject.mailbox.core.lifecycle.LifecycleManager.LifecycleState.RUNNING import org.briarproject.mailbox.core.lifecycle.LifecycleManager.OpenDatabaseHook import org.briarproject.mailbox.core.server.AuthException import org.briarproject.mailbox.core.server.AuthManager import org.briarproject.mailbox.core.settings.Settings import org.briarproject.mailbox.core.settings.SettingsManager +import org.briarproject.mailbox.core.setup.SetupComplete.FALSE +import org.briarproject.mailbox.core.setup.SetupComplete.TRUE +import org.briarproject.mailbox.core.setup.SetupComplete.UNKNOWN import org.briarproject.mailbox.core.system.RandomIdManager import javax.inject.Inject @@ -38,6 +45,12 @@ private const val SETTINGS_NAMESPACE_OWNER = "owner" private const val SETTINGS_SETUP_TOKEN = "setupToken" private const val SETTINGS_OWNER_TOKEN = "ownerToken" +enum class SetupComplete { + UNKNOWN, + FALSE, + TRUE, +} + interface SetupManager : OpenDatabaseHook { /** * True if a database has been setup. @@ -47,6 +60,12 @@ interface SetupManager : OpenDatabaseHook { */ val hasDb: Boolean + /** + * This is UNKNOWN initially and will be set to TRUE or FALSE while the database is opened. + * It is safe to assume a value != [UNKNOWN] when the [LifecycleState] is [RUNNING]. + */ + val setupComplete: StateFlow<SetupComplete> + @Throws(DbException::class) fun setToken(setupToken: String?, ownerToken: String?) @@ -65,6 +84,9 @@ class SetupManagerImpl @Inject constructor( override val hasDb: Boolean get() = fileManager.hasDbFile() + private val _setupComplete = MutableStateFlow(UNKNOWN) + override val setupComplete: StateFlow<SetupComplete> = _setupComplete + @Throws(DbException::class) override fun onDatabaseOpened(txn: Transaction) { val settings = settingsManager.getSettings(txn, SETTINGS_NAMESPACE_OWNER) @@ -74,6 +96,9 @@ class SetupManagerImpl @Inject constructor( if (setupToken == null && ownerToken == null) { settings[SETTINGS_SETUP_TOKEN] = randomIdManager.getNewRandomId() settingsManager.mergeSettings(txn, settings, SETTINGS_NAMESPACE_OWNER) + _setupComplete.value = FALSE + } else { + _setupComplete.value = if (ownerToken != null) TRUE else FALSE } } @@ -90,6 +115,7 @@ class SetupManagerImpl @Inject constructor( if (ownerToken != null) randomIdManager.assertIsRandomId(ownerToken) settings[SETTINGS_OWNER_TOKEN] = ownerToken settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE_OWNER) + _setupComplete.value = TRUE } @Throws(DbException::class)