From f5517f3d4958ee456996fd9acf6516bdf256cb5e Mon Sep 17 00:00:00 2001 From: FineFindus Date: Mon, 8 Jul 2024 21:34:01 +0200 Subject: [PATCH] feat: fallback to zxing-based lib to scan QrCodes Uses a zxing-based library as a fallback when scanning a Qr-Code and GMS is not available. Closes https://github.com/mastodon/mastodon-android/issues/825 --- mastodon/build.gradle | 1 + mastodon/src/main/AndroidManifest.xml | 4 + .../android/QrCodeScanActivity.java | 98 +++++++++++++++++++ .../fragments/ProfileQrCodeFragment.java | 27 ++--- .../fragments/discover/DiscoverFragment.java | 23 +++-- .../src/main/res/layout/activity_qr_scan.xml | 26 +++++ mastodon/src/main/res/values/strings.xml | 1 + settings.gradle | 1 + 8 files changed, 160 insertions(+), 21 deletions(-) create mode 100644 mastodon/src/main/java/org/joinmastodon/android/QrCodeScanActivity.java create mode 100644 mastodon/src/main/res/layout/activity_qr_scan.xml diff --git a/mastodon/build.gradle b/mastodon/build.gradle index c50342df49..133af60a49 100644 --- a/mastodon/build.gradle +++ b/mastodon/build.gradle @@ -96,6 +96,7 @@ dependencies { implementation 'com.squareup:otto:1.3.8' implementation 'de.psdev:async-otto:1.0.3' implementation 'com.google.zxing:core:3.5.3' + implementation 'com.github.markusfisch:BarcodeScannerView:1.6.0' implementation 'org.microg:safe-parcel:1.5.0' implementation 'org.parceler:parceler-api:1.1.12' annotationProcessor 'org.parceler:parceler:1.1.12' diff --git a/mastodon/src/main/AndroidManifest.xml b/mastodon/src/main/AndroidManifest.xml index 7a97cfcee7..fb661ca008 100644 --- a/mastodon/src/main/AndroidManifest.xml +++ b/mastodon/src/main/AndroidManifest.xml @@ -9,6 +9,9 @@ + + + @@ -79,6 +82,7 @@ + diff --git a/mastodon/src/main/java/org/joinmastodon/android/QrCodeScanActivity.java b/mastodon/src/main/java/org/joinmastodon/android/QrCodeScanActivity.java new file mode 100644 index 0000000000..f16fb11e69 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/QrCodeScanActivity.java @@ -0,0 +1,98 @@ +package org.joinmastodon.android; + +import android.Manifest; +import android.app.Activity; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.VibrationEffect; +import android.os.Vibrator; +import android.provider.Settings; +import android.view.HapticFeedbackConstants; +import android.view.View; + +import androidx.annotation.NonNull; + +import org.joinmastodon.android.ui.M3AlertDialogBuilder; +import org.joinmastodon.android.ui.utils.UiUtils; + +import de.markusfisch.android.barcodescannerview.widget.BarcodeScannerView; + +public class QrCodeScanActivity extends Activity{ + private static final int PERMISSION_RESULT=65537; + private BarcodeScannerView scannerView; + + @Override + public void onCreate(Bundle savedInstanceState){ + super.onCreate(savedInstanceState); + UiUtils.setUserPreferredTheme(this); + setContentView(R.layout.activity_qr_scan); + + if(this.checkSelfPermission(Manifest.permission.CAMERA)!=PackageManager.PERMISSION_GRANTED){ + requestPermissions(new String[]{Manifest.permission.CAMERA}, PERMISSION_RESULT); + } + + findViewById(R.id.dismiss).setOnClickListener(view -> finish()); + scannerView=findViewById(R.id.scanner); + scannerView.setCropRatio(.75f); + scannerView.setOnBarcodeListener(barcode -> { + vibrate(scannerView); + Intent result=new Intent(); + result.putExtra("barcode", barcode.getText()); + setResult(RESULT_OK, result); + finish(); + return false; + }); + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults){ + if(requestCode==PERMISSION_RESULT){ + if(grantResults[0]==PackageManager.PERMISSION_GRANTED){ + scannerView.openAsync(); + }else if(!this.shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)){ + new M3AlertDialogBuilder(this) + .setTitle(R.string.permission_required) + .setMessage(R.string.camera_permission_to_scan) + .setPositiveButton(R.string.open_settings, (dialog, which)->this.startActivity(new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.fromParts("package", this.getPackageName(), null)))) + .setNegativeButton(R.string.cancel, (dialogInterface, i) -> finish()) + .setOnCancelListener(dialogInterface -> finish()) + .show(); + } + } + } + + + private static void vibrate(View v) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + v.performHapticFeedback(HapticFeedbackConstants.CONFIRM); + return; + } + + Vibrator vibrator=v.getContext().getSystemService(Vibrator.class); + + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) { + vibrator.vibrate(VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + VibrationEffect effect=VibrationEffect.createOneShot(75L, 128); + vibrator.vibrate(effect); + } else { + vibrator.vibrate(75L); + } + } + + + @Override + public void onResume() { + super.onResume(); + scannerView.openAsync(); + } + + @Override + public void onPause() { + super.onPause(); + scannerView.close(); + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileQrCodeFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileQrCodeFragment.java index 32094a6410..d4d785adff 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileQrCodeFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileQrCodeFragment.java @@ -53,7 +53,6 @@ import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; -import android.window.OnBackInvokedCallback; import android.window.OnBackInvokedDispatcher; import com.google.zxing.BarcodeFormat; @@ -64,6 +63,7 @@ import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; import org.joinmastodon.android.MainActivity; +import org.joinmastodon.android.QrCodeScanActivity; import org.joinmastodon.android.R; import org.joinmastodon.android.api.MastodonAPIController; import org.joinmastodon.android.api.session.AccountSessionManager; @@ -127,7 +127,10 @@ public void onCreate(Bundle savedInstanceState){ accountID=getArguments().getString("account"); account=Parcels.unwrap(getArguments().getParcelable("targetAccount")); setCancelable(false); - scannerIntent=BarcodeScanner.createIntent(Barcode.FORMAT_QR_CODE, false, true); + scannerIntent=GmsClient.isGooglePlayServicesAvailable(getActivity()) + ? BarcodeScanner.createIntent(Barcode.FORMAT_QR_CODE, false, true) + : new Intent(getActivity(), QrCodeScanActivity.class); + } @Override @@ -265,11 +268,9 @@ public void dismiss(){ @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){ - if(GmsClient.isGooglePlayServicesAvailable(getActivity())){ - MenuItem item=menu.add(0, 0, 0, R.string.scan_qr_code); - item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); - item.setIcon(R.drawable.ic_qr_code_scanner_24px); - } + MenuItem item=menu.add(0, 0, 0, R.string.scan_qr_code); + item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); + item.setIcon(R.drawable.ic_qr_code_scanner_24px); } @Override @@ -330,11 +331,15 @@ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permis @Override public void onActivityResult(int requestCode, int resultCode, Intent data){ - if(requestCode==SCAN_RESULT && resultCode==Activity.RESULT_OK && BarcodeScanner.isValidResult(data)){ - Barcode code=BarcodeScanner.getResult(data); + if(requestCode==SCAN_RESULT && resultCode==Activity.RESULT_OK){ + String code=data.getStringExtra("barcode"); + if(BarcodeScanner.isValidResult(data)){ + Barcode barcode=BarcodeScanner.getResult(data); + code=barcode.rawValue; + } if(code!=null){ - if(code.rawValue.startsWith("https:") || code.rawValue.startsWith("http:")){ - ((MainActivity)getActivity()).handleURL(Uri.parse(code.rawValue), accountID); + if(code.startsWith("https:") || code.startsWith("http:")){ + ((MainActivity)getActivity()).handleURL(Uri.parse(code), accountID); dismiss(); }else{ Toast.makeText(themeWrapper, R.string.link_not_supported, Toast.LENGTH_SHORT).show(); diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverFragment.java index ca93bdd45d..f093672af2 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverFragment.java @@ -17,6 +17,7 @@ import android.widget.Toast; import org.joinmastodon.android.MainActivity; +import org.joinmastodon.android.QrCodeScanActivity; import org.joinmastodon.android.R; import org.joinmastodon.android.fragments.ScrollableToTop; import org.joinmastodon.android.googleservices.GmsClient; @@ -70,7 +71,9 @@ public void onCreate(Bundle savedInstanceState){ setRetainInstance(true); accountID=getArguments().getString("account"); - scannerIntent=BarcodeScanner.createIntent(Barcode.FORMAT_QR_CODE, false, true); + scannerIntent=GmsClient.isGooglePlayServicesAvailable(getActivity()) + ? BarcodeScanner.createIntent(Barcode.FORMAT_QR_CODE, false, true) + : new Intent(getActivity(), QrCodeScanActivity.class); } @Nullable @@ -186,11 +189,7 @@ public void onTabReselected(TabLayout.Tab tab){ searchView.setVisibility(View.VISIBLE); } searchScanQR=view.findViewById(R.id.search_scan_qr); - if(!GmsClient.isGooglePlayServicesAvailable(getActivity())){ - searchScanQR.setVisibility(View.GONE); - }else{ - searchScanQR.setOnClickListener(v->openQrScanner()); - } + searchScanQR.setOnClickListener(v->openQrScanner()); View searchWrap=view.findViewById(R.id.search_wrap); searchWrap.setOutlineProvider(OutlineProviders.roundedRect(28)); @@ -280,11 +279,15 @@ public void onFragmentResult(int reqCode, boolean success, Bundle result){ @Override public void onActivityResult(int requestCode, int resultCode, Intent data){ - if(requestCode==SCAN_RESULT && resultCode==Activity.RESULT_OK && BarcodeScanner.isValidResult(data)){ - Barcode code=BarcodeScanner.getResult(data); + if(requestCode==SCAN_RESULT && resultCode==Activity.RESULT_OK){ + String code=data.getStringExtra("barcode"); + if(BarcodeScanner.isValidResult(data)){ + Barcode barcode=BarcodeScanner.getResult(data); + code=barcode.rawValue; + } if(code!=null){ - if(code.rawValue.startsWith("https:") || code.rawValue.startsWith("http:")){ - ((MainActivity)getActivity()).handleURL(Uri.parse(code.rawValue), accountID); + if(code.startsWith("https:") || code.startsWith("http:")){ + ((MainActivity)getActivity()).handleURL(Uri.parse(code), accountID); }else{ Toast.makeText(getActivity(), R.string.link_not_supported, Toast.LENGTH_SHORT).show(); } diff --git a/mastodon/src/main/res/layout/activity_qr_scan.xml b/mastodon/src/main/res/layout/activity_qr_scan.xml new file mode 100644 index 0000000000..6fc27c3acd --- /dev/null +++ b/mastodon/src/main/res/layout/activity_qr_scan.xml @@ -0,0 +1,26 @@ + + + + + + + diff --git a/mastodon/src/main/res/values/strings.xml b/mastodon/src/main/res/values/strings.xml index a7967c8861..6cd316464a 100644 --- a/mastodon/src/main/res/values/strings.xml +++ b/mastodon/src/main/res/values/strings.xml @@ -233,6 +233,7 @@ Download Permission required The app needs access to your storage to save this file. + The app needs access to your camera to scan. Open settings Error saving file File saved diff --git a/settings.gradle b/settings.gradle index ff7f3f3b60..c8ea317756 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,6 +4,7 @@ dependencyResolutionManagement { google() mavenCentral() mavenLocal() + maven { url 'https://jitpack.io' } } } rootProject.name = "Mastodon"