diff --git a/app/build.gradle b/app/build.gradle index b9a64be..440428d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -43,7 +43,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.2' + implementation 'com.cringe_studios:CringeAuthenticatorLibrary:1.4' implementation 'com.google.mlkit:barcode-scanning:17.1.0' implementation 'com.google.code.gson:gson:2.8.9' diff --git a/app/src/main/java/com/cringe_studios/cringe_authenticator/IntroActivity.java b/app/src/main/java/com/cringe_studios/cringe_authenticator/IntroActivity.java index 1019cab..d719313 100644 --- a/app/src/main/java/com/cringe_studios/cringe_authenticator/IntroActivity.java +++ b/app/src/main/java/com/cringe_studios/cringe_authenticator/IntroActivity.java @@ -41,7 +41,7 @@ public class IntroActivity extends AppCompatActivity { binding.videoView.setOnCompletionListener(mp -> openMainActivity()); binding.videoView.setOnErrorListener((MediaPlayer mp, int what, int extra) -> { - Toast.makeText(this, "Failed to play video", Toast.LENGTH_LONG).show(); + Toast.makeText(this, R.string.intro_video_failed, Toast.LENGTH_LONG).show(); openMainActivity(); return true; }); 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 e3737bd..180f68e 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 @@ -3,14 +3,13 @@ package com.cringe_studios.cringe_authenticator; import static androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG; import static androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL; +import android.content.res.Configuration; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; import android.view.View; -import android.view.WindowManager; import android.widget.AdapterView; import android.widget.ArrayAdapter; -import android.widget.Button; import android.widget.EditText; import android.widget.Toast; @@ -26,20 +25,18 @@ import androidx.fragment.app.Fragment; import com.cringe_studios.cringe_authenticator.databinding.ActivityMainBinding; import com.cringe_studios.cringe_authenticator.databinding.DialogInputCodeChoiceBinding; -import com.cringe_studios.cringe_authenticator.databinding.DialogInputCodeHotpBinding; -import com.cringe_studios.cringe_authenticator.databinding.DialogInputCodeTotpBinding; import com.cringe_studios.cringe_authenticator.fragment.GroupFragment; 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.scanner.QRScannerContract; -import com.cringe_studios.cringe_authenticator.util.DialogCallback; +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_library.OTPAlgorithm; import com.cringe_studios.cringe_authenticator_library.OTPType; +import java.util.Locale; import java.util.concurrent.Executor; public class MainActivity extends AppCompatActivity { @@ -58,7 +55,9 @@ public class MainActivity extends AppCompatActivity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE); + setLocale("de"); + + //getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE); Integer themeID = SettingsUtil.THEMES.get(SettingsUtil.getTheme(this)); if(themeID != null) { @@ -85,8 +84,8 @@ public class MainActivity extends AppCompatActivity { if(!recentlyUnlocked && SettingsUtil.isBiometricLock(this) && supportsBiometricAuth) { BiometricPrompt.PromptInfo info = new BiometricPrompt.PromptInfo.Builder() - .setTitle("Cringe Authenticator") - .setSubtitle("Unlock the authenticator") + .setTitle(getString(R.string.app_name)) + .setSubtitle(getString(R.string.biometric_lock_subtitle)) .setAllowedAuthenticators(BIOMETRIC_STRONG | DEVICE_CREDENTIAL) .build(); @@ -99,7 +98,7 @@ public class MainActivity extends AppCompatActivity { if(obj == null) return; // Cancelled if(!obj.isSuccess()) { - Toast.makeText(this, "Failed to scan code: " + obj.getErrorMessage(), Toast.LENGTH_LONG).show(); + Toast.makeText(this, getString(R.string.qr_scanner_failed, obj.getErrorMessage()), Toast.LENGTH_LONG).show(); return; } @@ -111,6 +110,14 @@ public class MainActivity extends AppCompatActivity { }); } + private void setLocale(String lang) { + Locale locale = new Locale(lang); + Locale.setDefault(locale); + Configuration config = new Configuration(); + config.locale = locale; + getResources().updateConfiguration(config, getResources().getDisplayMetrics()); + } + private void launchApp() { unlocked = true; @@ -188,9 +195,9 @@ public class MainActivity extends AppCompatActivity { options[1] = OTPType.HOTP.getFriendlyName() + " (HOTP)"; AlertDialog dialog = new AlertDialog.Builder(this) - .setTitle("Select Code Type") + .setTitle(R.string.create_totp_title) .setView(binding.getRoot()) - .setNegativeButton("Cancel", (view, which) -> {}) + .setNegativeButton(R.string.cancel, (view, which) -> {}) .create(); binding.codeTypes.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, options)); @@ -211,105 +218,31 @@ public class MainActivity extends AppCompatActivity { } private void showTOTPDialog() { - DialogInputCodeTotpBinding binding = DialogInputCodeTotpBinding.inflate(getLayoutInflater()); - binding.inputAlgorithm.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, OTPAlgorithm.values())); - binding.inputDigits.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, new Integer[]{6, 7, 8, 9, 10, 11, 12})); - showCodeDialog(binding.getRoot(), () -> { + DialogUtil.showTOTPDialog(getLayoutInflater(), null, data -> { Fragment fragment = NavigationUtil.getCurrentFragment(this); - if(!(fragment instanceof GroupFragment)) return true; + if(!(fragment instanceof GroupFragment)) return; - try { - String name = binding.inputName.getText().toString(); - 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); - - String errorMessage = data.validate(); - if(errorMessage != null) { - showErrorDialog(errorMessage); - return false; - } - - ((GroupFragment) fragment).addOTP(data); - return true; - }catch(NumberFormatException e) { - showErrorDialog("Invalid number entered"); - return false; - } - }); + ((GroupFragment) fragment).addOTP(data); + }, () -> inputCode(), false); } private void showHOTPDialog() { - DialogInputCodeHotpBinding binding = DialogInputCodeHotpBinding.inflate(getLayoutInflater()); - binding.inputAlgorithm.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, OTPAlgorithm.values())); - binding.inputDigits.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, new Integer[]{6, 7, 8, 9, 10, 11, 12})); - showCodeDialog(binding.getRoot(), () -> { + DialogUtil.showHOTPDialog(getLayoutInflater(), null, data -> { Fragment fragment = NavigationUtil.getCurrentFragment(this); - if(!(fragment instanceof GroupFragment)) return true; + if(!(fragment instanceof GroupFragment)) return; - try { - String name = binding.inputName.getText().toString(); - 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); - - String errorMessage = data.validate(); - if(errorMessage != null) { - showErrorDialog(errorMessage); - return false; - } - - ((GroupFragment) fragment).addOTP(data); - return true; - }catch(NumberFormatException e) { - showErrorDialog("Invalid number entered"); - return false; - } - }); - } - - private void showCodeDialog(View view, DialogCallback ok) { - AlertDialog dialog = new AlertDialog.Builder(this) - .setTitle("Input Code") - .setView(view) - .setPositiveButton("Ok", (btnView, which) -> {}) - .setNegativeButton("Cancel", (btnView, which) -> {}) - .create(); - - dialog.setOnShowListener(d -> { - Button okButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE); - okButton.setOnClickListener(v -> { - if(ok.callback()) dialog.dismiss(); - }); - }); - - dialog.show(); - } - - private void showErrorDialog(String errorMessage) { - new AlertDialog.Builder(this) - .setTitle("Failed to add code") - .setMessage(errorMessage) - .setPositiveButton("Ok", (dialog, which) -> {}) - .show(); + ((GroupFragment) fragment).addOTP(data); + }, () -> inputCode(), false); } public void addGroup(MenuItem item) { EditText t = new EditText(this); new AlertDialog.Builder(this) - .setTitle("New Group") + .setTitle(R.string.action_new_group) .setView(t) - .setPositiveButton("Add", (view, which) -> { + .setPositiveButton(R.string.add, (view, which) -> { if(t.getText().length() == 0) { - showErrorDialog("You need to input a name"); + DialogUtil.showErrorDialog(this, getString(R.string.new_group_missing_title)); return; } @@ -318,7 +251,7 @@ public class MainActivity extends AppCompatActivity { ((MenuFragment) frag).addGroup(t.getText().toString()); } }) - .setNegativeButton("Cancel", (view, which) -> {}) + .setNegativeButton(R.string.cancel, (view, which) -> {}) .show(); } diff --git a/app/src/main/java/com/cringe_studios/cringe_authenticator/OTPData.java b/app/src/main/java/com/cringe_studios/cringe_authenticator/OTPData.java index e969b40..d321911 100644 --- a/app/src/main/java/com/cringe_studios/cringe_authenticator/OTPData.java +++ b/app/src/main/java/com/cringe_studios/cringe_authenticator/OTPData.java @@ -63,7 +63,11 @@ public class OTPData implements Serializable { return counter; } - public String getPin() { + public boolean hasChecksum() { + return checksum; + } + + public String getPin() throws OTPException { return getOTP().getPin(); } @@ -80,15 +84,18 @@ public class OTPData implements Serializable { try { getOTP(); return null; - }catch(IllegalArgumentException | OTPException e) { + }catch(RuntimeException e) { return e.getMessage() != null ? e.getMessage() : e.toString(); } } private OTP getOTP() { - // TODO: checksum if(otp != null) return otp; - return otp = OTP.createNewOTP(type, secret, algorithm, digits, counter, period, checksum); + try { + return otp = OTP.createNewOTP(type, secret, algorithm, digits, counter, period, checksum); + } catch (OTPException e) { + throw new RuntimeException(e.getMessage(), e); + } } @NonNull diff --git a/app/src/main/java/com/cringe_studios/cringe_authenticator/fragment/GroupFragment.java b/app/src/main/java/com/cringe_studios/cringe_authenticator/fragment/GroupFragment.java index 8a7944f..a83868c 100644 --- a/app/src/main/java/com/cringe_studios/cringe_authenticator/fragment/GroupFragment.java +++ b/app/src/main/java/com/cringe_studios/cringe_authenticator/fragment/GroupFragment.java @@ -9,13 +9,17 @@ import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; import com.cringe_studios.cringe_authenticator.OTPData; +import com.cringe_studios.cringe_authenticator.R; import com.cringe_studios.cringe_authenticator.databinding.FragmentGroupBinding; import com.cringe_studios.cringe_authenticator.otplist.OTPListAdapter; import com.cringe_studios.cringe_authenticator.otplist.OTPListItem; +import com.cringe_studios.cringe_authenticator.util.DialogUtil; import com.cringe_studios.cringe_authenticator.util.FabUtil; import com.cringe_studios.cringe_authenticator.util.SettingsUtil; +import com.cringe_studios.cringe_authenticator_library.OTPException; import com.cringe_studios.cringe_authenticator_library.OTPType; import java.util.List; @@ -53,7 +57,8 @@ public class GroupFragment extends NamedFragment { FabUtil.showFabs(requireActivity()); - otpListAdapter = new OTPListAdapter(getContext()); + otpListAdapter = new OTPListAdapter(requireContext(), data -> showOTPDialog(data)); + binding.itemList.setAdapter(otpListAdapter); loadOTPs(); @@ -69,6 +74,50 @@ public class GroupFragment extends NamedFragment { return binding.getRoot(); } + private void showOTPDialog(OTPData data) { + new AlertDialog.Builder(requireContext()) + .setTitle(R.string.edit_otp_title) + .setItems(R.array.view_edit_delete, (dialog, which) -> { + switch(which) { + case 0: + DialogUtil.showViewCodeDialog(getLayoutInflater(), data, newData -> { + otpListAdapter.replace(data, newData); + saveOTPs(); + }, () -> showOTPDialog(data)); + break; + case 1: + DialogUtil.showEditCodeDialog(getLayoutInflater(), data, newData -> { + otpListAdapter.replace(data, newData); + saveOTPs(); + }, () -> showOTPDialog(data)); + break; + case 2: break; + } + }) + .setNegativeButton(R.string.cancel, (dialog, which) -> {}) + .show(); + + /*switch(data.getType()) { + case HOTP: + DialogUtil.showHOTPDialog(getLayoutInflater(), data, newData -> { + otpListAdapter.replace(data, newData); + saveOTPs(); + }); + break; + case TOTP: + DialogUtil.showTOTPDialog(getLayoutInflater(), data, newData -> { + otpListAdapter.replace(data, newData); + saveOTPs(); + }); + break; + }*/ + } + + private void saveOTPs() { + SettingsUtil.updateOTPs(requireContext(), groupName, otpListAdapter.getItems()); + refreshCodes(); + } + private void loadOTPs() { List data = SettingsUtil.getOTPs(requireContext(), groupName); @@ -86,7 +135,11 @@ public class GroupFragment extends NamedFragment { for(int i = 0; i < binding.itemList.getChildCount(); i++) { OTPListItem vh = (OTPListItem) binding.itemList.findViewHolderForAdapterPosition(i); if(vh == null) continue; - vh.getBinding().otpCode.setText(vh.getOTPData().getPin()); + try { + vh.getBinding().otpCode.setText(vh.getOTPData().getPin()); + } catch (OTPException e) { + DialogUtil.showErrorDialog(requireContext(), e.getMessage() == null ? "An error occurred while refreshing the code" : e.getMessage()); + } if(vh.getOTPData().getType() == OTPType.TOTP) { long timeDiff = vh.getOTPData().getNextDueTime() - System.currentTimeMillis() / 1000; 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 b022116..d09903c 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 @@ -1,7 +1,6 @@ package com.cringe_studios.cringe_authenticator.fragment; import android.os.Bundle; -import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -54,7 +53,6 @@ public class MenuFragment extends NamedFragment { private void loadGroups() { List items = SettingsUtil.getGroups(requireContext()); - Log.i("AMOGUS", "items: " + items); for(String item : items) { groupListAdapter.add(item); diff --git a/app/src/main/java/com/cringe_studios/cringe_authenticator/grouplist/GroupListAdapter.java b/app/src/main/java/com/cringe_studios/cringe_authenticator/grouplist/GroupListAdapter.java index 18d178b..cdceb3d 100644 --- a/app/src/main/java/com/cringe_studios/cringe_authenticator/grouplist/GroupListAdapter.java +++ b/app/src/main/java/com/cringe_studios/cringe_authenticator/grouplist/GroupListAdapter.java @@ -11,6 +11,7 @@ import androidx.appcompat.app.AlertDialog; import androidx.core.util.Consumer; import androidx.recyclerview.widget.RecyclerView; +import com.cringe_studios.cringe_authenticator.R; import com.cringe_studios.cringe_authenticator.databinding.MenuItemBinding; import java.util.ArrayList; @@ -55,10 +56,10 @@ public class GroupListAdapter extends RecyclerView.Adapter { holder.getBinding().button.setOnClickListener(view -> navigateToGroup.accept(group)); holder.getBinding().button.setOnLongClickListener(view -> { new AlertDialog.Builder(context) - .setTitle("Delete?") - .setMessage("Delete this?") - .setPositiveButton("Yes", (dialog, which) -> removeGroup.accept(group)) - .setNegativeButton("No", (dialog, which) -> {}) + .setTitle(R.string.group_delete_title) + .setMessage(R.string.group_delete_message) + .setPositiveButton(R.string.yes, (dialog, which) -> removeGroup.accept(group)) + .setNegativeButton(R.string.no, (dialog, which) -> {}) .show(); // TODO: better method? return true; diff --git a/app/src/main/java/com/cringe_studios/cringe_authenticator/otplist/OTPListAdapter.java b/app/src/main/java/com/cringe_studios/cringe_authenticator/otplist/OTPListAdapter.java index e42adff..08339ec 100644 --- a/app/src/main/java/com/cringe_studios/cringe_authenticator/otplist/OTPListAdapter.java +++ b/app/src/main/java/com/cringe_studios/cringe_authenticator/otplist/OTPListAdapter.java @@ -9,10 +9,13 @@ import android.view.ViewGroup; import android.widget.Toast; import androidx.annotation.NonNull; +import androidx.core.util.Consumer; import androidx.recyclerview.widget.RecyclerView; import com.cringe_studios.cringe_authenticator.OTPData; +import com.cringe_studios.cringe_authenticator.R; import com.cringe_studios.cringe_authenticator.databinding.OtpCodeBinding; +import com.cringe_studios.cringe_authenticator_library.OTPException; import com.cringe_studios.cringe_authenticator_library.OTPType; import java.util.ArrayList; @@ -26,10 +29,13 @@ public class OTPListAdapter extends RecyclerView.Adapter { private Handler handler; - public OTPListAdapter(Context context) { + private Consumer showMenuCallback; + + public OTPListAdapter(Context context, Consumer showMenuCallback) { this.inflater = LayoutInflater.from(context); this.items = new ArrayList<>(); this.handler = new Handler(Looper.getMainLooper()); + this.showMenuCallback = showMenuCallback; } @NonNull @@ -52,11 +58,23 @@ public class OTPListAdapter extends RecyclerView.Adapter { // Click delay for HOTP view.setClickable(false); - Toast.makeText(view.getContext(), "Generated new code", Toast.LENGTH_LONG).show(); + Toast.makeText(view.getContext(), R.string.hotp_generated_new_code, Toast.LENGTH_SHORT).show(); data.incrementCounter(); - holder.getBinding().otpCode.setText(data.getPin()); + + try { + holder.getBinding().otpCode.setText(data.getPin()); + }catch(OTPException e) { + // TODO: show user an error message + return; + } + handler.postDelayed(() -> view.setClickable(true), 5000); }); + + holder.getBinding().getRoot().setOnLongClickListener(view -> { + showMenuCallback.accept(holder.getOTPData()); + return true; + }); } @Override @@ -64,11 +82,22 @@ public class OTPListAdapter extends RecyclerView.Adapter { return items.size(); } + public List getItems() { + return items; + } + public void add(OTPData data) { items.add(data); notifyItemInserted(items.size() - 1); } + public void replace(OTPData oldData, OTPData newData) { + int index = items.indexOf(oldData); + if(index == -1) return; + items.set(index, newData); + notifyItemChanged(index); + } + public void remove(OTPData data) { int index = items.indexOf(data); if(index == -1) return; diff --git a/app/src/main/java/com/cringe_studios/cringe_authenticator/urihandler/URIHandlerActivity.java b/app/src/main/java/com/cringe_studios/cringe_authenticator/urihandler/URIHandlerActivity.java index 5422398..03eaf55 100644 --- a/app/src/main/java/com/cringe_studios/cringe_authenticator/urihandler/URIHandlerActivity.java +++ b/app/src/main/java/com/cringe_studios/cringe_authenticator/urihandler/URIHandlerActivity.java @@ -9,6 +9,7 @@ import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import com.cringe_studios.cringe_authenticator.OTPData; +import com.cringe_studios.cringe_authenticator.R; import com.cringe_studios.cringe_authenticator.util.OTPParser; public class URIHandlerActivity extends AppCompatActivity { @@ -30,9 +31,9 @@ public class URIHandlerActivity extends AppCompatActivity { finish(); }catch(IllegalArgumentException e) { AlertDialog dialog = new AlertDialog.Builder(this) - .setTitle("Failed to add code") + .setTitle(R.string.uri_handler_failed_title) .setMessage(e.getMessage()) - .setPositiveButton("Ok", (d, which) -> finish()) + .setPositiveButton(R.string.ok, (d, which) -> finish()) .create(); dialog.setOnDismissListener(d -> 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 new file mode 100644 index 0000000..eb4c3c3 --- /dev/null +++ b/app/src/main/java/com/cringe_studios/cringe_authenticator/util/DialogUtil.java @@ -0,0 +1,175 @@ +package com.cringe_studios.cringe_authenticator.util; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ArrayAdapter; +import android.widget.Button; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; +import androidx.core.util.Consumer; + +import com.cringe_studios.cringe_authenticator.OTPData; +import com.cringe_studios.cringe_authenticator.R; +import com.cringe_studios.cringe_authenticator.databinding.DialogInputCodeHotpBinding; +import com.cringe_studios.cringe_authenticator.databinding.DialogInputCodeTotpBinding; +import com.cringe_studios.cringe_authenticator_library.OTPAlgorithm; +import com.cringe_studios.cringe_authenticator_library.OTPType; + +import java.util.Arrays; + +public class DialogUtil { + + private static final Integer[] DIGITS = new Integer[]{6, 7, 8, 9, 10, 11, 12}; + + private static void showCodeDialog(Context context, View view, DialogCallback ok, Runnable back) { + AlertDialog dialog = new AlertDialog.Builder(context) + .setTitle(R.string.code_input_title) + .setView(view) + .setPositiveButton(R.string.ok, (btnView, which) -> {}) + .setNeutralButton(R.string.back, (btnView, which) -> back.run()) + .setNegativeButton(R.string.cancel, (btnView, which) -> {}) + .create(); + + //dialog.getWindow().setBackgroundDrawableResource(R.drawable.button_themed); TODO: dialog style + + dialog.setOnShowListener(d -> { + Button okButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE); + okButton.setOnClickListener(v -> { + if(ok.callback()) dialog.dismiss(); + }); + }); + + dialog.show(); + } + + public static void showErrorDialog(Context context, String errorMessage) { + new AlertDialog.Builder(context) + .setTitle(R.string.failed_title) + .setMessage(errorMessage) + .setPositiveButton(R.string.ok, (dialog, which) -> {}) + .show(); + } + + public static void showTOTPDialog(LayoutInflater inflater, OTPData initialData, Consumer callback, Runnable back, boolean view) { + Context context = inflater.getContext(); + DialogInputCodeTotpBinding binding = DialogInputCodeTotpBinding.inflate(inflater); + + binding.inputAlgorithm.setAdapter(new ArrayAdapter<>(context, android.R.layout.simple_list_item_1, OTPAlgorithm.values())); + binding.inputAlgorithm.setEnabled(!view); + + binding.inputDigits.setAdapter(new ArrayAdapter<>(context, android.R.layout.simple_list_item_1, DIGITS)); + binding.inputDigits.setEnabled(!view); + + binding.inputName.setEnabled(!view); + binding.inputSecret.setEnabled(!view); + binding.inputPeriod.setEnabled(!view); + binding.inputChecksum.setEnabled(!view); + + if(initialData != null) { + binding.inputName.setText(initialData.getName()); + binding.inputSecret.setText(initialData.getSecret()); + binding.inputAlgorithm.setSelection(initialData.getAlgorithm().ordinal()); + + int index = Arrays.asList(DIGITS).indexOf(initialData.getDigits()); + if(index != -1) binding.inputDigits.setSelection(index); + + binding.inputPeriod.setText(String.valueOf(initialData.getPeriod())); + binding.inputChecksum.setChecked(initialData.hasChecksum()); + } + + showCodeDialog(context, binding.getRoot(), () -> { + try { + String name = binding.inputName.getText().toString(); + 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); + + String errorMessage = data.validate(); + if(errorMessage != null) { + showErrorDialog(context, errorMessage); + return false; + } + + callback.accept(data); + return true; + }catch(NumberFormatException e) { + showErrorDialog(context, context.getString(R.string.input_code_invalid_number)); + return false; + } + }, back); + } + + public static void showHOTPDialog(LayoutInflater inflater, OTPData initialData, Consumer callback, Runnable back, boolean view) { + Context context = inflater.getContext(); + DialogInputCodeHotpBinding binding = DialogInputCodeHotpBinding.inflate(inflater); + + binding.inputAlgorithm.setAdapter(new ArrayAdapter<>(context, android.R.layout.simple_list_item_1, OTPAlgorithm.values())); + binding.inputAlgorithm.setEnabled(!view); + + binding.inputDigits.setAdapter(new ArrayAdapter<>(context, android.R.layout.simple_list_item_1, DIGITS)); + binding.inputDigits.setEnabled(!view); + + binding.inputName.setEnabled(!view); + binding.inputSecret.setEnabled(!view); + binding.inputCounter.setEnabled(!view); + binding.inputChecksum.setEnabled(!view); + + if(initialData != null) { + binding.inputName.setText(initialData.getName()); + binding.inputSecret.setText(initialData.getSecret()); + binding.inputAlgorithm.setSelection(initialData.getAlgorithm().ordinal()); + + int index = Arrays.asList(DIGITS).indexOf(initialData.getDigits()); + if(index != -1) binding.inputDigits.setSelection(index); + + binding.inputCounter.setText(String.valueOf(initialData.getCounter())); + binding.inputChecksum.setChecked(initialData.hasChecksum()); + } + + showCodeDialog(context, binding.getRoot(), () -> { + try { + String name = binding.inputName.getText().toString(); + 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); + + String errorMessage = data.validate(); + if(errorMessage != null) { + showErrorDialog(context, errorMessage); + return false; + } + + callback.accept(data); + return true; + }catch(NumberFormatException e) { + showErrorDialog(context, context.getString(R.string.input_code_invalid_number)); + return false; + } + }, back); + } + + public static void showViewCodeDialog(LayoutInflater inflater, @NonNull OTPData initialData, Consumer callback, Runnable back) { + switch(initialData.getType()) { + case HOTP: showHOTPDialog(inflater, initialData, callback, back, true); break; + case TOTP: showTOTPDialog(inflater, initialData, callback, back, true); break; + } + } + + public static void showEditCodeDialog(LayoutInflater inflater, @NonNull OTPData initialData, Consumer callback, Runnable back) { + switch(initialData.getType()) { + case HOTP: showHOTPDialog(inflater, initialData, callback, back, false); break; + case TOTP: showTOTPDialog(inflater, initialData, callback, back, false); break; + } + } + +} diff --git a/app/src/main/java/com/cringe_studios/cringe_authenticator/util/SettingsUtil.java b/app/src/main/java/com/cringe_studios/cringe_authenticator/util/SettingsUtil.java index 9a6cd51..f4648f1 100644 --- a/app/src/main/java/com/cringe_studios/cringe_authenticator/util/SettingsUtil.java +++ b/app/src/main/java/com/cringe_studios/cringe_authenticator/util/SettingsUtil.java @@ -76,6 +76,12 @@ public class SettingsUtil { .apply(); } + public static void updateOTPs(Context ctx, String group, List otps) { + ctx.getSharedPreferences(GROUPS_PREFS_NAME, Context.MODE_PRIVATE).edit() + .putString("group." + group, GSON.toJson(otps.toArray(new OTPData[0]))) + .apply(); + } + private static void deleteOTPs(Context ctx, String group) { ctx.getSharedPreferences(GROUPS_PREFS_NAME, Context.MODE_PRIVATE).edit() .remove("group." + group) diff --git a/app/src/main/res/menu/menu_groups.xml b/app/src/main/res/menu/menu_groups.xml index 5149e2a..2c11002 100644 --- a/app/src/main/res/menu/menu_groups.xml +++ b/app/src/main/res/menu/menu_groups.xml @@ -3,15 +3,15 @@ xmlns:tools="http://schemas.android.com/tools" tools:context="com.cringe_studios.cringe_authenticator.MainActivity"> \ No newline at end of file diff --git a/app/src/main/res/values-de-rDE/strings.xml b/app/src/main/res/values-de-rDE/strings.xml index 86edd60..1c5242e 100644 --- a/app/src/main/res/values-de-rDE/strings.xml +++ b/app/src/main/res/values-de-rDE/strings.xml @@ -4,16 +4,40 @@ Einstellungen Weiter Zurück - Editieren + Bearbeiten Sprachauswahl Abbrechen - hinzufügen - nein - ja - ungültige Eingabe - über - vibration + Hinzufügen + Nein + Ja + Ungültige Eingabe + Über + Vibration App zurücksetzen - importieren / exportieren + Importieren / Exportieren Sprache + Entsperre den Authentifikator + Wähle den Code-Typ + Neue Gruppe + Du musst einen Namen eingeben + Scannen des Codes fehlgeschlagen: %s + Abspielen des Videos fehlgeschlagen + OTP bearbeiten + Löschen? + Gruppe löschen? + Neuen Code generiert + Hinzufügen des Codes fehlgeschlagen + Code eingeben + Aktion fehlgeschlagen + Ungültige Zahl + Zurück + + Anzeigen + Bearbeiten + Löschen + + + Umbenennen + Löschen + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2b78828..60178d9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -44,16 +44,40 @@ vestibulum. Fusce dictum libero quis erat maximus, vitae volutpat diam dignissim. Edit - choose Language - cancel - add - Ok - no - yes - invalid input - about - haptic feedback - reset app - import / export - language + Choose Language + Cancel + Add + OK + No + Yes + Invalid Input + About + Haptic Feedback + Reset App + Import / Export + Language + Unlock the authenticator + Select Code Type + New Group + You need to input a name + Failed to scan code: %s + Failed to play video + Edit OTP + Delete? + Delete this? + Generated new code + Failed to add code + Input Code + Action failed + Invalid number entered + Back + + View + Edit + Delete + + + Rename + Delete + \ No newline at end of file