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
This commit is contained in:
parent
72df72228f
commit
f5517f3d49
|
@ -96,6 +96,7 @@ dependencies {
|
||||||
implementation 'com.squareup:otto:1.3.8'
|
implementation 'com.squareup:otto:1.3.8'
|
||||||
implementation 'de.psdev:async-otto:1.0.3'
|
implementation 'de.psdev:async-otto:1.0.3'
|
||||||
implementation 'com.google.zxing:core:3.5.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.microg:safe-parcel:1.5.0'
|
||||||
implementation 'org.parceler:parceler-api:1.1.12'
|
implementation 'org.parceler:parceler-api:1.1.12'
|
||||||
annotationProcessor 'org.parceler:parceler:1.1.12'
|
annotationProcessor 'org.parceler:parceler:1.1.12'
|
||||||
|
|
|
@ -9,6 +9,9 @@
|
||||||
<uses-permission android:name="${applicationId}.permission.C2D_MESSAGE"/>
|
<uses-permission android:name="${applicationId}.permission.C2D_MESSAGE"/>
|
||||||
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE"/>
|
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE"/>
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||||
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
|
<uses-feature android:name="android.hardware.camera" />
|
||||||
|
<uses-feature android:name="android.hardware.camera.autofocus" />
|
||||||
|
|
||||||
<permission android:name="${applicationId}.permission.C2D_MESSAGE" android:protectionLevel="signature"/>
|
<permission android:name="${applicationId}.permission.C2D_MESSAGE" android:protectionLevel="signature"/>
|
||||||
|
|
||||||
|
@ -79,6 +82,7 @@
|
||||||
<data android:mimeType="*/*"/>
|
<data android:mimeType="*/*"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity android:name=".QrCodeScanActivity"/>
|
||||||
|
|
||||||
<service android:name=".AudioPlayerService" android:foregroundServiceType="mediaPlayback"/>
|
<service android:name=".AudioPlayerService" android:foregroundServiceType="mediaPlayback"/>
|
||||||
<service android:name=".NotificationActionHandlerService" android:exported="false"/>
|
<service android:name=".NotificationActionHandlerService" android:exported="false"/>
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -53,7 +53,6 @@ import android.widget.ImageView;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
import android.window.OnBackInvokedCallback;
|
|
||||||
import android.window.OnBackInvokedDispatcher;
|
import android.window.OnBackInvokedDispatcher;
|
||||||
|
|
||||||
import com.google.zxing.BarcodeFormat;
|
import com.google.zxing.BarcodeFormat;
|
||||||
|
@ -64,6 +63,7 @@ import com.google.zxing.qrcode.QRCodeWriter;
|
||||||
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
|
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
|
||||||
|
|
||||||
import org.joinmastodon.android.MainActivity;
|
import org.joinmastodon.android.MainActivity;
|
||||||
|
import org.joinmastodon.android.QrCodeScanActivity;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.MastodonAPIController;
|
import org.joinmastodon.android.api.MastodonAPIController;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
|
@ -127,7 +127,10 @@ public class ProfileQrCodeFragment extends AppKitFragment{
|
||||||
accountID=getArguments().getString("account");
|
accountID=getArguments().getString("account");
|
||||||
account=Parcels.unwrap(getArguments().getParcelable("targetAccount"));
|
account=Parcels.unwrap(getArguments().getParcelable("targetAccount"));
|
||||||
setCancelable(false);
|
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
|
@Override
|
||||||
|
@ -265,11 +268,9 @@ public class ProfileQrCodeFragment extends AppKitFragment{
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
||||||
if(GmsClient.isGooglePlayServicesAvailable(getActivity())){
|
MenuItem item=menu.add(0, 0, 0, R.string.scan_qr_code);
|
||||||
MenuItem item=menu.add(0, 0, 0, R.string.scan_qr_code);
|
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
||||||
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
item.setIcon(R.drawable.ic_qr_code_scanner_24px);
|
||||||
item.setIcon(R.drawable.ic_qr_code_scanner_24px);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -330,11 +331,15 @@ public class ProfileQrCodeFragment extends AppKitFragment{
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onActivityResult(int requestCode, int resultCode, Intent data){
|
public void onActivityResult(int requestCode, int resultCode, Intent data){
|
||||||
if(requestCode==SCAN_RESULT && resultCode==Activity.RESULT_OK && BarcodeScanner.isValidResult(data)){
|
if(requestCode==SCAN_RESULT && resultCode==Activity.RESULT_OK){
|
||||||
Barcode code=BarcodeScanner.getResult(data);
|
String code=data.getStringExtra("barcode");
|
||||||
|
if(BarcodeScanner.isValidResult(data)){
|
||||||
|
Barcode barcode=BarcodeScanner.getResult(data);
|
||||||
|
code=barcode.rawValue;
|
||||||
|
}
|
||||||
if(code!=null){
|
if(code!=null){
|
||||||
if(code.rawValue.startsWith("https:") || code.rawValue.startsWith("http:")){
|
if(code.startsWith("https:") || code.startsWith("http:")){
|
||||||
((MainActivity)getActivity()).handleURL(Uri.parse(code.rawValue), accountID);
|
((MainActivity)getActivity()).handleURL(Uri.parse(code), accountID);
|
||||||
dismiss();
|
dismiss();
|
||||||
}else{
|
}else{
|
||||||
Toast.makeText(themeWrapper, R.string.link_not_supported, Toast.LENGTH_SHORT).show();
|
Toast.makeText(themeWrapper, R.string.link_not_supported, Toast.LENGTH_SHORT).show();
|
||||||
|
|
|
@ -17,6 +17,7 @@ import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import org.joinmastodon.android.MainActivity;
|
import org.joinmastodon.android.MainActivity;
|
||||||
|
import org.joinmastodon.android.QrCodeScanActivity;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.fragments.ScrollableToTop;
|
import org.joinmastodon.android.fragments.ScrollableToTop;
|
||||||
import org.joinmastodon.android.googleservices.GmsClient;
|
import org.joinmastodon.android.googleservices.GmsClient;
|
||||||
|
@ -70,7 +71,9 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop{
|
||||||
setRetainInstance(true);
|
setRetainInstance(true);
|
||||||
|
|
||||||
accountID=getArguments().getString("account");
|
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
|
@Nullable
|
||||||
|
@ -186,11 +189,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop{
|
||||||
searchView.setVisibility(View.VISIBLE);
|
searchView.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
searchScanQR=view.findViewById(R.id.search_scan_qr);
|
searchScanQR=view.findViewById(R.id.search_scan_qr);
|
||||||
if(!GmsClient.isGooglePlayServicesAvailable(getActivity())){
|
searchScanQR.setOnClickListener(v->openQrScanner());
|
||||||
searchScanQR.setVisibility(View.GONE);
|
|
||||||
}else{
|
|
||||||
searchScanQR.setOnClickListener(v->openQrScanner());
|
|
||||||
}
|
|
||||||
|
|
||||||
View searchWrap=view.findViewById(R.id.search_wrap);
|
View searchWrap=view.findViewById(R.id.search_wrap);
|
||||||
searchWrap.setOutlineProvider(OutlineProviders.roundedRect(28));
|
searchWrap.setOutlineProvider(OutlineProviders.roundedRect(28));
|
||||||
|
@ -280,11 +279,15 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop{
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onActivityResult(int requestCode, int resultCode, Intent data){
|
public void onActivityResult(int requestCode, int resultCode, Intent data){
|
||||||
if(requestCode==SCAN_RESULT && resultCode==Activity.RESULT_OK && BarcodeScanner.isValidResult(data)){
|
if(requestCode==SCAN_RESULT && resultCode==Activity.RESULT_OK){
|
||||||
Barcode code=BarcodeScanner.getResult(data);
|
String code=data.getStringExtra("barcode");
|
||||||
|
if(BarcodeScanner.isValidResult(data)){
|
||||||
|
Barcode barcode=BarcodeScanner.getResult(data);
|
||||||
|
code=barcode.rawValue;
|
||||||
|
}
|
||||||
if(code!=null){
|
if(code!=null){
|
||||||
if(code.rawValue.startsWith("https:") || code.rawValue.startsWith("http:")){
|
if(code.startsWith("https:") || code.startsWith("http:")){
|
||||||
((MainActivity)getActivity()).handleURL(Uri.parse(code.rawValue), accountID);
|
((MainActivity)getActivity()).handleURL(Uri.parse(code), accountID);
|
||||||
}else{
|
}else{
|
||||||
Toast.makeText(getActivity(), R.string.link_not_supported, Toast.LENGTH_SHORT).show();
|
Toast.makeText(getActivity(), R.string.link_not_supported, Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<FrameLayout 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"
|
||||||
|
android:clipChildren="false"
|
||||||
|
android:clipToPadding="false">
|
||||||
|
|
||||||
|
<de.markusfisch.android.barcodescannerview.widget.BarcodeScannerView
|
||||||
|
android:id="@+id/scanner"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"/>
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/dismiss"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="start|top"
|
||||||
|
android:background="?android:selectableItemBackground"
|
||||||
|
android:contentDescription="@string/back"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:src="@drawable/ic_arrow_back"
|
||||||
|
android:tint="@android:color/white"
|
||||||
|
android:layout_margin="16dp"/>
|
||||||
|
</FrameLayout>
|
|
@ -233,6 +233,7 @@
|
||||||
<string name="download">Download</string>
|
<string name="download">Download</string>
|
||||||
<string name="permission_required">Permission required</string>
|
<string name="permission_required">Permission required</string>
|
||||||
<string name="storage_permission_to_download">The app needs access to your storage to save this file.</string>
|
<string name="storage_permission_to_download">The app needs access to your storage to save this file.</string>
|
||||||
|
<string name="camera_permission_to_scan">The app needs access to your camera to scan.</string>
|
||||||
<string name="open_settings">Open settings</string>
|
<string name="open_settings">Open settings</string>
|
||||||
<string name="error_saving_file">Error saving file</string>
|
<string name="error_saving_file">Error saving file</string>
|
||||||
<string name="file_saved">File saved</string>
|
<string name="file_saved">File saved</string>
|
||||||
|
|
|
@ -4,6 +4,7 @@ dependencyResolutionManagement {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
|
maven { url 'https://jitpack.io' }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rootProject.name = "Mastodon"
|
rootProject.name = "Mastodon"
|
||||||
|
|
Loading…
Reference in New Issue