diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/StatusManager.kt b/mailbox-android/src/main/java/org/briarproject/mailbox/android/StatusManager.kt index 811b1ba8ff210b3057429c24aa2bcf76d479b348..455321245797ae82a040439dc1bfff4157056ba5 100644 --- a/mailbox-android/src/main/java/org/briarproject/mailbox/android/StatusManager.kt +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/android/StatusManager.kt @@ -124,7 +124,7 @@ class StatusManager @Inject constructor( object NeedsDozeExemption : MailboxAppState(false) object NotStarted : MailboxAppState(false) data class Starting(val status: String) : MailboxAppState(true) - data class StartedSettingUp(val qrCode: Bitmap) : MailboxAppState(true) + data class StartedSettingUp(val qrCode: Bitmap, val link: String) : MailboxAppState(true) object StartedSetupComplete : MailboxAppState(true) object ErrorClockSkew : MailboxAppState(true) object ErrorNoNetwork : MailboxAppState(true) @@ -204,12 +204,15 @@ class StatusManager @Inject constructor( else -> Starting(getString(R.string.startup_publishing_onion_service)) } setup == SetupComplete.FALSE -> { + // FIXME we shouldn't do expensive calls on the UiThread 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") + qrCode = bitMatrix?.let { it -> QrCodeUtils.renderQrCode(it) } + ?: error("The QR code bit matrix is expected to be non-null here"), + link = qrCodeEncoder.getLink() + ?: error("The QR code link is expected to be non-null here"), ) } setup == SetupComplete.TRUE -> StartedSetupComplete diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/MainActivity.kt b/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/MainActivity.kt index bc7db26741a7f65aad013ab226af63fcf153923f..0340ce138a2546e9b25754b571926a9f352f4aa9 100644 --- a/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/MainActivity.kt +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/MainActivity.kt @@ -83,6 +83,21 @@ class MainActivity : AppCompatActivity() { hadBeenStartedOnSave = savedInstanceState?.getBoolean(BUNDLE_LIFECYCLE_HAS_STARTED) ?: false + // set action bar titles based on navigation destination + nav.addOnDestinationChangedListener { _, destination, _ -> + // never show the up indicator. exceptions below + supportActionBar?.setDisplayHomeAsUpEnabled(false) + title = when (destination.id) { + R.id.qrCodeFragment -> getString(R.string.link_title) + R.id.qrCodeLinkFragment -> { + supportActionBar?.setDisplayHomeAsUpEnabled(true) + getString(R.string.link_text_title) + } + R.id.statusFragment -> getString(R.string.app_name) + else -> "" + } + } + launchAndRepeatWhileStarted { viewModel.appState.collect { onAppStateChanged(it) } } @@ -100,35 +115,68 @@ class MainActivity : AppCompatActivity() { return } when (state) { - Undecided -> {} // Do nothing yet - NeedOnboarding -> if (nav.currentDestination?.id == R.id.initFragment) - nav.navigate(actionGlobalOnboardingContainer()) - NeedsDozeExemption -> if (nav.currentDestination?.id != R.id.doNotKillMeFragment) - nav.navigate(actionGlobalDoNotKillMeFragment()) - NotStarted -> nav.navigate(actionGlobalStartupFragment()) + Undecided -> supportActionBar?.hide() // hide action bar until we need it + NeedOnboarding -> { + supportActionBar?.hide() + if (nav.currentDestination?.id == R.id.initFragment) + nav.navigate(actionGlobalOnboardingContainer()) + } + NeedsDozeExemption -> { + supportActionBar?.hide() + if (nav.currentDestination?.id != R.id.doNotKillMeFragment) + nav.navigate(actionGlobalDoNotKillMeFragment()) + } + NotStarted -> { + supportActionBar?.hide() + nav.navigate(actionGlobalStartupFragment()) + } // It is important to navigate here from various fragments. The normal case is // that we come from the init fragment, do-not-kill fragment or the onboarding fragment. // However, when the service got killed and the app has been restored with a different // UI state such as the qr code screen or the status screen, then we also want to // navigate to the startup fragment. - is Starting -> if (nav.currentDestination?.id != R.id.startupFragment) - nav.navigate(actionGlobalStartupFragment()) - is StartedSettingUp -> if (nav.currentDestination?.id != R.id.qrCodeFragment) - nav.navigate(actionGlobalQrCodeFragment()) - StartedSetupComplete -> - if (nav.currentDestination?.id == R.id.qrCodeFragment) + is Starting -> { + supportActionBar?.hide() + if (nav.currentDestination?.id != R.id.startupFragment) + nav.navigate(actionGlobalStartupFragment()) + } + is StartedSettingUp -> { + supportActionBar?.show() + if (nav.currentDestination?.id != R.id.qrCodeFragment && + nav.currentDestination?.id != R.id.qrCodeLinkFragment + ) nav.navigate(actionGlobalQrCodeFragment()) + } + StartedSetupComplete -> { + if (nav.currentDestination?.id == R.id.qrCodeFragment) { + supportActionBar?.hide() nav.navigate(actionGlobalSetupCompleteFragment()) - else if (nav.currentDestination?.id != R.id.statusFragment && + } else if (nav.currentDestination?.id != R.id.statusFragment && nav.currentDestination?.id != R.id.setupCompleteFragment - ) nav.navigate(actionGlobalStatusFragment()) - ErrorNoNetwork -> if (nav.currentDestination?.id != R.id.noNetworkFragment) - nav.navigate(actionGlobalNoNetworkFragment()) - ErrorClockSkew -> if (nav.currentDestination?.id != R.id.clockSkewFragment) - nav.navigate(actionGlobalClockSkewFragment()) - Stopping -> if (nav.currentDestination?.id != R.id.stoppingFragment) - nav.navigate(actionGlobalStoppingFragment()) - Wiping -> if (nav.currentDestination?.id != R.id.wipingFragment) - nav.navigate(actionGlobalWipingFragment()) + ) { + supportActionBar?.show() + nav.navigate(actionGlobalStatusFragment()) + } + } + ErrorNoNetwork -> { + supportActionBar?.hide() + if (nav.currentDestination?.id != R.id.noNetworkFragment) + nav.navigate(actionGlobalNoNetworkFragment()) + } + ErrorClockSkew -> { + supportActionBar?.hide() + if (nav.currentDestination?.id != R.id.clockSkewFragment) + nav.navigate(actionGlobalClockSkewFragment()) + } + Stopping -> { + supportActionBar?.hide() + if (nav.currentDestination?.id != R.id.stoppingFragment) + nav.navigate(actionGlobalStoppingFragment()) + } + Wiping -> { + supportActionBar?.hide() + if (nav.currentDestination?.id != R.id.wipingFragment) + nav.navigate(actionGlobalWipingFragment()) + } Stopped -> {} // nothing to do but needs to be exhaustive for Kotlin 1.7 } } 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 index 44cb232a1c4219876d76ebe87ae58658d1f6669a..db5a5893b04454565b44a662e9a5f50e13f8fec0 100644 --- 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 @@ -21,20 +21,25 @@ package org.briarproject.mailbox.android.ui import android.os.Bundle import android.view.LayoutInflater +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem import android.view.View import android.view.ViewGroup import android.widget.Button import android.widget.ImageView +import androidx.core.view.MenuProvider import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Lifecycle.State.RESUMED +import androidx.navigation.fragment.findNavController import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.flow.collect import org.briarproject.mailbox.R import org.briarproject.mailbox.android.StatusManager.MailboxAppState import org.briarproject.mailbox.android.StatusManager.StartedSettingUp @AndroidEntryPoint -class QrCodeFragment : Fragment() { +class QrCodeFragment : Fragment(), MenuProvider { private val viewModel: MailboxViewModel by activityViewModels() private lateinit var qrCodeView: ImageView @@ -56,12 +61,25 @@ class QrCodeFragment : Fragment() { viewModel.stopLifecycle() requireActivity().finishAffinity() } + requireActivity().addMenuProvider(this, viewLifecycleOwner, RESUMED) launchAndRepeatWhileStarted { viewModel.appState.collect { onAppStateChanged(it) } } } + override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { + menuInflater.inflate(R.menu.link_actions, menu) + } + + override fun onMenuItemSelected(menuItem: MenuItem): Boolean { + if (menuItem.itemId == R.id.action_show_link) { + findNavController().navigate(R.id.action_qrCodeFragment_to_qrCodeLinkFragment) + return true + } + return false + } + private fun onAppStateChanged(state: MailboxAppState) { if (state is StartedSettingUp) { qrCodeView.setImageBitmap(state.qrCode) diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/QrCodeLinkFragment.kt b/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/QrCodeLinkFragment.kt new file mode 100644 index 0000000000000000000000000000000000000000..652356162267247c22ba8a0027e13b73d594428a --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/QrCodeLinkFragment.kt @@ -0,0 +1,119 @@ +/* + * 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.content.ActivityNotFoundException +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Intent +import android.content.Intent.ACTION_SEND +import android.content.Intent.EXTRA_TEXT +import android.os.Build.VERSION.SDK_INT +import android.os.Bundle +import android.view.LayoutInflater +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import android.widget.Toast +import android.widget.Toast.LENGTH_LONG +import android.widget.Toast.LENGTH_SHORT +import androidx.core.content.ContextCompat.getSystemService +import androidx.core.view.MenuProvider +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Lifecycle +import com.google.android.material.button.MaterialButton +import dagger.hilt.android.AndroidEntryPoint +import org.briarproject.mailbox.R +import org.briarproject.mailbox.android.StatusManager.MailboxAppState +import org.briarproject.mailbox.android.StatusManager.StartedSettingUp + +@AndroidEntryPoint +class QrCodeLinkFragment : Fragment(), MenuProvider { + + private val viewModel: MailboxViewModel by activityViewModels() + private lateinit var linkView: TextView + private lateinit var shareButton: MaterialButton + private lateinit var copyButton: MaterialButton + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View? { + return inflater.inflate(R.layout.fragment_qr_link, container, false) + } + + override fun onViewCreated(v: View, savedInstanceState: Bundle?) { + linkView = v.findViewById(R.id.linkView) + shareButton = v.findViewById(R.id.shareButton) + copyButton = v.findViewById(R.id.copyButton) + + // only needed for up/back navigation in action bar + requireActivity().addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED) + + launchAndRepeatWhileStarted { + viewModel.appState.collect { onAppStateChanged(it) } + } + } + + override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { + } + + override fun onMenuItemSelected(menuItem: MenuItem): Boolean { + if (menuItem.itemId == android.R.id.home) { + requireActivity().onBackPressedDispatcher.onBackPressed() + return true + } + return false + } + + private fun onAppStateChanged(state: MailboxAppState) { + if (state is StartedSettingUp) { + linkView.text = state.link + shareButton.setOnClickListener { + val sendIntent = Intent().apply { + action = ACTION_SEND + putExtra(EXTRA_TEXT, state.link) + type = "text/plain" + } + val shareIntent = Intent.createChooser(sendIntent, null) + try { + startActivity(shareIntent) + } catch (ignored: ActivityNotFoundException) { + Toast.makeText(requireContext(), R.string.activity_not_found, LENGTH_LONG) + .show() + } + } + copyButton.setOnClickListener { + val clipboard = getSystemService(requireContext(), ClipboardManager::class.java) + ?: return@setOnClickListener + val clip = ClipData.newPlainText("Briar Mailbox text", state.link) + clipboard.setPrimaryClip(clip) + // Only show a toast for Android 12 and lower. + if (SDK_INT <= 32) Toast.makeText(context, R.string.copied, LENGTH_SHORT).show() + } + } + } + +} diff --git a/mailbox-android/src/main/res/drawable/ic_content_copy.xml b/mailbox-android/src/main/res/drawable/ic_content_copy.xml new file mode 100644 index 0000000000000000000000000000000000000000..89f03149c328390244e5b8dd64cca1aab48ad0df --- /dev/null +++ b/mailbox-android/src/main/res/drawable/ic_content_copy.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="M16,1L4,1c-1.1,0 -2,0.9 -2,2v14h2L4,3h12L16,1zM19,5L8,5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h11c1.1,0 2,-0.9 2,-2L21,7c0,-1.1 -0.9,-2 -2,-2zM19,21L8,21L8,7h11v14z" /> +</vector> diff --git a/mailbox-android/src/main/res/drawable/ic_share.xml b/mailbox-android/src/main/res/drawable/ic_share.xml new file mode 100644 index 0000000000000000000000000000000000000000..59b0e84dd92be63eeda3928fa3484b1a6b9ec8c1 --- /dev/null +++ b/mailbox-android/src/main/res/drawable/ic_share.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="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z" /> +</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 index d57eccc01c05f4b984270b8299799470b9f669e3..95e269bfdc34bb9fe90b26945cc6d71a76c35905 100644 --- a/mailbox-android/src/main/res/layout-land/fragment_qr.xml +++ b/mailbox-android/src/main/res/layout-land/fragment_qr.xml @@ -6,27 +6,11 @@ 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:layout_margin="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" - app:layout_constraintVertical_bias="0.0" - app:layout_constraintVertical_chainStyle="packed" /> - <TextView android:id="@+id/description" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginHorizontal="16dp" + android:layout_margin="16dp" android:gravity="center_horizontal" android:text="@string/link_description" android:textAlignment="center" @@ -34,14 +18,9 @@ app:layout_constraintBottom_toTopOf="@id/buttonCancel" app:layout_constraintEnd_toStartOf="@id/card" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/headline" /> - - <androidx.constraintlayout.widget.Barrier - android:id="@+id/barrier" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - app:barrierDirection="end" - app:constraint_referenced_ids="headline,description" /> + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_bias="0.0" + app:layout_constraintVertical_chainStyle="packed" /> <com.google.android.material.button.MaterialButton android:id="@+id/buttonCancel" @@ -58,11 +37,14 @@ android:id="@+id/card" android:layout_width="wrap_content" android:layout_height="match_parent" - android:layout_margin="16dp" + android:layout_marginTop="16dp" + android:layout_marginEnd="16dp" + android:layout_marginRight="16dp" + android:layout_marginBottom="16dp" app:cardCornerRadius="32dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toEndOf="@id/barrier" + app:layout_constraintStart_toEndOf="@id/description" app:layout_constraintTop_toTopOf="parent" app:srcCompat="@drawable/ic_square"> diff --git a/mailbox-android/src/main/res/layout/fragment_qr.xml b/mailbox-android/src/main/res/layout/fragment_qr.xml index e25d2eb58be66a45e8831ba4cf2b984cc7ae391e..819efe791ec72ca992ec251759aa1646a4b2b183 100644 --- a/mailbox-android/src/main/res/layout/fragment_qr.xml +++ b/mailbox-android/src/main/res/layout/fragment_qr.xml @@ -6,20 +6,6 @@ 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:layout_margin="16dp" - android:gravity="center" - 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" - app:layout_constraintVertical_bias="0.0" /> - <TextView android:id="@+id/description" android:layout_width="0dp" @@ -31,7 +17,8 @@ android:textSize="20sp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/headline" /> + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_bias="0.0" /> <androidx.cardview.widget.CardView android:id="@+id/card" diff --git a/mailbox-android/src/main/res/layout/fragment_qr_link.xml b/mailbox-android/src/main/res/layout/fragment_qr_link.xml new file mode 100644 index 0000000000000000000000000000000000000000..ef5ae8fb7f03d8b0be7f11b8edd0e87afb14a3ec --- /dev/null +++ b/mailbox-android/src/main/res/layout/fragment_qr_link.xml @@ -0,0 +1,86 @@ +<?xml version="1.0" encoding="utf-8"?> +<ScrollView 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"> + + <androidx.constraintlayout.widget.ConstraintLayout + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <TextView + android:id="@+id/description" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_margin="16dp" + android:gravity="center_horizontal" + android:text="@string/link_text_description" + android:textAlignment="center" + android:textSize="20sp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_bias="0.0" /> + + <androidx.cardview.widget.CardView + android:id="@+id/card" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_margin="16dp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/description" + app:srcCompat="@drawable/ic_square"> + + <TextView + android:id="@+id/linkView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:background="@android:color/white" + android:padding="8dp" + android:textColor="@color/briar_night_800" + android:textIsSelectable="true" + android:textSize="18sp" + tools:text="briar-mailbox://eb64akialxyqs74agq63u3nsjkuohf7i5wzht5va2rmu5acmvsczzcsrioemhaalhmakamjww4p3kiaznx6jcpnx7lzhj44gxm3zzd5o" /> + + </androidx.cardview.widget.CardView> + + <com.google.android.material.button.MaterialButton + android:id="@+id/shareButton" + style="@style/Widget.Material3.Button.OutlinedButton.Icon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:layout_marginEnd="16dp" + android:layout_marginRight="16dp" + android:text="@string/share" + app:icon="@drawable/ic_share" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@+id/card" + app:layout_constraintVertical_bias="0.0" /> + + <Button + android:id="@+id/copyButton" + style="@style/Widget.Material3.Button.OutlinedButton.Icon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:layout_marginLeft="16dp" + android:layout_marginTop="16dp" + android:layout_marginBottom="16dp" + android:text="@string/copy" + app:icon="@drawable/ic_content_copy" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toStartOf="@+id/shareButton" + app:layout_constraintHorizontal_bias="0.0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/card" + app:layout_constraintVertical_bias="0.0" /> + + </androidx.constraintlayout.widget.ConstraintLayout> + + +</ScrollView> diff --git a/mailbox-android/src/main/res/menu/link_actions.xml b/mailbox-android/src/main/res/menu/link_actions.xml new file mode 100644 index 0000000000000000000000000000000000000000..79b21865feb344f870bbfba46889f1252698cc68 --- /dev/null +++ b/mailbox-android/src/main/res/menu/link_actions.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> +<menu xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> + + <item + android:id="@+id/action_show_link" + android:title="@string/link_menu_title" + app:showAsAction="never" /> +</menu> diff --git a/mailbox-android/src/main/res/navigation/nav_main.xml b/mailbox-android/src/main/res/navigation/nav_main.xml index bbd9087fc3cdfd67e407484e5795493533290c2e..fbb0eee8f501fe1631ceb4be70b7f03dffaae3cf 100644 --- a/mailbox-android/src/main/res/navigation/nav_main.xml +++ b/mailbox-android/src/main/res/navigation/nav_main.xml @@ -28,8 +28,17 @@ <fragment android:id="@+id/qrCodeFragment" android:name="org.briarproject.mailbox.android.ui.QrCodeFragment" - android:label="QrCodeFragment" - tools:layout="@layout/fragment_qr" /> + android:label="@string/link_title" + tools:layout="@layout/fragment_qr"> + <action + android:id="@+id/action_qrCodeFragment_to_qrCodeLinkFragment" + app:destination="@id/qrCodeLinkFragment" /> + </fragment> + <fragment + android:id="@+id/qrCodeLinkFragment" + android:name="org.briarproject.mailbox.android.ui.QrCodeLinkFragment" + android:label="@string/link_title" + tools:layout="@layout/fragment_qr_link" /> <fragment android:id="@+id/setupCompleteFragment" android:name="org.briarproject.mailbox.android.ui.SetupCompleteFragment" diff --git a/mailbox-android/src/main/res/values-night/themes.xml b/mailbox-android/src/main/res/values-night/themes.xml index 8cd614568d5a241ee6627b81c92a7f608b25e590..05035d002d96ce2c2606e5906ed473acd59d72dd 100644 --- a/mailbox-android/src/main/res/values-night/themes.xml +++ b/mailbox-android/src/main/res/values-night/themes.xml @@ -1,6 +1,6 @@ <resources xmlns:tools="http://schemas.android.com/tools"> <!-- Base application theme. --> - <style name="Theme.BriarMailbox" parent="Theme.Material3.DayNight.NoActionBar"> + <style name="Theme.BriarMailbox" parent="Theme.Material3.DayNight"> <!-- Primary brand color. --> <item name="colorPrimary">@color/briar_night</item> <item name="colorPrimaryDark">#1F78D1</item> diff --git a/mailbox-android/src/main/res/values/strings.xml b/mailbox-android/src/main/res/values/strings.xml index 04bb3261b2e1c3401a9c4f99fe430c79085d8f85..bcffbb7d70fa972c7d0f97604d18c57d15b7ef3f 100644 --- a/mailbox-android/src/main/res/values/strings.xml +++ b/mailbox-android/src/main/res/values/strings.xml @@ -29,9 +29,14 @@ <string name="fix">Fix</string> <string name="ok">OK</string> <string name="cancel">Cancel</string> + <string name="share">Share</string> + <string name="copied">Copied</string> <string name="link_title">Link via QR code</string> <string name="link_description">Scan this QR code with Briar</string> + <string name="link_menu_title">Show as text</string> + <string name="link_text_title">Link via text</string> + <string name="link_text_description">Copy this and paste it into Briar Desktop:</string> <string name="link_cancel">Cancel Setup</string> <string name="startup_headline">Starting Mailbox</string> @@ -68,6 +73,7 @@ <string name="wipe_complete_description">Next time you have access to your Briar device, please open the Mailbox settings screen in the Briar app, then tap the \"Unlink\" button to complete the process.</string> <string name="sorry">Sorry</string> + <string name="activity_not_found">No app found for this action</string> <string name="startup_failed_activity_title">Mailbox Startup Failure</string> <string name="startup_failed_service_error">Mailbox was unable to start a required component.\n\nPlease upgrade to the latest version of the app and try again.</string> <string name="startup_failed_lifecycle_reuse">Mailbox has already stopped.\n\nPlease close the app and try again.</string> diff --git a/mailbox-android/src/main/res/values/themes.xml b/mailbox-android/src/main/res/values/themes.xml index ec0a371756ace43d773006f8b91470ef49b406b7..892878df15c459ee8d1946233793b8365691433f 100644 --- a/mailbox-android/src/main/res/values/themes.xml +++ b/mailbox-android/src/main/res/values/themes.xml @@ -1,6 +1,6 @@ <resources xmlns:tools="http://schemas.android.com/tools"> <!-- Base application theme. --> - <style name="Theme.BriarMailboxBase" parent="Theme.Material3.DayNight.NoActionBar"> + <style name="Theme.BriarMailboxBase" parent="Theme.Material3.DayNight"> <!-- Primary brand color. --> <item name="colorPrimary">@color/briar_night</item> <item name="colorPrimaryDark">@color/briar_blue</item> diff --git a/mailbox-cli/src/main/java/org/briarproject/mailbox/cli/Main.kt b/mailbox-cli/src/main/java/org/briarproject/mailbox/cli/Main.kt index da436f36811783ebe5a1f13d05d44fd73a554bfb..772522234aaa8bdb039b8dfcded0d9ceb1d0fc0c 100644 --- a/mailbox-cli/src/main/java/org/briarproject/mailbox/cli/Main.kt +++ b/mailbox-cli/src/main/java/org/briarproject/mailbox/cli/Main.kt @@ -111,8 +111,13 @@ class Main : CliktCommand( } mailbox.waitForTorPublished() mailbox.getQrCode()?.let { + println("Please scan this with the Briar Android app:") println(QrCodeRenderer.getQrString(it)) } + mailbox.getLink()?.let { + println("Or copy and paste this into Briar Desktop:\n") + println(it) + } } } diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/setup/QrCodeEncoder.kt b/mailbox-core/src/main/java/org/briarproject/mailbox/core/setup/QrCodeEncoder.kt index 2c95bb31a1caa1e4a7323e7ffc8f433716d604a6..ec901f8772bc9b1a8d0702e88df0a21b59fce7ae 100644 --- a/mailbox-core/src/main/java/org/briarproject/mailbox/core/setup/QrCodeEncoder.kt +++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/setup/QrCodeEncoder.kt @@ -58,6 +58,11 @@ class QrCodeEncoder @Inject constructor( return QRCodeWriter().encode(content, QR_CODE, edgeLen, edgeLen) } + fun getLink(): String? { + val bytes = getQrCodeBytes() ?: return null + return "briar-mailbox://${Base32.encode(bytes).lowercase()}" + } + private fun getQrCodeBytes(): ByteArray? { // The ID and version of the QR code format: 3 bits for the format ID and 5 for the version val formatIdAndVersion = ((FORMAT_ID shl 5) or FORMAT_VERSION).toByte() diff --git a/mailbox-lib/src/main/java/org/briarproject/mailbox/lib/AbstractMailbox.kt b/mailbox-lib/src/main/java/org/briarproject/mailbox/lib/AbstractMailbox.kt index 67d71043c22c194f54d327d9931a84aa0b40d758..bedad403c012c8065c57e69d2a05f8104a0ed7ac 100644 --- a/mailbox-lib/src/main/java/org/briarproject/mailbox/lib/AbstractMailbox.kt +++ b/mailbox-lib/src/main/java/org/briarproject/mailbox/lib/AbstractMailbox.kt @@ -127,5 +127,9 @@ abstract class AbstractMailbox(protected val customDataDir: File? = null) { return qrCodeEncoder.getQrCodeBitMatrix() } + fun getLink(): String? { + return qrCodeEncoder.getLink() + } + fun getSystem(): System = system }