Rework OTP editing (WIP)
This commit is contained in:
parent
041513fb30
commit
82cc4760cf
@ -167,6 +167,13 @@ public class MainActivity extends BaseActivity {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(fragment instanceof GroupFragment) {
|
||||||
|
GroupFragment frag = (GroupFragment) fragment;
|
||||||
|
getMenuInflater().inflate(frag.isEditing() ? R.menu.menu_otps_edit : R.menu.menu_otps, menu);
|
||||||
|
if(frag.isEditing() && frag.hasSelectedMultipleItems()) menu.removeItem(R.id.action_edit_group);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
getMenuInflater().inflate(R.menu.menu_main, menu);
|
getMenuInflater().inflate(R.menu.menu_main, menu);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -197,6 +204,14 @@ public class MainActivity extends BaseActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(fragment instanceof GroupFragment) {
|
||||||
|
GroupFragment groupFragment = (GroupFragment) fragment;
|
||||||
|
if(groupFragment.isEditing()) {
|
||||||
|
groupFragment.finishEditing();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(!(fragment instanceof HomeFragment)) {
|
if(!(fragment instanceof HomeFragment)) {
|
||||||
NavigationUtil.navigate(this, HomeFragment.class, null);
|
NavigationUtil.navigate(this, HomeFragment.class, null);
|
||||||
}
|
}
|
||||||
@ -256,7 +271,7 @@ public class MainActivity extends BaseActivity {
|
|||||||
if(!(fragment instanceof GroupFragment)) return;
|
if(!(fragment instanceof GroupFragment)) return;
|
||||||
|
|
||||||
((GroupFragment) fragment).addOTP(data);
|
((GroupFragment) fragment).addOTP(data);
|
||||||
}, () -> inputCode(), false);
|
}, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showHOTPDialog() {
|
private void showHOTPDialog() {
|
||||||
@ -265,7 +280,7 @@ public class MainActivity extends BaseActivity {
|
|||||||
if(!(fragment instanceof GroupFragment)) return;
|
if(!(fragment instanceof GroupFragment)) return;
|
||||||
|
|
||||||
((GroupFragment) fragment).addOTP(data);
|
((GroupFragment) fragment).addOTP(data);
|
||||||
}, () -> inputCode(), false);
|
}, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addGroup(MenuItem item) {
|
public void addGroup(MenuItem item) {
|
||||||
@ -289,6 +304,41 @@ public class MainActivity extends BaseActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addOTP(MenuItem item) {
|
||||||
|
Fragment frag = NavigationUtil.getCurrentFragment(this);
|
||||||
|
if(frag instanceof GroupFragment) {
|
||||||
|
((GroupFragment) frag).addOTP();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void viewOTP(MenuItem item) {
|
||||||
|
Fragment frag = NavigationUtil.getCurrentFragment(this);
|
||||||
|
if(frag instanceof GroupFragment) {
|
||||||
|
((GroupFragment) frag).viewOTP();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void editOTP(MenuItem item) {
|
||||||
|
Fragment frag = NavigationUtil.getCurrentFragment(this);
|
||||||
|
if(frag instanceof GroupFragment) {
|
||||||
|
((GroupFragment) frag).editOTP();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void moveOTP(MenuItem item) {
|
||||||
|
Fragment frag = NavigationUtil.getCurrentFragment(this);
|
||||||
|
if(frag instanceof GroupFragment) {
|
||||||
|
((GroupFragment) frag).moveOTP();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteOTP(MenuItem item) {
|
||||||
|
Fragment frag = NavigationUtil.getCurrentFragment(this);
|
||||||
|
if(frag instanceof GroupFragment) {
|
||||||
|
((GroupFragment) frag).deleteOTP();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPause() {
|
protected void onPause() {
|
||||||
super.onPause();
|
super.onPause();
|
||||||
|
@ -5,6 +5,7 @@ import android.os.Bundle;
|
|||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
@ -16,6 +17,7 @@ import androidx.recyclerview.widget.RecyclerView;
|
|||||||
import com.cringe_studios.cringe_authenticator.R;
|
import com.cringe_studios.cringe_authenticator.R;
|
||||||
import com.cringe_studios.cringe_authenticator.crypto.CryptoException;
|
import com.cringe_studios.cringe_authenticator.crypto.CryptoException;
|
||||||
import com.cringe_studios.cringe_authenticator.databinding.FragmentGroupBinding;
|
import com.cringe_studios.cringe_authenticator.databinding.FragmentGroupBinding;
|
||||||
|
import com.cringe_studios.cringe_authenticator.grouplist.GroupListItem;
|
||||||
import com.cringe_studios.cringe_authenticator.model.OTPData;
|
import com.cringe_studios.cringe_authenticator.model.OTPData;
|
||||||
import com.cringe_studios.cringe_authenticator.otplist.OTPListAdapter;
|
import com.cringe_studios.cringe_authenticator.otplist.OTPListAdapter;
|
||||||
import com.cringe_studios.cringe_authenticator.otplist.OTPListItem;
|
import com.cringe_studios.cringe_authenticator.otplist.OTPListItem;
|
||||||
@ -63,8 +65,7 @@ public class GroupFragment extends NamedFragment {
|
|||||||
|
|
||||||
FabUtil.showFabs(requireActivity());
|
FabUtil.showFabs(requireActivity());
|
||||||
|
|
||||||
otpListAdapter = new OTPListAdapter(requireContext(), data -> showOTPDialog(data));
|
otpListAdapter = new OTPListAdapter(requireContext(), binding.itemList);
|
||||||
|
|
||||||
binding.itemList.setAdapter(otpListAdapter);
|
binding.itemList.setAdapter(otpListAdapter);
|
||||||
|
|
||||||
loadOTPs();
|
loadOTPs();
|
||||||
@ -80,52 +81,6 @@ public class GroupFragment extends NamedFragment {
|
|||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showOTPDialog(OTPData data) {
|
|
||||||
new StyledDialogBuilder(requireContext())
|
|
||||||
.setTitle(R.string.edit_otp_title)
|
|
||||||
.setItems(R.array.view_edit_move_delete, (dialog, which) -> {
|
|
||||||
switch(which) {
|
|
||||||
case 0:
|
|
||||||
DialogUtil.showViewCodeDialog(getLayoutInflater(), data, () -> showOTPDialog(data));
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
DialogUtil.showEditCodeDialog(getLayoutInflater(), data, newData -> {
|
|
||||||
otpListAdapter.replace(data, newData);
|
|
||||||
saveOTPs();
|
|
||||||
}, () -> showOTPDialog(data));
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
DialogUtil.showChooseGroupDialog(requireContext(), group -> {
|
|
||||||
OTPDatabase.promptLoadDatabase(requireActivity(), () -> {
|
|
||||||
try {
|
|
||||||
OTPDatabase.getLoadedDatabase().addOTP(group, data);
|
|
||||||
OTPDatabase.saveDatabase(requireContext(), SettingsUtil.getCryptoParameters(requireContext()));
|
|
||||||
otpListAdapter.remove(data);
|
|
||||||
saveOTPs();
|
|
||||||
} catch (OTPDatabaseException | CryptoException e) {
|
|
||||||
DialogUtil.showErrorDialog(requireContext(), e.toString());
|
|
||||||
}
|
|
||||||
}, null);
|
|
||||||
saveOTPs();
|
|
||||||
}, null);
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
new StyledDialogBuilder(requireContext())
|
|
||||||
.setTitle(R.string.otp_delete_title)
|
|
||||||
.setMessage(R.string.otp_delete_message)
|
|
||||||
.setPositiveButton(R.string.yes, (d, w) -> {
|
|
||||||
otpListAdapter.remove(data);
|
|
||||||
saveOTPs();
|
|
||||||
})
|
|
||||||
.setNegativeButton(R.string.no, (d, w) -> {})
|
|
||||||
.show();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.setNegativeButton(R.string.cancel, (dialog, which) -> {})
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void saveOTPs() {
|
private void saveOTPs() {
|
||||||
OTPDatabase.promptLoadDatabase(requireActivity(), () -> {
|
OTPDatabase.promptLoadDatabase(requireActivity(), () -> {
|
||||||
try {
|
try {
|
||||||
@ -178,6 +133,89 @@ public class GroupFragment extends NamedFragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addOTP() {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
public void viewOTP() {
|
||||||
|
if(!otpListAdapter.isEditing()) return;
|
||||||
|
|
||||||
|
List<OTPListItem> items = otpListAdapter.getSelectedCodes();
|
||||||
|
if(items.size() != 1) return;
|
||||||
|
|
||||||
|
OTPData data = items.get(0).getOTPData();
|
||||||
|
DialogUtil.showViewCodeDialog(getLayoutInflater(), data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void editOTP() {
|
||||||
|
if(!otpListAdapter.isEditing()) return;
|
||||||
|
|
||||||
|
List<OTPListItem> items = otpListAdapter.getSelectedCodes();
|
||||||
|
if(items.size() != 1) return;
|
||||||
|
|
||||||
|
OTPData data = items.get(0).getOTPData();
|
||||||
|
DialogUtil.showEditCodeDialog(getLayoutInflater(), data, newData -> {
|
||||||
|
otpListAdapter.replace(data, newData);
|
||||||
|
saveOTPs();
|
||||||
|
otpListAdapter.finishEditing();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void moveOTP() {
|
||||||
|
if(!otpListAdapter.isEditing()) return;
|
||||||
|
|
||||||
|
List<OTPListItem> items = otpListAdapter.getSelectedCodes();
|
||||||
|
|
||||||
|
DialogUtil.showChooseGroupDialog(requireContext(), group -> {
|
||||||
|
OTPDatabase.promptLoadDatabase(requireActivity(), () -> {
|
||||||
|
try {
|
||||||
|
for(OTPListItem item : items) {
|
||||||
|
OTPData data = item.getOTPData();
|
||||||
|
OTPDatabase.getLoadedDatabase().addOTP(group, data);
|
||||||
|
OTPDatabase.saveDatabase(requireContext(), SettingsUtil.getCryptoParameters(requireContext()));
|
||||||
|
otpListAdapter.remove(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
saveOTPs();
|
||||||
|
} catch (OTPDatabaseException | CryptoException e) {
|
||||||
|
DialogUtil.showErrorDialog(requireContext(), e.toString());
|
||||||
|
}
|
||||||
|
}, null);
|
||||||
|
saveOTPs();
|
||||||
|
}, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteOTP() {
|
||||||
|
if(!otpListAdapter.isEditing()) return;
|
||||||
|
|
||||||
|
List<OTPListItem> items = otpListAdapter.getSelectedCodes();
|
||||||
|
|
||||||
|
new StyledDialogBuilder(requireContext())
|
||||||
|
.setTitle(R.string.otp_delete_title)
|
||||||
|
.setMessage(R.string.otp_delete_message)
|
||||||
|
.setPositiveButton(R.string.yes, (d, w) -> {
|
||||||
|
for(OTPListItem item : items) {
|
||||||
|
otpListAdapter.remove(item.getOTPData());
|
||||||
|
}
|
||||||
|
|
||||||
|
saveOTPs();
|
||||||
|
})
|
||||||
|
.setNegativeButton(R.string.no, (d, w) -> {})
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEditing() {
|
||||||
|
return otpListAdapter.isEditing();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void finishEditing() {
|
||||||
|
otpListAdapter.finishEditing();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasSelectedMultipleItems() {
|
||||||
|
return otpListAdapter.getSelectedCodes().size() > 1;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
|
@ -54,31 +54,6 @@ public class MenuFragment extends NamedFragment {
|
|||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showGroupDialog(String group) {
|
|
||||||
new StyledDialogBuilder(requireContext())
|
|
||||||
.setTitle(R.string.edit_group_title)
|
|
||||||
.setItems(R.array.rename_delete, (dialog, which) -> {
|
|
||||||
switch(which) {
|
|
||||||
case 0:
|
|
||||||
DialogUtil.showCreateGroupDialog(getLayoutInflater(), SettingsUtil.getGroupName(requireContext(), group), newName -> {
|
|
||||||
renameGroup(group, newName);
|
|
||||||
}, null);
|
|
||||||
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
new StyledDialogBuilder(requireContext())
|
|
||||||
.setTitle(R.string.group_delete_title)
|
|
||||||
.setMessage(R.string.group_delete_message)
|
|
||||||
.setPositiveButton(R.string.yes, (d, w) -> removeGroup(group))
|
|
||||||
.setNegativeButton(R.string.no, (d, w) -> {})
|
|
||||||
.show();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.setNegativeButton(R.string.cancel, (dialog, which) -> {})
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadGroups() {
|
private void loadGroups() {
|
||||||
List<String> items = SettingsUtil.getGroups(requireContext());
|
List<String> items = SettingsUtil.getGroups(requireContext());
|
||||||
|
|
||||||
@ -114,7 +89,7 @@ public class MenuFragment extends NamedFragment {
|
|||||||
|
|
||||||
new StyledDialogBuilder(requireContext())
|
new StyledDialogBuilder(requireContext())
|
||||||
.setTitle(R.string.group_delete_title)
|
.setTitle(R.string.group_delete_title)
|
||||||
.setMessage("Delete selected groups?")
|
.setMessage(R.string.group_delete_message)
|
||||||
.setPositiveButton(R.string.yes, (d, w) -> {
|
.setPositiveButton(R.string.yes, (d, w) -> {
|
||||||
for(GroupListItem item : groupListAdapter.getSelectedGroups()) {
|
for(GroupListItem item : groupListAdapter.getSelectedGroups()) {
|
||||||
removeGroup(item.getGroupId());
|
removeGroup(item.getGroupId());
|
||||||
|
@ -68,6 +68,7 @@ public class GroupListAdapter extends RecyclerView.Adapter<GroupListItem> {
|
|||||||
String group = items.get(position);
|
String group = items.get(position);
|
||||||
|
|
||||||
holder.setGroupId(group);
|
holder.setGroupId(group);
|
||||||
|
holder.setSelected(false);
|
||||||
|
|
||||||
holder.getBinding().button.setText(SettingsUtil.getGroupName(context, group));
|
holder.getBinding().button.setText(SettingsUtil.getGroupName(context, group));
|
||||||
|
|
||||||
@ -80,10 +81,6 @@ public class GroupListAdapter extends RecyclerView.Adapter<GroupListItem> {
|
|||||||
((BaseActivity) context).invalidateMenu();
|
((BaseActivity) context).invalidateMenu();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
/*holder.getBinding().button.setOnLongClickListener(view -> {
|
|
||||||
showMenuCallback.accept(group);
|
|
||||||
return true;
|
|
||||||
});*/
|
|
||||||
|
|
||||||
holder.getBinding().button.setOnLongClickListener(view -> {
|
holder.getBinding().button.setOnLongClickListener(view -> {
|
||||||
if(editing) return true;
|
if(editing) return true;
|
||||||
|
@ -40,7 +40,7 @@ public class GroupListItem extends RecyclerView.ViewHolder {
|
|||||||
this.selected = selected;
|
this.selected = selected;
|
||||||
|
|
||||||
if(selected) {
|
if(selected) {
|
||||||
binding.menuItemBackground.setBackground(new ColorDrawable(0xFFFF00FF));
|
binding.menuItemBackground.setBackground(new ColorDrawable(binding.getRoot().getContext().getResources().getColor(R.color.selected_highlight)));
|
||||||
}else {
|
}else {
|
||||||
binding.menuItemBackground.setBackground(null);
|
binding.menuItemBackground.setBackground(null);
|
||||||
}
|
}
|
||||||
|
@ -11,8 +11,10 @@ import android.widget.Toast;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.core.util.Consumer;
|
import androidx.core.util.Consumer;
|
||||||
|
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import com.cringe_studios.cringe_authenticator.BaseActivity;
|
||||||
import com.cringe_studios.cringe_authenticator.R;
|
import com.cringe_studios.cringe_authenticator.R;
|
||||||
import com.cringe_studios.cringe_authenticator.databinding.OtpCodeBinding;
|
import com.cringe_studios.cringe_authenticator.databinding.OtpCodeBinding;
|
||||||
import com.cringe_studios.cringe_authenticator.model.OTPData;
|
import com.cringe_studios.cringe_authenticator.model.OTPData;
|
||||||
@ -21,26 +23,29 @@ import com.cringe_studios.cringe_authenticator_library.OTPException;
|
|||||||
import com.cringe_studios.cringe_authenticator_library.OTPType;
|
import com.cringe_studios.cringe_authenticator_library.OTPType;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class OTPListAdapter extends RecyclerView.Adapter<OTPListItem> {
|
public class OTPListAdapter extends RecyclerView.Adapter<OTPListItem> {
|
||||||
|
|
||||||
private Context context;
|
private Context context;
|
||||||
|
|
||||||
|
private RecyclerView recyclerView;
|
||||||
|
|
||||||
private LayoutInflater inflater;
|
private LayoutInflater inflater;
|
||||||
|
|
||||||
private List<OTPData> items;
|
private List<OTPData> items;
|
||||||
|
|
||||||
private Handler handler;
|
private Handler handler;
|
||||||
|
|
||||||
private Consumer<OTPData> showMenuCallback;
|
private boolean editing;
|
||||||
|
|
||||||
public OTPListAdapter(Context context, Consumer<OTPData> showMenuCallback) {
|
public OTPListAdapter(Context context, RecyclerView recyclerView) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
|
this.recyclerView = recyclerView;
|
||||||
this.inflater = LayoutInflater.from(context);
|
this.inflater = LayoutInflater.from(context);
|
||||||
this.items = new ArrayList<>();
|
this.items = new ArrayList<>();
|
||||||
this.handler = new Handler(Looper.getMainLooper());
|
this.handler = new Handler(Looper.getMainLooper());
|
||||||
this.showMenuCallback = showMenuCallback;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@ -55,10 +60,13 @@ public class OTPListAdapter extends RecyclerView.Adapter<OTPListItem> {
|
|||||||
OTPData data = items.get(position);
|
OTPData data = items.get(position);
|
||||||
|
|
||||||
holder.setOTPData(data);
|
holder.setOTPData(data);
|
||||||
|
holder.setSelected(false);
|
||||||
|
|
||||||
holder.getBinding().label.setText(String.format("%s%s", data.getIssuer() == null || data.getIssuer().isEmpty() ? "" : data.getIssuer() + ": ", data.getName()));
|
holder.getBinding().label.setText(String.format("%s%s", data.getIssuer() == null || data.getIssuer().isEmpty() ? "" : data.getIssuer() + ": ", data.getName()));
|
||||||
holder.getBinding().progress.setVisibility(data.getType() == OTPType.TOTP ? View.VISIBLE : View.INVISIBLE);
|
holder.getBinding().progress.setVisibility(data.getType() == OTPType.TOTP ? View.VISIBLE : View.INVISIBLE);
|
||||||
|
|
||||||
holder.getBinding().getRoot().setOnClickListener(view -> {
|
holder.getBinding().getRoot().setOnClickListener(view -> {
|
||||||
|
if(!editing) {
|
||||||
if (!view.isClickable()) return;
|
if (!view.isClickable()) return;
|
||||||
|
|
||||||
if (data.getType() != OTPType.HOTP) return;
|
if (data.getType() != OTPType.HOTP) return;
|
||||||
@ -77,10 +85,19 @@ public class OTPListAdapter extends RecyclerView.Adapter<OTPListItem> {
|
|||||||
Toast.makeText(view.getContext(), R.string.hotp_generated_new_code, Toast.LENGTH_SHORT).show();
|
Toast.makeText(view.getContext(), R.string.hotp_generated_new_code, Toast.LENGTH_SHORT).show();
|
||||||
|
|
||||||
handler.postDelayed(() -> view.setClickable(true), 5000);
|
handler.postDelayed(() -> view.setClickable(true), 5000);
|
||||||
|
}else {
|
||||||
|
holder.setSelected(!holder.isSelected());
|
||||||
|
if(getSelectedCodes().isEmpty()) editing = false;
|
||||||
|
((BaseActivity) context).invalidateMenu();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
holder.getBinding().getRoot().setOnLongClickListener(view -> {
|
holder.getBinding().getRoot().setOnLongClickListener(view -> {
|
||||||
showMenuCallback.accept(holder.getOTPData());
|
if(editing) return true;
|
||||||
|
|
||||||
|
holder.setSelected(true);
|
||||||
|
editing = true;
|
||||||
|
((BaseActivity) context).invalidateMenu();
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -113,4 +130,59 @@ public class OTPListAdapter extends RecyclerView.Adapter<OTPListItem> {
|
|||||||
notifyItemRemoved(index);
|
notifyItemRemoved(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isEditing() {
|
||||||
|
return editing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void finishEditing() {
|
||||||
|
if(!editing) return;
|
||||||
|
|
||||||
|
editing = false;
|
||||||
|
for(OTPListItem item : getSelectedCodes()) {
|
||||||
|
item.setSelected(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
((BaseActivity) context).invalidateMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<OTPListItem> getSelectedCodes() {
|
||||||
|
if(!editing) return Collections.emptyList();
|
||||||
|
|
||||||
|
List<OTPListItem> selected = new ArrayList<>();
|
||||||
|
for(int i = 0; i < items.size(); i++) {
|
||||||
|
OTPListItem vh = (OTPListItem) recyclerView.findViewHolderForAdapterPosition(i);
|
||||||
|
if(vh == null) continue;
|
||||||
|
if(vh.isSelected()) selected.add(vh);
|
||||||
|
}
|
||||||
|
return selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void attachTouchHelper(RecyclerView view) {
|
||||||
|
new ItemTouchHelper(new OTPListAdapter.TouchHelperCallback()).attachToRecyclerView(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TouchHelperCallback extends ItemTouchHelper.Callback {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
|
||||||
|
return makeMovementFlags(ItemTouchHelper.UP | ItemTouchHelper.DOWN, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
|
||||||
|
Collections.swap(items, viewHolder.getAdapterPosition(), target.getAdapterPosition());
|
||||||
|
notifyItemMoved(viewHolder.getAdapterPosition(), target.getAdapterPosition());
|
||||||
|
//saveGroups.run();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLongPressDragEnabled() {
|
||||||
|
return editing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
package com.cringe_studios.cringe_authenticator.otplist;
|
package com.cringe_studios.cringe_authenticator.otplist;
|
||||||
|
|
||||||
|
import android.graphics.drawable.ColorDrawable;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import com.cringe_studios.cringe_authenticator.R;
|
||||||
import com.cringe_studios.cringe_authenticator.databinding.OtpCodeBinding;
|
import com.cringe_studios.cringe_authenticator.databinding.OtpCodeBinding;
|
||||||
import com.cringe_studios.cringe_authenticator.model.OTPData;
|
import com.cringe_studios.cringe_authenticator.model.OTPData;
|
||||||
|
|
||||||
@ -12,6 +15,8 @@ public class OTPListItem extends RecyclerView.ViewHolder {
|
|||||||
|
|
||||||
private OTPData otpData;
|
private OTPData otpData;
|
||||||
|
|
||||||
|
private boolean selected;
|
||||||
|
|
||||||
public OTPListItem(OtpCodeBinding binding) {
|
public OTPListItem(OtpCodeBinding binding) {
|
||||||
super(binding.getRoot());
|
super(binding.getRoot());
|
||||||
this.binding = binding;
|
this.binding = binding;
|
||||||
@ -43,4 +48,18 @@ public class OTPListItem extends RecyclerView.ViewHolder {
|
|||||||
return b.toString();
|
return b.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setSelected(boolean selected) {
|
||||||
|
this.selected = selected;
|
||||||
|
|
||||||
|
if(selected) {
|
||||||
|
binding.otpCodeBackground.setBackground(new ColorDrawable(binding.getRoot().getContext().getResources().getColor(R.color.selected_highlight)));
|
||||||
|
}else {
|
||||||
|
binding.otpCodeBackground.setBackground(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSelected() {
|
||||||
|
return selected;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -28,12 +28,11 @@ public class DialogUtil {
|
|||||||
|
|
||||||
private static final Integer[] DIGITS = new Integer[]{6, 7, 8, 9, 10, 11, 12};
|
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) {
|
private static void showCodeDialog(Context context, View view, DialogCallback ok) {
|
||||||
AlertDialog dialog = new StyledDialogBuilder(context)
|
AlertDialog dialog = new StyledDialogBuilder(context)
|
||||||
.setTitle(R.string.code_input_title)
|
.setTitle(R.string.code_input_title)
|
||||||
.setView(view)
|
.setView(view)
|
||||||
.setPositiveButton(R.string.ok, (btnView, which) -> {})
|
.setPositiveButton(R.string.ok, (btnView, which) -> {})
|
||||||
.setNeutralButton(R.string.back, (btnView, which) -> back.run())
|
|
||||||
.setNegativeButton(R.string.cancel, (btnView, which) -> {})
|
.setNegativeButton(R.string.cancel, (btnView, which) -> {})
|
||||||
.create();
|
.create();
|
||||||
|
|
||||||
@ -60,7 +59,7 @@ public class DialogUtil {
|
|||||||
showErrorDialog(context, errorMessage, null);
|
showErrorDialog(context, errorMessage, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void showTOTPDialog(LayoutInflater inflater, OTPData initialData, Consumer<OTPData> callback, Runnable back, boolean view) {
|
public static void showTOTPDialog(LayoutInflater inflater, OTPData initialData, Consumer<OTPData> callback, boolean view) {
|
||||||
Context context = inflater.getContext();
|
Context context = inflater.getContext();
|
||||||
DialogInputCodeTotpBinding binding = DialogInputCodeTotpBinding.inflate(inflater);
|
DialogInputCodeTotpBinding binding = DialogInputCodeTotpBinding.inflate(inflater);
|
||||||
|
|
||||||
@ -122,10 +121,10 @@ public class DialogUtil {
|
|||||||
showErrorDialog(context, context.getString(R.string.input_code_invalid_number));
|
showErrorDialog(context, context.getString(R.string.input_code_invalid_number));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}, back);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void showHOTPDialog(LayoutInflater inflater, OTPData initialData, Consumer<OTPData> callback, Runnable back, boolean view) {
|
public static void showHOTPDialog(LayoutInflater inflater, OTPData initialData, Consumer<OTPData> callback, boolean view) {
|
||||||
Context context = inflater.getContext();
|
Context context = inflater.getContext();
|
||||||
DialogInputCodeHotpBinding binding = DialogInputCodeHotpBinding.inflate(inflater);
|
DialogInputCodeHotpBinding binding = DialogInputCodeHotpBinding.inflate(inflater);
|
||||||
|
|
||||||
@ -187,21 +186,21 @@ public class DialogUtil {
|
|||||||
showErrorDialog(context, context.getString(R.string.input_code_invalid_number));
|
showErrorDialog(context, context.getString(R.string.input_code_invalid_number));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}, back);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void showViewCodeDialog(LayoutInflater inflater, @NonNull OTPData initialData, Runnable back) {
|
public static void showViewCodeDialog(LayoutInflater inflater, @NonNull OTPData initialData) {
|
||||||
// TODO: use better dialogs
|
// TODO: use better dialogs
|
||||||
switch(initialData.getType()) {
|
switch(initialData.getType()) {
|
||||||
case HOTP: showHOTPDialog(inflater, initialData, d -> {}, back, true); break;
|
case HOTP: showHOTPDialog(inflater, initialData, d -> {}, true); break;
|
||||||
case TOTP: showTOTPDialog(inflater, initialData, d -> {}, back, true); break;
|
case TOTP: showTOTPDialog(inflater, initialData, d -> {}, true); break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void showEditCodeDialog(LayoutInflater inflater, @NonNull OTPData initialData, Consumer<OTPData> callback, Runnable back) {
|
public static void showEditCodeDialog(LayoutInflater inflater, @NonNull OTPData initialData, Consumer<OTPData> callback) {
|
||||||
switch(initialData.getType()) {
|
switch(initialData.getType()) {
|
||||||
case HOTP: showHOTPDialog(inflater, initialData, callback, back, false); break;
|
case HOTP: showHOTPDialog(inflater, initialData, callback, false); break;
|
||||||
case TOTP: showTOTPDialog(inflater, initialData, callback, back, false); break;
|
case TOTP: showTOTPDialog(inflater, initialData, callback, false); break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<vector android:height="24dp" android:tint="#000000"
|
<vector android:height="24dp" android:tint="?android:attr/textColor"
|
||||||
android:viewportHeight="24" android:viewportWidth="24"
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<path android:fillColor="@android:color/white" android:pathData="M10,3L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h5v2h2L12,1h-2v2zM10,18L5,18l5,-6v6zM19,3h-5v2h5v13l-5,-6v9h5c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2z"/>
|
<path android:fillColor="@android:color/white" android:pathData="M10,3L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h5v2h2L12,1h-2v2zM10,18L5,18l5,-6v6zM19,3h-5v2h5v13l-5,-6v9h5c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2z"/>
|
||||||
|
@ -8,8 +8,7 @@
|
|||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent">
|
||||||
android:padding="16dp">
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/itemList"
|
android:id="@+id/itemList"
|
||||||
@ -23,7 +22,7 @@
|
|||||||
|
|
||||||
<Space
|
<Space
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="65dp"
|
android:layout_height="76dp"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/itemList" />
|
app:layout_constraintTop_toBottomOf="@+id/itemList" />
|
||||||
|
@ -35,5 +35,12 @@
|
|||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="76dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/menu_items" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
</androidx.core.widget.NestedScrollView>
|
</androidx.core.widget.NestedScrollView>
|
@ -7,9 +7,7 @@
|
|||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:padding="5dp"
|
android:padding="5dp"
|
||||||
android:paddingStart="16dp"
|
android:paddingStart="16dp"
|
||||||
android:paddingEnd="16dp"
|
android:paddingEnd="16dp" >
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true" >
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatButton
|
<androidx.appcompat.widget.AppCompatButton
|
||||||
android:id="@+id/button"
|
android:id="@+id/button"
|
||||||
|
@ -2,17 +2,21 @@
|
|||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/otp_code_background"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="5dp"
|
|
||||||
android:background="@drawable/button_themed"
|
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:padding="5dp">
|
android:padding="5dp"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="16dp">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal"
|
||||||
|
android:paddingTop="5dp"
|
||||||
|
android:paddingBottom="5dp"
|
||||||
|
android:background="@drawable/button_themed">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/imageView5"
|
android:id="@+id/imageView5"
|
||||||
|
23
app/src/main/res/menu/menu_otps.xml
Normal file
23
app/src/main/res/menu/menu_otps.xml
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_add_otp"
|
||||||
|
android:orderInCategory="100"
|
||||||
|
android:icon="@drawable/baseline_add_24"
|
||||||
|
android:title="@string/action_new_group"
|
||||||
|
android:onClick="addOTP"
|
||||||
|
app:showAsAction="always" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_settings"
|
||||||
|
android:orderInCategory="100"
|
||||||
|
android:title="@string/action_settings"
|
||||||
|
app:showAsAction="never"
|
||||||
|
android:onClick="openSettings" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_about"
|
||||||
|
android:orderInCategory="100"
|
||||||
|
android:title="@string/action_about"
|
||||||
|
app:showAsAction="never"
|
||||||
|
android:onClick="openAbout" />
|
||||||
|
</menu>
|
44
app/src/main/res/menu/menu_otps_edit.xml
Normal file
44
app/src/main/res/menu/menu_otps_edit.xml
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_view_otp"
|
||||||
|
android:orderInCategory="100"
|
||||||
|
android:icon="@drawable/baseline_compare_24"
|
||||||
|
android:title="View OTP"
|
||||||
|
android:onClick="viewOTP"
|
||||||
|
app:showAsAction="always" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_edit_otp"
|
||||||
|
android:orderInCategory="100"
|
||||||
|
android:icon="@drawable/baseline_edit_24"
|
||||||
|
android:title="Edit OTP"
|
||||||
|
android:onClick="editOTP"
|
||||||
|
app:showAsAction="always" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_move_otp"
|
||||||
|
android:orderInCategory="100"
|
||||||
|
android:icon="@drawable/baseline_qr_code_scanner_24"
|
||||||
|
android:title="Move OTP"
|
||||||
|
android:onClick="moveOTP"
|
||||||
|
app:showAsAction="always" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_delete_otp"
|
||||||
|
android:orderInCategory="100"
|
||||||
|
android:icon="@drawable/baseline_delete_24"
|
||||||
|
android:title="Delete OTP"
|
||||||
|
android:onClick="deleteOTP"
|
||||||
|
app:showAsAction="always" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_settings"
|
||||||
|
android:orderInCategory="100"
|
||||||
|
android:title="@string/action_settings"
|
||||||
|
app:showAsAction="never"
|
||||||
|
android:onClick="openSettings" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_about"
|
||||||
|
android:orderInCategory="100"
|
||||||
|
android:title="@string/action_about"
|
||||||
|
app:showAsAction="never"
|
||||||
|
android:onClick="openAbout" />
|
||||||
|
</menu>
|
@ -21,8 +21,8 @@
|
|||||||
<string name="qr_scanner_failed">Scannen fehlgeschlagen: %s</string>
|
<string name="qr_scanner_failed">Scannen fehlgeschlagen: %s</string>
|
||||||
<string name="intro_video_failed">Abspielen des Videos fehlgeschlagen</string>
|
<string name="intro_video_failed">Abspielen des Videos fehlgeschlagen</string>
|
||||||
<string name="edit_otp_title">OTP bearbeiten</string>
|
<string name="edit_otp_title">OTP bearbeiten</string>
|
||||||
<string name="group_delete_title">Gruppe(n) löschen</string>
|
<string name="group_delete_title">Gruppen löschen</string>
|
||||||
<string name="group_delete_message">Willst du die ausgewählte(n) Gruppe(n) löschen?\n\nHinweis: Dadurch werden alle darin enthaltenen OTPs gelöscht!</string>
|
<string name="group_delete_message">Willst du die ausgewählten Gruppen löschen?\n\nHinweis: Dadurch werden alle darin enthaltenen OTPs gelöscht!</string>
|
||||||
<string name="hotp_generated_new_code">Neuen Code generiert</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="uri_handler_failed_title">Hinzufügen des Codes fehlgeschlagen</string>
|
||||||
<string name="code_input_title">Code eingeben</string>
|
<string name="code_input_title">Code eingeben</string>
|
||||||
@ -30,7 +30,7 @@
|
|||||||
<string name="input_code_invalid_number">Ungültige Zahl</string>
|
<string name="input_code_invalid_number">Ungültige Zahl</string>
|
||||||
<string name="back">Zurück</string>
|
<string name="back">Zurück</string>
|
||||||
<string name="otp_delete_title">OTP löschen</string>
|
<string name="otp_delete_title">OTP löschen</string>
|
||||||
<string name="otp_delete_message">Willst du das OTP löschen?</string>
|
<string name="otp_delete_message">Willst du die ausgewählten OTPs löschen?</string>
|
||||||
<string name="edit_group_title">Gruppe bearbeiten</string>
|
<string name="edit_group_title">Gruppe bearbeiten</string>
|
||||||
<string name="settings_enable_intro_video">Intro-Video zeigen</string>
|
<string name="settings_enable_intro_video">Intro-Video zeigen</string>
|
||||||
<string name="settings_biometric_lock">Biometrische Authentifizierung aktivieren</string>
|
<string name="settings_biometric_lock">Biometrische Authentifizierung aktivieren</string>
|
||||||
|
@ -16,4 +16,6 @@
|
|||||||
<color name="color_yellow">#FFE500</color>
|
<color name="color_yellow">#FFE500</color>
|
||||||
<color name="color_turquoise">#00FFF7</color>
|
<color name="color_turquoise">#00FFF7</color>
|
||||||
<color name="color_green">#00FF0A</color>
|
<color name="color_green">#00FF0A</color>
|
||||||
|
|
||||||
|
<color name="selected_highlight">#33008BFF</color>
|
||||||
</resources>
|
</resources>
|
@ -21,16 +21,16 @@
|
|||||||
<string name="qr_scanner_failed">Scan failed: %s</string>
|
<string name="qr_scanner_failed">Scan failed: %s</string>
|
||||||
<string name="intro_video_failed">Failed to play video</string>
|
<string name="intro_video_failed">Failed to play video</string>
|
||||||
<string name="edit_otp_title">Edit OTP</string>
|
<string name="edit_otp_title">Edit OTP</string>
|
||||||
<string name="group_delete_title">Delete Group(s)</string>
|
<string name="group_delete_title">Delete Groups</string>
|
||||||
<string name="group_delete_message">Do you want to delete the group(s)?\n\nNote: This will delete all of the contained OTPs!</string>
|
<string name="group_delete_message">Do you want to delete the groups?\n\nNote: This will delete all of the contained OTPs!</string>
|
||||||
<string name="hotp_generated_new_code">Generated new code</string>
|
<string name="hotp_generated_new_code">Generated new code</string>
|
||||||
<string name="uri_handler_failed_title">Failed to add code</string>
|
<string name="uri_handler_failed_title">Failed to add code</string>
|
||||||
<string name="code_input_title">Input Code</string>
|
<string name="code_input_title">Input Code</string>
|
||||||
<string name="failed_title">Action failed</string>
|
<string name="failed_title">Action failed</string>
|
||||||
<string name="input_code_invalid_number">Invalid number entered</string>
|
<string name="input_code_invalid_number">Invalid number entered</string>
|
||||||
<string name="back">Back</string>
|
<string name="back">Back</string>
|
||||||
<string name="otp_delete_title">Delete OTP</string>
|
<string name="otp_delete_title">Delete OTP(s)</string>
|
||||||
<string name="otp_delete_message">Do you want to delete the OTP?</string>
|
<string name="otp_delete_message">Do you want to delete the selected OTP(s)?</string>
|
||||||
<string name="edit_group_title">Edit Group</string>
|
<string name="edit_group_title">Edit Group</string>
|
||||||
<string name="settings_enable_intro_video">Enable intro video</string>
|
<string name="settings_enable_intro_video">Enable intro video</string>
|
||||||
<string name="settings_biometric_lock">Require biometric unlock</string>
|
<string name="settings_biometric_lock">Require biometric unlock</string>
|
||||||
|
Loading…
Reference in New Issue
Block a user