Scan Mailbox QR code for setup and show progress screen

This commit is contained in:
Torsten Grote
2022-02-08 15:04:51 -03:00
parent e14773985d
commit 73d9e05ada
18 changed files with 416 additions and 72 deletions

View File

@@ -44,6 +44,7 @@ import org.briarproject.briar.android.hotspot.ManualHotspotFragment;
import org.briarproject.briar.android.hotspot.QrHotspotFragment; import org.briarproject.briar.android.hotspot.QrHotspotFragment;
import org.briarproject.briar.android.logging.CachingLogHandler; import org.briarproject.briar.android.logging.CachingLogHandler;
import org.briarproject.briar.android.login.SignInReminderReceiver; import org.briarproject.briar.android.login.SignInReminderReceiver;
import org.briarproject.briar.android.mailbox.MailboxScanFragment;
import org.briarproject.briar.android.removabledrive.ChooserFragment; import org.briarproject.briar.android.removabledrive.ChooserFragment;
import org.briarproject.briar.android.removabledrive.ReceiveFragment; import org.briarproject.briar.android.removabledrive.ReceiveFragment;
import org.briarproject.briar.android.removabledrive.SendFragment; import org.briarproject.briar.android.removabledrive.SendFragment;
@@ -239,4 +240,6 @@ public interface AndroidComponent
void inject(ReceiveFragment receiveFragment); void inject(ReceiveFragment receiveFragment);
void inject(BluetoothIntroFragment bluetoothIntroFragment); void inject(BluetoothIntroFragment bluetoothIntroFragment);
void inject(MailboxScanFragment mailboxScanFragment);
} }

View File

@@ -3,11 +3,10 @@ package org.briarproject.briar.android.contact.add.nearby;
import android.content.Context; import android.content.Context;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.util.Permission;
import java.util.Map; import java.util.Map;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AlertDialog;
import androidx.core.util.Consumer; import androidx.core.util.Consumer;
import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentActivity;
@@ -17,16 +16,13 @@ import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION.SDK_INT;
import static androidx.core.app.ActivityCompat.shouldShowRequestPermissionRationale; import static androidx.core.app.ActivityCompat.shouldShowRequestPermissionRationale;
import static androidx.core.content.ContextCompat.checkSelfPermission; import static androidx.core.content.ContextCompat.checkSelfPermission;
import static org.briarproject.briar.android.util.UiUtils.getGoToSettingsListener;
import static org.briarproject.briar.android.util.UiUtils.isLocationEnabled; import static org.briarproject.briar.android.util.UiUtils.isLocationEnabled;
import static org.briarproject.briar.android.util.UiUtils.showDenialDialog;
import static org.briarproject.briar.android.util.UiUtils.showLocationDialog; import static org.briarproject.briar.android.util.UiUtils.showLocationDialog;
import static org.briarproject.briar.android.util.UiUtils.showRationale;
class AddNearbyContactPermissionManager { class AddNearbyContactPermissionManager {
private enum Permission {
UNKNOWN, GRANTED, SHOW_RATIONALE, PERMANENTLY_DENIED
}
private Permission cameraPermission = Permission.UNKNOWN; private Permission cameraPermission = Permission.UNKNOWN;
private Permission locationPermission = Permission.UNKNOWN; private Permission locationPermission = Permission.UNKNOWN;
@@ -68,27 +64,30 @@ class AddNearbyContactPermissionManager {
// If an essential permission has been permanently denied, ask the // If an essential permission has been permanently denied, ask the
// user to change the setting // user to change the setting
if (cameraPermission == Permission.PERMANENTLY_DENIED) { if (cameraPermission == Permission.PERMANENTLY_DENIED) {
showDenialDialog(R.string.permission_camera_title, showDenialDialog(ctx, R.string.permission_camera_title,
R.string.permission_camera_denied_body); R.string.permission_camera_denied_body);
return false; return false;
} }
if (isBluetoothSupported && if (isBluetoothSupported &&
locationPermission == Permission.PERMANENTLY_DENIED) { locationPermission == Permission.PERMANENTLY_DENIED) {
showDenialDialog(R.string.permission_location_title, showDenialDialog(ctx, R.string.permission_location_title,
R.string.permission_location_denied_body); R.string.permission_location_denied_body);
return false; return false;
} }
// Should we show the rationale for one or both permissions? // Should we show the rationale for one or both permissions?
if (cameraPermission == Permission.SHOW_RATIONALE && if (cameraPermission == Permission.SHOW_RATIONALE &&
locationPermission == Permission.SHOW_RATIONALE) { locationPermission == Permission.SHOW_RATIONALE) {
showRationale(R.string.permission_camera_location_title, showRationale(ctx, R.string.permission_camera_location_title,
R.string.permission_camera_location_request_body); R.string.permission_camera_location_request_body,
this::requestPermissions);
} else if (cameraPermission == Permission.SHOW_RATIONALE) { } else if (cameraPermission == Permission.SHOW_RATIONALE) {
showRationale(R.string.permission_camera_title, showRationale(ctx, R.string.permission_camera_title,
R.string.permission_camera_request_body); R.string.permission_camera_request_body,
this::requestPermissions);
} else if (locationPermission == Permission.SHOW_RATIONALE) { } else if (locationPermission == Permission.SHOW_RATIONALE) {
showRationale(R.string.permission_location_title, showRationale(ctx, R.string.permission_location_title,
R.string.permission_location_request_body); R.string.permission_location_request_body,
this::requestPermissions);
} else if (locationEnabled) { } else if (locationEnabled) {
requestPermissions(); requestPermissions();
} else { } else {
@@ -97,27 +96,6 @@ class AddNearbyContactPermissionManager {
return false; return false;
} }
private void showDenialDialog(@StringRes int title, @StringRes int body) {
AlertDialog.Builder builder =
new AlertDialog.Builder(ctx, R.style.BriarDialogTheme);
builder.setTitle(title);
builder.setMessage(body);
builder.setPositiveButton(R.string.ok, getGoToSettingsListener(ctx));
builder.setNegativeButton(R.string.cancel,
(dialog, which) -> ctx.supportFinishAfterTransition());
builder.show();
}
private void showRationale(@StringRes int title, @StringRes int body) {
AlertDialog.Builder builder =
new AlertDialog.Builder(ctx, R.style.BriarDialogTheme);
builder.setTitle(title);
builder.setMessage(body);
builder.setNeutralButton(R.string.continue_button,
(dialog, which) -> requestPermissions());
builder.show();
}
private void requestPermissions() { private void requestPermissions() {
String[] permissions; String[] permissions;
if (isBluetoothSupported) { if (isBluetoothSupported) {

View File

@@ -4,6 +4,7 @@ import android.app.Activity;
import android.content.Context; import android.content.Context;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.util.Permission;
import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.ActivityResultLauncher;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@@ -19,10 +20,6 @@ import static org.briarproject.briar.android.util.UiUtils.showLocationDialog;
class BluetoothConditionManager { class BluetoothConditionManager {
private enum Permission {
UNKNOWN, GRANTED, SHOW_RATIONALE, PERMANENTLY_DENIED
}
private Permission locationPermission = Permission.UNKNOWN; private Permission locationPermission = Permission.UNKNOWN;
/** /**

View File

@@ -20,11 +20,7 @@ import static android.content.Context.WIFI_SERVICE;
*/ */
abstract class AbstractConditionManager { abstract class AbstractConditionManager {
enum Permission { final Consumer<Boolean> permissionUpdateCallback;
UNKNOWN, GRANTED, SHOW_RATIONALE, PERMANENTLY_DENIED
}
protected final Consumer<Boolean> permissionUpdateCallback;
protected FragmentActivity ctx; protected FragmentActivity ctx;
WifiManager wifiManager; WifiManager wifiManager;

View File

@@ -4,6 +4,7 @@ import android.content.Intent;
import android.provider.Settings; import android.provider.Settings;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.util.Permission;
import java.util.logging.Logger; import java.util.logging.Logger;

View File

@@ -0,0 +1,83 @@
package org.briarproject.briar.android.mailbox;
import android.content.Context;
import org.briarproject.briar.R;
import org.briarproject.briar.android.util.Permission;
import java.util.Map;
import androidx.core.util.Consumer;
import androidx.fragment.app.FragmentActivity;
import static android.Manifest.permission.CAMERA;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static androidx.core.app.ActivityCompat.shouldShowRequestPermissionRationale;
import static androidx.core.content.ContextCompat.checkSelfPermission;
import static org.briarproject.briar.android.util.UiUtils.showDenialDialog;
import static org.briarproject.briar.android.util.UiUtils.showRationale;
class CameraPermissionManager {
private Permission cameraPermission = Permission.UNKNOWN;
private final FragmentActivity ctx;
private final Consumer<String[]> requestPermissions;
CameraPermissionManager(FragmentActivity ctx,
Consumer<String[]> requestPermissions) {
this.ctx = ctx;
this.requestPermissions = requestPermissions;
}
void resetPermissions() {
cameraPermission = Permission.UNKNOWN;
}
private static boolean areEssentialPermissionsGranted(Context ctx) {
return checkSelfPermission(ctx, CAMERA) == PERMISSION_GRANTED;
}
private boolean areEssentialPermissionsGranted() {
return cameraPermission == Permission.GRANTED;
}
boolean checkPermissions() {
if (areEssentialPermissionsGranted()) return true;
// If an essential permission has been permanently denied, ask the
// user to change the setting
if (cameraPermission == Permission.PERMANENTLY_DENIED) {
showDenialDialog(ctx, R.string.permission_camera_title,
R.string.permission_camera_qr_denied_body);
} else if (cameraPermission == Permission.SHOW_RATIONALE) {
showRationale(ctx, R.string.permission_camera_title,
R.string.permission_camera_request_body,
this::requestPermissions);
} else {
requestPermissions();
}
return false;
}
private void requestPermissions() {
String[] permissions = new String[] {CAMERA};
requestPermissions.accept(permissions);
}
void onRequestPermissionResult(Map<String, Boolean> result) {
if (gotPermission(result)) {
cameraPermission = Permission.GRANTED;
} else if (shouldShowRequestPermissionRationale(ctx, CAMERA)) {
cameraPermission = Permission.SHOW_RATIONALE;
} else {
cameraPermission = Permission.PERMANENTLY_DENIED;
}
}
private boolean gotPermission(Map<String, Boolean> result) {
Boolean permissionResult = result.get(CAMERA);
return permissionResult == null ? areEssentialPermissionsGranted(ctx) :
permissionResult;
}
}

View File

@@ -17,6 +17,7 @@ import androidx.lifecycle.ViewModelProvider;
import static android.view.View.INVISIBLE; import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE; import static android.view.View.VISIBLE;
import static org.briarproject.briar.android.util.UiUtils.showFragment;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
@@ -46,13 +47,13 @@ public class MailboxActivity extends BriarActivity {
progressBar.setVisibility(VISIBLE); progressBar.setVisibility(VISIBLE);
} }
if (savedInstanceState == null) { viewModel.getState().observe(this, state -> {
viewModel.getState().observe(this, state -> { if (state instanceof MailboxState.NotSetup) {
if (state instanceof MailboxState.NotSetup) { if (savedInstanceState == null) onNotSetup();
onNotSetup(); } else if (state instanceof MailboxState.SettingUp) {
} onCodeScanned();
}); }
} });
} }
@Override @Override
@@ -64,6 +65,17 @@ public class MailboxActivity extends BriarActivity {
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
@Override
public void onBackPressed() {
if (viewModel.getState()
.getValue() instanceof MailboxState.SettingUp) {
// don't go back in flow if we are already setting up mailbox
supportFinishAfterTransition();
} else {
super.onBackPressed();
}
}
private void onNotSetup() { private void onNotSetup() {
progressBar.setVisibility(INVISIBLE); progressBar.setVisibility(INVISIBLE);
getSupportFragmentManager().beginTransaction() getSupportFragmentManager().beginTransaction()
@@ -72,4 +84,10 @@ public class MailboxActivity extends BriarActivity {
.commit(); .commit();
} }
private void onCodeScanned() {
showFragment(getSupportFragmentManager(),
new MailboxConnectingFragment(),
MailboxConnectingFragment.TAG, false);
}
} }

View File

@@ -0,0 +1,36 @@
package org.briarproject.briar.android.mailbox;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class MailboxConnectingFragment extends Fragment {
static final String TAG = MailboxConnectingFragment.class.getName();
@Nullable
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_mailbox_connecting,
container, false);
}
@Override
public void onStart() {
super.onStart();
requireActivity().setTitle(R.string.mailbox_setup_title);
}
}

View File

@@ -0,0 +1,100 @@
package org.briarproject.briar.android.mailbox;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.qrcode.CameraException;
import org.briarproject.briar.android.qrcode.CameraView;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.inject.Inject;
import androidx.annotation.UiThread;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.ViewModelProvider;
import static android.widget.Toast.LENGTH_LONG;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.briar.android.AppModule.getAndroidComponent;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class MailboxScanFragment extends Fragment {
static final String TAG = MailboxScanFragment.class.getName();
private static final Logger LOG = Logger.getLogger(TAG);
@Inject
ViewModelProvider.Factory viewModelFactory;
private MailboxViewModel viewModel;
private CameraView cameraView;
@Override
public void onAttach(Context context) {
super.onAttach(context);
FragmentActivity activity = requireActivity();
getAndroidComponent(activity).inject(this);
viewModel = new ViewModelProvider(activity, viewModelFactory)
.get(MailboxViewModel.class);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_mailbox_scan, container,
false);
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
cameraView = view.findViewById(R.id.camera_view);
cameraView.setPreviewConsumer(viewModel.getQrCodeDecoder());
}
@Override
public void onStart() {
super.onStart();
requireActivity().setTitle(R.string.mailbox_setup_button_scan);
try {
cameraView.start();
} catch (CameraException e) {
logCameraExceptionAndFinish(e);
}
}
@Override
public void onStop() {
super.onStop();
try {
cameraView.stop();
} catch (CameraException e) {
logCameraExceptionAndFinish(e);
}
}
@UiThread
private void logCameraExceptionAndFinish(CameraException e) {
logException(LOG, WARNING, e);
Toast.makeText(requireContext(), R.string.camera_error,
LENGTH_LONG).show();
requireActivity().getSupportFragmentManager().popBackStack();
}
}

View File

@@ -5,6 +5,9 @@ class MailboxState {
static class NotSetup extends MailboxState { static class NotSetup extends MailboxState {
} }
static class SettingUp extends MailboxState {
}
// TODO add other states // TODO add other states
} }

View File

@@ -9,9 +9,10 @@ import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.TransactionManager; import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.AndroidExecutor; import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.bramble.util.StringUtils;
import org.briarproject.briar.android.mailbox.MailboxState.NotSetup; import org.briarproject.briar.android.mailbox.MailboxState.NotSetup;
import org.briarproject.briar.android.qrcode.QrCodeDecoder; import org.briarproject.briar.android.qrcode.QrCodeDecoder;
import org.briarproject.briar.android.viewmodel.DbViewModel; import org.briarproject.briar.android.viewmodel.DbViewModel;
@@ -23,7 +24,6 @@ import java.util.logging.Logger;
import javax.inject.Inject; import javax.inject.Inject;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread; import androidx.annotation.UiThread;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
@@ -47,11 +47,6 @@ class MailboxViewModel extends DbViewModel
private final MutableLiveData<MailboxState> state = new MutableLiveData<>(); private final MutableLiveData<MailboxState> state = new MutableLiveData<>();
@Nullable
private String onionAddress = null;
@Nullable
private String setupToken = null;
@Inject @Inject
MailboxViewModel( MailboxViewModel(
Application app, Application app,
@@ -80,11 +75,6 @@ class MailboxViewModel extends DbViewModel
}); });
} }
@UiThread
LiveData<MailboxState> getState() {
return state;
}
@Override @Override
@IoExecutor @IoExecutor
public void onQrCodeDecoded(Result result) { public void onQrCodeDecoded(Result result) {
@@ -105,15 +95,25 @@ class MailboxViewModel extends DbViewModel
return; return;
} }
byte[] onionPubKey = Arrays.copyOfRange(bytes, 1, 33);
onionAddress = crypto.encodeOnionAddress(onionPubKey);
setupToken = StringUtils.toHexString(Arrays.copyOfRange(bytes, 33, 65))
.toLowerCase();
LOG.info("QR code is valid"); LOG.info("QR code is valid");
byte[] onionPubKey = Arrays.copyOfRange(bytes, 1, 33);
String onionAddress = crypto.encodeOnionAddress(onionPubKey);
byte[] tokenBytes = Arrays.copyOfRange(bytes, 33, 65);
MailboxAuthToken setupToken = new MailboxAuthToken(tokenBytes);
MailboxProperties props =
new MailboxProperties(onionAddress, setupToken, true);
// TODO pass props to core (maybe even do payload parsing there)
state.postValue(new MailboxState.SettingUp());
} }
@UiThread
QrCodeDecoder getQrCodeDecoder() { QrCodeDecoder getQrCodeDecoder() {
return qrCodeDecoder; return qrCodeDecoder;
} }
@UiThread
LiveData<MailboxState> getState() {
return state;
}
} }

View File

@@ -14,12 +14,16 @@ import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import static android.content.Intent.ACTION_SEND; import static android.content.Intent.ACTION_SEND;
import static android.content.Intent.EXTRA_TEXT; import static android.content.Intent.EXTRA_TEXT;
import static android.widget.Toast.LENGTH_LONG; import static android.widget.Toast.LENGTH_LONG;
import static org.briarproject.briar.android.util.UiUtils.showFragment;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
@@ -27,6 +31,16 @@ public class SetupDownloadFragment extends Fragment {
static final String TAG = SetupDownloadFragment.class.getName(); static final String TAG = SetupDownloadFragment.class.getName();
private CameraPermissionManager permissionManager;
private final ActivityResultLauncher<String[]> permissionLauncher =
registerForActivityResult(new RequestMultiplePermissions(), r -> {
permissionManager.onRequestPermissionResult(r);
if (permissionManager.checkPermissions()) {
scanCode();
}
});
@Nullable @Nullable
@Override @Override
public View onCreateView(LayoutInflater inflater, public View onCreateView(LayoutInflater inflater,
@@ -34,10 +48,19 @@ public class SetupDownloadFragment extends Fragment {
@Nullable Bundle savedInstanceState) { @Nullable Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_mailbox_setup_download, View v = inflater.inflate(R.layout.fragment_mailbox_setup_download,
container, false); container, false);
permissionManager = new CameraPermissionManager(requireActivity(),
permissionLauncher::launch);
Button shareLinkButton = v.findViewById(R.id.shareLinkButton); Button shareLinkButton = v.findViewById(R.id.shareLinkButton);
Button scanButton = v.findViewById(R.id.scanButton);
shareLinkButton.setOnClickListener(this::shareLink); shareLinkButton.setOnClickListener(this::shareLink);
scanButton.setOnClickListener(this::scanCode);
Button scanButton = v.findViewById(R.id.scanButton);
scanButton.setOnClickListener(view -> {
if (permissionManager.checkPermissions()) {
scanCode();
}
});
return v; return v;
} }
@@ -45,6 +68,8 @@ public class SetupDownloadFragment extends Fragment {
public void onStart() { public void onStart() {
super.onStart(); super.onStart();
requireActivity().setTitle(R.string.mailbox_setup_title); requireActivity().setTitle(R.string.mailbox_setup_title);
// Permissions may have been granted manually while we were stopped
permissionManager.resetPermissions();
} }
private void shareLink(View v) { private void shareLink(View v) {
@@ -69,8 +94,10 @@ public class SetupDownloadFragment extends Fragment {
} }
} }
private void scanCode(View v) { private void scanCode() {
Toast.makeText(requireContext(), "TODO", LENGTH_LONG).show(); FragmentManager fm = getParentFragmentManager();
Fragment f = new MailboxScanFragment();
showFragment(fm, f, MailboxScanFragment.TAG);
} }
} }

View File

@@ -0,0 +1,5 @@
package org.briarproject.briar.android.util;
public enum Permission {
UNKNOWN, GRANTED, SHOW_RATIONALE, PERMANENTLY_DENIED
}

View File

@@ -52,6 +52,7 @@ import androidx.annotation.ColorRes;
import androidx.annotation.DrawableRes; import androidx.annotation.DrawableRes;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import androidx.annotation.StringRes;
import androidx.annotation.UiThread; import androidx.annotation.UiThread;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
@@ -574,4 +575,26 @@ public class UiUtils {
SOFT_INPUT_STATE_HIDDEN); SOFT_INPUT_STATE_HIDDEN);
} }
public static void showDenialDialog(FragmentActivity ctx,
@StringRes int title, @StringRes int body) {
AlertDialog.Builder builder =
new AlertDialog.Builder(ctx, R.style.BriarDialogTheme);
builder.setTitle(title);
builder.setMessage(body);
builder.setPositiveButton(R.string.ok, getGoToSettingsListener(ctx));
builder.setNegativeButton(R.string.cancel,
(dialog, which) -> ctx.supportFinishAfterTransition());
builder.show();
}
public static void showRationale(FragmentActivity ctx, @StringRes int title,
@StringRes int body, Runnable requestPermissions) {
AlertDialog.Builder builder =
new AlertDialog.Builder(ctx, R.style.BriarDialogTheme);
builder.setTitle(title);
builder.setMessage(body);
builder.setNeutralButton(R.string.continue_button,
(dialog, which) -> requestPermissions.run());
builder.show();
}
} }

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:dither="true"
android:shape="rectangle">
<corners android:radius="32dp" />
<solid android:color="@android:color/transparent" />
<stroke
android:width="4dp"
android:color="#ffffff" />
</shape>

View File

@@ -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"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
app:layout_constraintBottom_toTopOf="@+id/textView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" />
<TextView
android:id="@+id/textView"
style="@style/TextAppearance.AppCompat.Large"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:text="@string/mailbox_setup_connecting"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/progressBar" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,27 @@
<?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">
<org.briarproject.briar.android.qrcode.CameraView
android:id="@+id/camera_view"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="16dp"
android:background="@drawable/border_qr_scanner"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="1,1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -628,6 +628,8 @@
<string name="mailbox_share_fdroid" translatable="false">https://f-droid.org/packages/org.briarproject.mailbox/</string> <string name="mailbox_share_fdroid" translatable="false">https://f-droid.org/packages/org.briarproject.mailbox/</string>
<string name="mailbox_share_gplay" translatable="false">https://play.google.com/store/apps/details?id=org.briarproject.mailbox</string> <string name="mailbox_share_gplay" translatable="false">https://play.google.com/store/apps/details?id=org.briarproject.mailbox</string>
<string name="mailbox_share_download" translatable="false">https://briarproject.org/apk</string> <string name="mailbox_share_download" translatable="false">https://briarproject.org/apk</string>
<string name="permission_camera_qr_denied_body">You have denied access to the camera, but scanning a QR code requires using the camera.\n\nPlease consider granting access.</string>
<string name="mailbox_setup_connecting">Connecting…</string>
<!-- Conversation Settings --> <!-- Conversation Settings -->
<string name="disappearing_messages_title">Disappearing messages</string> <string name="disappearing_messages_title">Disappearing messages</string>