diff --git a/app/build.gradle b/app/build.gradle index 35efdbf..ac7e36b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,5 +1,6 @@ plugins { id 'com.android.application' + id 'com.google.protobuf' } android { @@ -29,6 +30,32 @@ android { buildFeatures { viewBinding true } + + sourceSets { + main { + java { + srcDir "build/generated/source/proto/debug/javalite" + } + proto { + srcDir 'src/main/proto' + } + } + } +} + +protobuf { + protoc { + artifact = 'com.google.protobuf:protoc:21.0-rc-1' + } + generateProtoTasks { + all().each { task -> + task.builtins { + java { + option "lite" + } + } + } + } } dependencies { @@ -43,7 +70,7 @@ dependencies { androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' implementation "androidx.biometric:biometric:1.1.0" - implementation 'com.cringe_studios:CringeAuthenticatorLibrary:1.4' + implementation 'com.cringe_studios:CringeAuthenticatorLibrary:1.5' implementation 'com.google.mlkit:barcode-scanning:17.1.0' implementation 'com.google.code.gson:gson:2.8.9' @@ -56,4 +83,6 @@ dependencies { implementation "androidx.camera:camera-view:${camerax_version}" implementation "androidx.camera:camera-extensions:${camerax_version}" -} \ No newline at end of file + + implementation 'com.google.protobuf:protobuf-javalite:3.20.1' +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 84240f9..33fa8eb 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -47,6 +47,7 @@ + diff --git a/app/src/main/java/com/cringe_studios/cringe_authenticator/MainActivity.java b/app/src/main/java/com/cringe_studios/cringe_authenticator/MainActivity.java index 9d4ffce..8d25ada 100644 --- a/app/src/main/java/com/cringe_studios/cringe_authenticator/MainActivity.java +++ b/app/src/main/java/com/cringe_studios/cringe_authenticator/MainActivity.java @@ -30,11 +30,13 @@ import com.cringe_studios.cringe_authenticator.fragment.HomeFragment; import com.cringe_studios.cringe_authenticator.fragment.MenuFragment; import com.cringe_studios.cringe_authenticator.fragment.NamedFragment; import com.cringe_studios.cringe_authenticator.fragment.SettingsFragment; +import com.cringe_studios.cringe_authenticator.model.OTPData; import com.cringe_studios.cringe_authenticator.scanner.QRScannerContract; import com.cringe_studios.cringe_authenticator.util.DialogUtil; import com.cringe_studios.cringe_authenticator.util.NavigationUtil; import com.cringe_studios.cringe_authenticator.util.SettingsUtil; import com.cringe_studios.cringe_authenticator.util.StyledDialogBuilder; +import com.cringe_studios.cringe_authenticator.util.ThemeUtil; import com.cringe_studios.cringe_authenticator_library.OTPType; import java.util.Locale; @@ -58,12 +60,7 @@ public class MainActivity extends AppCompatActivity { //getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE); TODO: enable secure flag - Integer themeID = SettingsUtil.THEMES.get(SettingsUtil.getTheme(this)); - if(themeID != null) { - setTheme(themeID); - }else { - setTheme(R.style.Theme_CringeAuthenticator_Blue_Green); - } + ThemeUtil.loadTheme(this); setLocale(SettingsUtil.getLocale(this)); @@ -106,7 +103,7 @@ public class MainActivity extends AppCompatActivity { Fragment fragment = NavigationUtil.getCurrentFragment(this); if(fragment instanceof GroupFragment) { GroupFragment frag = (GroupFragment) fragment; - frag.addOTP(obj.getData()); + for(OTPData d : obj.getData()) frag.addOTP(d); } }); } @@ -238,7 +235,7 @@ public class MainActivity extends AppCompatActivity { if(frag instanceof MenuFragment) { ((MenuFragment) frag).addGroup(group); } - }); + }, null); } @Override diff --git a/app/src/main/java/com/cringe_studios/cringe_authenticator/fragment/MenuFragment.java b/app/src/main/java/com/cringe_studios/cringe_authenticator/fragment/MenuFragment.java index 0051ec1..3bdd609 100644 --- a/app/src/main/java/com/cringe_studios/cringe_authenticator/fragment/MenuFragment.java +++ b/app/src/main/java/com/cringe_studios/cringe_authenticator/fragment/MenuFragment.java @@ -59,7 +59,7 @@ public class MenuFragment extends NamedFragment { case 0: DialogUtil.showCreateGroupDialog(getLayoutInflater(), SettingsUtil.getGroupName(requireContext(), group), newName -> { renameGroup(group, newName); - }); + }, null); break; case 1: diff --git a/app/src/main/java/com/cringe_studios/cringe_authenticator/model/OTPData.java b/app/src/main/java/com/cringe_studios/cringe_authenticator/model/OTPData.java index 37e2dec..04e1b2e 100644 --- a/app/src/main/java/com/cringe_studios/cringe_authenticator/model/OTPData.java +++ b/app/src/main/java/com/cringe_studios/cringe_authenticator/model/OTPData.java @@ -1,7 +1,5 @@ package com.cringe_studios.cringe_authenticator.model; -import androidx.annotation.NonNull; - import com.cringe_studios.cringe_authenticator_library.OTP; import com.cringe_studios.cringe_authenticator_library.OTPAlgorithm; import com.cringe_studios.cringe_authenticator_library.OTPException; @@ -13,6 +11,7 @@ import java.util.Objects; public class OTPData implements Serializable { private String name; + private String issuer; private OTPType type; private String secret; private OTPAlgorithm algorithm; @@ -24,8 +23,9 @@ public class OTPData implements Serializable { // Cached private transient OTP otp; - public OTPData(String name, OTPType type, String secret, OTPAlgorithm algorithm, int digits, int period, long counter, boolean checksum) { + public OTPData(String name, String issuer, OTPType type, String secret, OTPAlgorithm algorithm, int digits, int period, long counter, boolean checksum) { this.name = name; + this.issuer = issuer; this.type = type; this.secret = secret; this.algorithm = algorithm; @@ -39,6 +39,10 @@ public class OTPData implements Serializable { return name; } + public String getIssuer() { + return issuer; + } + public OTPType getType() { return type; } @@ -98,31 +102,17 @@ public class OTPData implements Serializable { } } - @NonNull - @Override - public String toString() { - return "OTPData{" + - "name='" + name + '\'' + - ", type=" + type + - ", secret='" + secret + '\'' + - ", algorithm=" + algorithm + - ", digits=" + digits + - ", period=" + period + - ", counter=" + counter + - ", checksum=" + checksum + - '}'; - } - @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; OTPData otpData = (OTPData) o; - return digits == otpData.digits && period == otpData.period && counter == otpData.counter && Objects.equals(name, otpData.name) && type == otpData.type && Objects.equals(secret, otpData.secret) && algorithm == otpData.algorithm; + return digits == otpData.digits && period == otpData.period && counter == otpData.counter && checksum == otpData.checksum && Objects.equals(name, otpData.name) && Objects.equals(issuer, otpData.issuer) && type == otpData.type && Objects.equals(secret, otpData.secret) && algorithm == otpData.algorithm && Objects.equals(otp, otpData.otp); } @Override public int hashCode() { - return Objects.hash(name, type, secret, algorithm, digits, period, counter); + return Objects.hash(name, issuer, type, secret, algorithm, digits, period, counter, checksum, otp); } + } diff --git a/app/src/main/java/com/cringe_studios/cringe_authenticator/scanner/QRScannerActivity.java b/app/src/main/java/com/cringe_studios/cringe_authenticator/scanner/QRScannerActivity.java index e5ccfb4..154787e 100644 --- a/app/src/main/java/com/cringe_studios/cringe_authenticator/scanner/QRScannerActivity.java +++ b/app/src/main/java/com/cringe_studios/cringe_authenticator/scanner/QRScannerActivity.java @@ -2,6 +2,7 @@ package com.cringe_studios.cringe_authenticator.scanner; import android.Manifest; import android.app.Activity; +import android.app.Dialog; import android.content.Intent; import android.content.pm.PackageManager; import android.media.Image; @@ -27,9 +28,12 @@ import androidx.camera.core.SurfaceOrientedMeteringPointFactory; import androidx.camera.lifecycle.ProcessCameraProvider; import androidx.core.content.ContextCompat; +import com.cringe_studios.cringe_authenticator.R; import com.cringe_studios.cringe_authenticator.databinding.ActivityQrScannerBinding; import com.cringe_studios.cringe_authenticator.model.OTPData; import com.cringe_studios.cringe_authenticator.util.OTPParser; +import com.cringe_studios.cringe_authenticator.util.StyledDialogBuilder; +import com.cringe_studios.cringe_authenticator.util.ThemeUtil; import com.google.common.util.concurrent.ListenableFuture; import com.google.mlkit.vision.barcode.BarcodeScanner; import com.google.mlkit.vision.barcode.BarcodeScanning; @@ -48,12 +52,16 @@ public class QRScannerActivity extends AppCompatActivity { private BarcodeScanner scanner; + private boolean process = true; + @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE); + ThemeUtil.loadTheme(this); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { requestPermissions(new String[] {Manifest.permission.CAMERA}, 1234); } @@ -113,6 +121,8 @@ public class QRScannerActivity extends AppCompatActivity { @OptIn(markerClass = ExperimentalGetImage.class) @Override public void analyze(@NonNull ImageProxy image) { + if(!process) return; + Image mediaImage = image.getImage(); if(mediaImage != null) { InputImage input = InputImage.fromMediaImage(mediaImage, image.getImageInfo().getRotationDegrees()); @@ -132,18 +142,40 @@ public class QRScannerActivity extends AppCompatActivity { if(code == null) return; Uri uri = Uri.parse(code.getRawValue()); - try { - success(OTPParser.parse(uri)); - }catch(IllegalArgumentException e) { - error(e.getMessage()); - } + process = false; + importUri(uri); } }).addOnFailureListener(e -> {}); } } } - private void success(@NonNull OTPData data) { + private void importUri(Uri uri) { + if("otpauth-migration".equalsIgnoreCase(uri.getScheme())) { + Dialog dialog = new StyledDialogBuilder(this) + .setTitle(R.string.qr_scanner_migration_title) + .setMessage(R.string.qr_scanner_migration_message) + .setPositiveButton(R.string.yes, (d, which) -> { + try { + success(OTPParser.parseMigration(uri)); + }catch(IllegalArgumentException e) { + error(e.getMessage()); + } + }) + .setNegativeButton(R.string.no, (d, which) -> cancel()) + .setOnDismissListener(d -> cancel()) + .show(); + return; + } + + try { + success(OTPParser.parse(uri)); + }catch(IllegalArgumentException e) { + error(e.getMessage()); + } + } + + private void success(@NonNull OTPData... data) { Intent result = new Intent(); result.putExtra("data", data); setResult(Activity.RESULT_OK, result); @@ -157,4 +189,9 @@ public class QRScannerActivity extends AppCompatActivity { finish(); } + private void cancel() { + setResult(RESULT_CANCELED); + finish(); + } + } diff --git a/app/src/main/java/com/cringe_studios/cringe_authenticator/scanner/QRScannerContract.java b/app/src/main/java/com/cringe_studios/cringe_authenticator/scanner/QRScannerContract.java index f0ecf39..f73b342 100644 --- a/app/src/main/java/com/cringe_studios/cringe_authenticator/scanner/QRScannerContract.java +++ b/app/src/main/java/com/cringe_studios/cringe_authenticator/scanner/QRScannerContract.java @@ -28,7 +28,7 @@ public class QRScannerContract extends ActivityResultContract groups = SettingsUtil.getGroups(this); - String[] groupNames = new String[groups.size()]; - for(int i = 0; i < groups.size(); i++) { - groupNames[i] = SettingsUtil.getGroupName(this, groups.get(i)); - } - - // TODO: add option to create new group? - AlertDialog dialog = new StyledDialogBuilder(this) - .setTitle(R.string.uri_handler_add_code_title) - .setItems(groupNames, (d, which) -> { - SettingsUtil.addOTP(this, groups.get(which), data); - Toast.makeText(this, R.string.uri_handler_code_added, Toast.LENGTH_SHORT).show(); - }) - .setPositiveButton(R.string.ok, (d, which) -> finish()) - .create(); - - dialog.setOnDismissListener(d -> finish()); - dialog.show(); + OTPData data = OTPParser.parse(uri); + importCodes(data); }catch(IllegalArgumentException e) { AlertDialog dialog = new StyledDialogBuilder(this) .setTitle(R.string.uri_handler_failed_title) @@ -60,4 +57,26 @@ public class URIHandlerActivity extends AppCompatActivity { } } + private void importMigration(Uri uri) { + try { + OTPData[] data = OTPParser.parseMigration(uri); + importCodes(data); + }catch(IllegalArgumentException e) { + AlertDialog dialog = new StyledDialogBuilder(this) + .setTitle(R.string.uri_handler_failed_title) + .setMessage(e.getMessage()) + .setPositiveButton(R.string.ok, (d, which) -> finish()) + .create(); + + dialog.setOnDismissListener(d -> finish()); + dialog.show(); + } + } + + private void importCodes(OTPData... data) { + DialogUtil.showImportCodeDialog(this, group -> { + for(OTPData d : data) SettingsUtil.addOTP(this, group, d); + }, this::finish); + } + } diff --git a/app/src/main/java/com/cringe_studios/cringe_authenticator/util/DialogUtil.java b/app/src/main/java/com/cringe_studios/cringe_authenticator/util/DialogUtil.java index 239affa..db4a495 100644 --- a/app/src/main/java/com/cringe_studios/cringe_authenticator/util/DialogUtil.java +++ b/app/src/main/java/com/cringe_studios/cringe_authenticator/util/DialogUtil.java @@ -5,6 +5,7 @@ import android.view.LayoutInflater; import android.view.View; import android.widget.ArrayAdapter; import android.widget.Button; +import android.widget.Toast; import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; @@ -19,6 +20,8 @@ import com.cringe_studios.cringe_authenticator_library.OTPAlgorithm; import com.cringe_studios.cringe_authenticator_library.OTPType; import java.util.Arrays; +import java.util.List; +import java.util.UUID; public class DialogUtil { @@ -81,13 +84,23 @@ public class DialogUtil { showCodeDialog(context, binding.getRoot(), () -> { try { String name = binding.inputName.getText().toString(); + if(name.trim().isEmpty()) { + showErrorDialog(context, context.getString(R.string.otp_add_missing_name)); + return false; + } + + String issuer = binding.inputIssuer.getText().toString(); + if(issuer.trim().isEmpty()) { + issuer = null; + } + String secret = binding.inputSecret.getText().toString(); OTPAlgorithm algorithm = (OTPAlgorithm) binding.inputAlgorithm.getSelectedItem(); int digits = (int) binding.inputDigits.getSelectedItem(); int period = Integer.parseInt(binding.inputPeriod.getText().toString()); boolean checksum = binding.inputChecksum.isChecked(); - OTPData data = new OTPData(name, OTPType.TOTP, secret, algorithm, digits, period, 0, checksum); + OTPData data = new OTPData(name, issuer, OTPType.TOTP, secret, algorithm, digits, period, 0, checksum); String errorMessage = data.validate(); if(errorMessage != null) { @@ -134,13 +147,23 @@ public class DialogUtil { showCodeDialog(context, binding.getRoot(), () -> { try { String name = binding.inputName.getText().toString(); + if(name.trim().isEmpty()) { + showErrorDialog(context, context.getString(R.string.otp_add_missing_name)); + return false; + } + + String issuer = binding.inputIssuer.getText().toString(); + if(issuer.trim().isEmpty()) { + issuer = null; + } + String secret = binding.inputSecret.getText().toString(); OTPAlgorithm algorithm = (OTPAlgorithm) binding.inputAlgorithm.getSelectedItem(); int digits = (int) binding.inputDigits.getSelectedItem(); int counter = Integer.parseInt(binding.inputCounter.getText().toString()); boolean checksum = binding.inputChecksum.isChecked(); - OTPData data = new OTPData(name, OTPType.TOTP, secret, algorithm, digits, 0, counter, checksum); + OTPData data = new OTPData(name, issuer, OTPType.TOTP, secret, algorithm, digits, 0, counter, checksum); String errorMessage = data.validate(); if(errorMessage != null) { @@ -171,7 +194,7 @@ public class DialogUtil { } } - public static void showCreateGroupDialog(LayoutInflater inflater, String initialName, Consumer callback) { + public static void showCreateGroupDialog(LayoutInflater inflater, String initialName, Consumer callback, Runnable onDismiss) { Context context = inflater.getContext(); DialogCreateGroupBinding binding = DialogCreateGroupBinding.inflate(inflater); @@ -181,7 +204,7 @@ public class DialogUtil { .setTitle(R.string.action_new_group) .setView(binding.getRoot()) .setPositiveButton(R.string.add, (view, which) -> {}) - .setNegativeButton(R.string.cancel, (view, which) -> {}) + .setNegativeButton(R.string.cancel, (view, which) -> { if(onDismiss != null) onDismiss.run(); }) .create(); dialog.setOnShowListener(d -> { @@ -194,9 +217,43 @@ public class DialogUtil { dialog.dismiss(); callback.accept(binding.createGroupName.getText().toString()); + if(onDismiss != null) onDismiss.run(); }); }); + dialog.setOnCancelListener(d -> { if(onDismiss != null) onDismiss.run(); }); + dialog.show(); + } + + public static void showImportCodeDialog(Context context, Consumer callback, Runnable onDismiss) { + List groups = SettingsUtil.getGroups(context); + String[] groupNames = new String[groups.size() + 1]; + + groupNames[0] = context.getString(R.string.uri_handler_create_group); + for(int i = 0; i < groups.size(); i++) { + groupNames[i + 1] = SettingsUtil.getGroupName(context, groups.get(i)); + } + + AlertDialog dialog = new StyledDialogBuilder(context) + .setTitle(R.string.uri_handler_add_code_title) + .setItems(groupNames, (d, which) -> { + if(which == 0) { // Create New Group + DialogUtil.showCreateGroupDialog(LayoutInflater.from(context), null, group -> { + String id = UUID.randomUUID().toString(); + SettingsUtil.addGroup(context, id, group); + callback.accept(id); + }, onDismiss); + return; + } + + callback.accept(groups.get(which - 1)); + Toast.makeText(context, R.string.uri_handler_code_added, Toast.LENGTH_SHORT).show(); + if(onDismiss != null) onDismiss.run(); + }) + .setNegativeButton(R.string.cancel, (d, which) -> { if(onDismiss != null) onDismiss.run(); }) + .setOnCancelListener(d -> { if(onDismiss != null) onDismiss.run(); }) + .create(); + dialog.show(); } diff --git a/app/src/main/java/com/cringe_studios/cringe_authenticator/util/OTPParser.java b/app/src/main/java/com/cringe_studios/cringe_authenticator/util/OTPParser.java index 8916394..355f759 100644 --- a/app/src/main/java/com/cringe_studios/cringe_authenticator/util/OTPParser.java +++ b/app/src/main/java/com/cringe_studios/cringe_authenticator/util/OTPParser.java @@ -1,20 +1,119 @@ package com.cringe_studios.cringe_authenticator.util; import android.net.Uri; +import android.util.Base64; import com.cringe_studios.cringe_authenticator.model.OTPData; +import com.cringe_studios.cringe_authenticator.proto.OTPMigration; import com.cringe_studios.cringe_authenticator_library.OTPAlgorithm; import com.cringe_studios.cringe_authenticator_library.OTPType; +import com.cringe_studios.cringe_authenticator_library.impl.Base32; +import com.google.protobuf.InvalidProtocolBufferException; public class OTPParser { + public static OTPData[] parseMigration(Uri uri) throws IllegalArgumentException { + if(!"otpauth-migration".equals(uri.getScheme())) { + throw new IllegalArgumentException("Wrong URI scheme"); + } + + if(!uri.isHierarchical()) { + throw new IllegalArgumentException("Not a hierarchical URI"); + } + + String data = uri.getQueryParameter("data"); + if(data == null) { + throw new IllegalArgumentException("Missing data"); + } + + byte[] dataBytes = Base64.decode(data, Base64.DEFAULT); + try { + OTPMigration.MigrationPayload payload = OTPMigration.MigrationPayload.parseFrom(dataBytes); + int count = payload.getOtpParametersCount(); + OTPData[] otps = new OTPData[count]; + for(int i = 0; i < payload.getOtpParametersCount(); i++) { + OTPMigration.MigrationPayload.OtpParameters params = payload.getOtpParameters(i); + + // TODO: issuer + + String name = params.getName(); + String issuer = params.getIssuer(); + + OTPType type; + switch(params.getType()) { + case OTP_TYPE_UNSPECIFIED: + case UNRECOGNIZED: + default: + // TODO: be more lenient and only exclude the broken codes + throw new IllegalArgumentException("Unknown OTP type in migration"); + case OTP_TYPE_HOTP: + type = OTPType.HOTP; + break; + case OTP_TYPE_TOTP: + type = OTPType.TOTP; + break; + } + + String secret = Base32.encode(params.getSecret().toByteArray()); + + OTPAlgorithm algorithm; + switch(params.getAlgorithm()) { + case ALGORITHM_UNSPECIFIED: + case UNRECOGNIZED: + default: + throw new IllegalArgumentException("Unknown or unsupported algorithm in migration"); + case ALGORITHM_SHA1: + algorithm = OTPAlgorithm.SHA1; + break; + case ALGORITHM_SHA256: + algorithm = OTPAlgorithm.SHA256; + break; + case ALGORITHM_SHA512: + algorithm = OTPAlgorithm.SHA512; + break; + case ALGORITHM_MD5: + algorithm = OTPAlgorithm.MD5; + break; + } + + int digits; + switch(params.getDigits()) { + case DIGIT_COUNT_UNSPECIFIED: + case UNRECOGNIZED: + default: + throw new IllegalArgumentException("Unknown or unsupported digit count in migration"); + case DIGIT_COUNT_SIX: + digits = 6; + break; + case DIGIT_COUNT_EIGHT: + digits = 8; + break; + } + + int period = 30; // Google authenticator doesn't support other periods + long counter = params.getCounter(); + boolean checksum = false; // Google authenticator doesn't support checksums + + otps[i] = new OTPData(name, issuer, type, secret, algorithm, digits, period, counter, checksum); + } + return otps; + } catch (InvalidProtocolBufferException e) { + throw new IllegalArgumentException("Failed to parse migration data", e); + } + } + public static OTPData parse(Uri uri) throws IllegalArgumentException { if(!"otpauth".equals(uri.getScheme())) { throw new IllegalArgumentException("Wrong URI scheme"); } + if(!uri.isHierarchical()) { + throw new IllegalArgumentException("Not a hierarchical URI"); + } + String type = uri.getHost(); String accountName = uri.getPath(); + String issuer = uri.getQueryParameter("issuer"); String secret = uri.getQueryParameter("secret"); String algorithm = uri.getQueryParameter("algorithm"); String digits = uri.getQueryParameter("digits"); @@ -58,7 +157,7 @@ public class OTPParser { } } - OTPData data = new OTPData(accountName, fType, secret, fAlgorithm, fDigits, fPeriod, fCounter, fChecksum); + OTPData data = new OTPData(accountName, issuer, fType, secret, fAlgorithm, fDigits, fPeriod, fCounter, fChecksum); String errorMessage = data.validate(); if(errorMessage != null) { diff --git a/app/src/main/java/com/cringe_studios/cringe_authenticator/util/ThemeUtil.java b/app/src/main/java/com/cringe_studios/cringe_authenticator/util/ThemeUtil.java new file mode 100644 index 0000000..dbc90fc --- /dev/null +++ b/app/src/main/java/com/cringe_studios/cringe_authenticator/util/ThemeUtil.java @@ -0,0 +1,18 @@ +package com.cringe_studios.cringe_authenticator.util; + +import androidx.appcompat.app.AppCompatActivity; + +import com.cringe_studios.cringe_authenticator.R; + +public class ThemeUtil { + + public static void loadTheme(AppCompatActivity activity) { + Integer themeID = SettingsUtil.THEMES.get(SettingsUtil.getTheme(activity)); + if(themeID != null) { + activity.setTheme(themeID); + }else { + activity.setTheme(R.style.Theme_CringeAuthenticator_Blue_Green); + } + } + +} diff --git a/app/src/main/res/layout/dialog_input_code_hotp.xml b/app/src/main/res/layout/dialog_input_code_hotp.xml index 7ebf300..060cc21 100644 --- a/app/src/main/res/layout/dialog_input_code_hotp.xml +++ b/app/src/main/res/layout/dialog_input_code_hotp.xml @@ -14,6 +14,15 @@ android:hint="@string/otp_add_name" android:autofillHints="" /> + + + + Wähle den Code-Typ Neue Gruppe Du musst einen Namen eingeben - Scannen des Codes fehlgeschlagen: %s + Scannen fehlgeschlagen: %s Abspielen des Videos fehlgeschlagen OTP bearbeiten Löschen? @@ -38,6 +38,7 @@ Biometrische Authentifizierung aktivieren Code hinzugefügt Code hinzufügen + Neue Gruppe erstellen Theme Zähler Prüfsumme hinzufügen @@ -45,6 +46,10 @@ Name Intervall Hinzufügen des OTP-Codes fehlgeschlagen: %s + Aussteller (optional) + Name fehlt + OTP-Migration + Du scheinst zu versuchen, Codes aus einer anderen App zu importieren. Willst du alle Codes in diese Gruppe importieren? Anzeigen Bearbeiten diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6de6646..faa29d7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -60,7 +60,7 @@ Select Code Type New Group You need to input a name - Failed to scan code: %s + Scan failed: %s Failed to play video Edit OTP Delete? @@ -78,6 +78,7 @@ Require biometric unlock Code added Add Code + Create New Group Theme Counter Add Checksum @@ -85,6 +86,10 @@ Name Period Failed to update OTP: %s + Issuer (optional) + Missing name + OTP Migration + It seems like you\'re trying to import OTP codes from another app. Do you want to import all codes into this group? View Edit diff --git a/build.gradle b/build.gradle index a100ce0..dea9549 100644 --- a/build.gradle +++ b/build.gradle @@ -2,4 +2,5 @@ plugins { id 'com.android.application' version '8.0.2' apply false id 'com.android.library' version '8.0.2' apply false + id 'com.google.protobuf' version '0.9.3' apply false } \ No newline at end of file