Add OTP editing/viewing (WIP), some more translation
This commit is contained in:
parent
8a06d5eb4d
commit
3c90ab9d15
@ -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'
|
||||
|
||||
|
@ -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;
|
||||
});
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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<OTPData> 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;
|
||||
|
@ -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<String> items = SettingsUtil.getGroups(requireContext());
|
||||
Log.i("AMOGUS", "items: " + items);
|
||||
|
||||
for(String item : items) {
|
||||
groupListAdapter.add(item);
|
||||
|
@ -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<GroupListItem> {
|
||||
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;
|
||||
|
@ -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<OTPListItem> {
|
||||
|
||||
private Handler handler;
|
||||
|
||||
public OTPListAdapter(Context context) {
|
||||
private Consumer<OTPData> showMenuCallback;
|
||||
|
||||
public OTPListAdapter(Context context, Consumer<OTPData> 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<OTPListItem> {
|
||||
|
||||
// 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<OTPListItem> {
|
||||
return items.size();
|
||||
}
|
||||
|
||||
public List<OTPData> 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;
|
||||
|
@ -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());
|
||||
|
@ -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<OTPData> 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<OTPData> 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<OTPData> 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<OTPData> callback, Runnable back) {
|
||||
switch(initialData.getType()) {
|
||||
case HOTP: showHOTPDialog(inflater, initialData, callback, back, false); break;
|
||||
case TOTP: showTOTPDialog(inflater, initialData, callback, back, false); break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -76,6 +76,12 @@ public class SettingsUtil {
|
||||
.apply();
|
||||
}
|
||||
|
||||
public static void updateOTPs(Context ctx, String group, List<OTPData> 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)
|
||||
|
@ -3,15 +3,15 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:context="com.cringe_studios.cringe_authenticator.MainActivity">
|
||||
<item
|
||||
android:id="@+id/action_code"
|
||||
android:id="@+id/action_new_group"
|
||||
android:orderInCategory="100"
|
||||
android:title="New Group"
|
||||
android:title="@string/action_new_group"
|
||||
app:showAsAction="never"
|
||||
android:onClick="addGroup" />
|
||||
<item
|
||||
android:id="@+id/action_settings"
|
||||
android:orderInCategory="100"
|
||||
android:title="Settings"
|
||||
android:title="@string/action_settings"
|
||||
app:showAsAction="never"
|
||||
android:onClick="openSettings" />
|
||||
</menu>
|
@ -4,16 +4,40 @@
|
||||
<string name="action_settings">Einstellungen</string>
|
||||
<string name="next">Weiter</string>
|
||||
<string name="previous">Zurück</string>
|
||||
<string name="edit">Editieren</string>
|
||||
<string name="edit">Bearbeiten</string>
|
||||
<string name="choose_language">Sprachauswahl</string>
|
||||
<string name="cancel">Abbrechen</string>
|
||||
<string name="add">hinzufügen</string>
|
||||
<string name="no">nein</string>
|
||||
<string name="yes">ja</string>
|
||||
<string name="invalid_input">ungültige Eingabe</string>
|
||||
<string name="about">über</string>
|
||||
<string name="haptic_feedback">vibration</string>
|
||||
<string name="add">Hinzufügen</string>
|
||||
<string name="no">Nein</string>
|
||||
<string name="yes">Ja</string>
|
||||
<string name="invalid_input">Ungültige Eingabe</string>
|
||||
<string name="about">Über</string>
|
||||
<string name="haptic_feedback">Vibration</string>
|
||||
<string name="reset_app">App zurücksetzen</string>
|
||||
<string name="import_export">importieren / exportieren</string>
|
||||
<string name="import_export">Importieren / Exportieren</string>
|
||||
<string name="language">Sprache</string>
|
||||
<string name="biometric_lock_subtitle">Entsperre den Authentifikator</string>
|
||||
<string name="create_totp_title">Wähle den Code-Typ</string>
|
||||
<string name="action_new_group">Neue Gruppe</string>
|
||||
<string name="new_group_missing_title">Du musst einen Namen eingeben</string>
|
||||
<string name="qr_scanner_failed">Scannen des Codes fehlgeschlagen: %s</string>
|
||||
<string name="intro_video_failed">Abspielen des Videos fehlgeschlagen</string>
|
||||
<string name="edit_otp_title">OTP bearbeiten</string>
|
||||
<string name="group_delete_title">Löschen?</string>
|
||||
<string name="group_delete_message">Gruppe löschen?</string>
|
||||
<string name="hotp_generated_new_code">Neuen Code generiert</string>
|
||||
<string name="uri_handler_failed_title">Hinzufügen des Codes fehlgeschlagen</string>
|
||||
<string name="code_input_title">Code eingeben</string>
|
||||
<string name="failed_title">Aktion fehlgeschlagen</string>
|
||||
<string name="input_code_invalid_number">Ungültige Zahl</string>
|
||||
<string name="back">Zurück</string>
|
||||
<string-array name="view_edit_delete">
|
||||
<item>Anzeigen</item>
|
||||
<item>Bearbeiten</item>
|
||||
<item>Löschen</item>
|
||||
</string-array>
|
||||
<string-array name="rename_delete">
|
||||
<item>Umbenennen</item>
|
||||
<item>Löschen</item>
|
||||
</string-array>
|
||||
</resources>
|
@ -44,16 +44,40 @@
|
||||
vestibulum. Fusce dictum libero quis erat maximus, vitae volutpat diam dignissim.
|
||||
</string>
|
||||
<string name="edit">Edit</string>
|
||||
<string name="choose_language">choose Language</string>
|
||||
<string name="cancel">cancel</string>
|
||||
<string name="add">add</string>
|
||||
<string name="ok" translatable="false">Ok</string>
|
||||
<string name="no">no</string>
|
||||
<string name="yes">yes</string>
|
||||
<string name="invalid_input">invalid input</string>
|
||||
<string name="about">about</string>
|
||||
<string name="haptic_feedback">haptic feedback</string>
|
||||
<string name="reset_app">reset app</string>
|
||||
<string name="import_export">import / export</string>
|
||||
<string name="language">language</string>
|
||||
<string name="choose_language">Choose Language</string>
|
||||
<string name="cancel">Cancel</string>
|
||||
<string name="add">Add</string>
|
||||
<string name="ok" translatable="false">OK</string>
|
||||
<string name="no">No</string>
|
||||
<string name="yes">Yes</string>
|
||||
<string name="invalid_input">Invalid Input</string>
|
||||
<string name="about">About</string>
|
||||
<string name="haptic_feedback">Haptic Feedback</string>
|
||||
<string name="reset_app">Reset App</string>
|
||||
<string name="import_export">Import / Export</string>
|
||||
<string name="language">Language</string>
|
||||
<string name="biometric_lock_subtitle">Unlock the authenticator</string>
|
||||
<string name="create_totp_title">Select Code Type</string>
|
||||
<string name="action_new_group">New Group</string>
|
||||
<string name="new_group_missing_title">You need to input a name</string>
|
||||
<string name="qr_scanner_failed">Failed to scan code: %s</string>
|
||||
<string name="intro_video_failed">Failed to play video</string>
|
||||
<string name="edit_otp_title">Edit OTP</string>
|
||||
<string name="group_delete_title">Delete?</string>
|
||||
<string name="group_delete_message">Delete this?</string>
|
||||
<string name="hotp_generated_new_code">Generated new code</string>
|
||||
<string name="uri_handler_failed_title">Failed to add code</string>
|
||||
<string name="code_input_title">Input Code</string>
|
||||
<string name="failed_title">Action failed</string>
|
||||
<string name="input_code_invalid_number">Invalid number entered</string>
|
||||
<string name="back">Back</string>
|
||||
<string-array name="view_edit_delete">
|
||||
<item>View</item>
|
||||
<item>Edit</item>
|
||||
<item>Delete</item>
|
||||
</string-array>
|
||||
<string-array name="rename_delete">
|
||||
<item>Rename</item>
|
||||
<item>Delete</item>
|
||||
</string-array>
|
||||
</resources>
|
Loading…
Reference in New Issue
Block a user