Allow adding/removing menu items, Fix TOTP/HOTP code input

This commit is contained in:
MrLetsplay 2023-06-26 21:13:35 +02:00
parent eb57876fe3
commit 002475d1b3
Signed by: mr
SSH Key Fingerprint: SHA256:92jBH80vpXyaZHjaIl47pjRq+Yt7XGTArqQg1V7hSqg
18 changed files with 387 additions and 68 deletions

View File

@ -1,17 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="deploymentTargetDropDown"> <component name="deploymentTargetDropDown">
<runningDeviceTargetSelectedWithDropDown> <targetSelectedWithDropDown>
<Target> <Target>
<type value="RUNNING_DEVICE_TARGET" /> <type value="QUICK_BOOT_TARGET" />
<deviceKey> <deviceKey>
<Key> <Key>
<type value="SERIAL_NUMBER" /> <type value="VIRTUAL_DEVICE_PATH" />
<value value="R38N50464FV" /> <value value="$USER_HOME$/.android/avd/Pixel_2_API_33.avd" />
</Key> </Key>
</deviceKey> </deviceKey>
</Target> </Target>
</runningDeviceTargetSelectedWithDropDown> </targetSelectedWithDropDown>
<timeTargetWasSelectedWithDropDown value="2023-06-25T19:29:01.472337342Z" /> <timeTargetWasSelectedWithDropDown value="2023-06-26T17:29:55.573199337Z" />
</component> </component>
</project> </project>

View File

@ -35,15 +35,15 @@ dependencies {
implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.9.0' implementation 'com.google.android.material:material:1.9.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.1' implementation 'androidx.lifecycle:lifecycle-livedata:2.4.1'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1' implementation 'androidx.lifecycle:lifecycle-viewmodel:2.4.1'
implementation 'androidx.navigation:navigation-fragment:2.5.3' implementation 'androidx.navigation:navigation-fragment:2.5.3'
implementation 'androidx.navigation:navigation-ui:2.5.3' implementation 'androidx.navigation:navigation-ui:2.5.3'
testImplementation 'junit:junit:4.13.2' testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
implementation "androidx.biometric:biometric:1.1.0" implementation "androidx.biometric:biometric:1.1.0"
implementation 'com.cringe_studios:CringeAuthenticatorLibrary:1.0' implementation 'com.cringe_studios:CringeAuthenticatorLibrary:1.2'
implementation 'com.google.mlkit:barcode-scanning:17.1.0' implementation 'com.google.mlkit:barcode-scanning:17.1.0'
implementation 'com.google.code.gson:gson:2.8.9' implementation 'com.google.code.gson:gson:2.8.9'

View File

@ -6,6 +6,7 @@ import android.content.SharedPreferences;
import android.media.MediaPlayer; import android.media.MediaPlayer;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Toast; import android.widget.Toast;
@ -24,6 +25,8 @@ public class IntroActivity extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
Log.i("AMOGUS", "CREATE");
if (!SettingsUtil.isIntroVideoEnabled(this)) { if (!SettingsUtil.isIntroVideoEnabled(this)) {
openMainActivity(); openMainActivity();
return; return;
@ -61,11 +64,16 @@ public class IntroActivity extends AppCompatActivity {
@Override @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
// When the Activity is destroyed, release our MediaPlayer and set it to null.
if(mMediaPlayer != null) mMediaPlayer.release(); if(mMediaPlayer != null) mMediaPlayer.release();
mMediaPlayer = null; mMediaPlayer = null;
} }
@Override
protected void onResume() {
super.onResume();
binding.videoView.start();
}
private void setDimension() { private void setDimension() {
float videoProportion = (float) mMediaPlayer.getVideoHeight() / mMediaPlayer.getVideoWidth(); float videoProportion = (float) mMediaPlayer.getVideoHeight() / mMediaPlayer.getVideoWidth();
int screenWidth = getResources().getDisplayMetrics().widthPixels; int screenWidth = getResources().getDisplayMetrics().widthPixels;

View File

@ -23,6 +23,7 @@ import androidx.navigation.ui.NavigationUI;
import com.cringe_studios.cringe_authenticator.databinding.ActivityMainBinding; import com.cringe_studios.cringe_authenticator.databinding.ActivityMainBinding;
import com.cringe_studios.cringe_authenticator.databinding.DialogInputCodeChoiceBinding; 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.databinding.DialogInputCodeTotpBinding;
import com.cringe_studios.cringe_authenticator.fragment.DynamicFragment; import com.cringe_studios.cringe_authenticator.fragment.DynamicFragment;
import com.cringe_studios.cringe_authenticator.fragment.HomeFragment; import com.cringe_studios.cringe_authenticator.fragment.HomeFragment;
@ -185,7 +186,6 @@ public class MainActivity extends AppCompatActivity {
} }
private void showTOTPDialog() { private void showTOTPDialog() {
// TODO: checksum option
DialogInputCodeTotpBinding binding = DialogInputCodeTotpBinding.inflate(getLayoutInflater()); DialogInputCodeTotpBinding binding = DialogInputCodeTotpBinding.inflate(getLayoutInflater());
binding.inputAlgorithm.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, OTPAlgorithm.values())); 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})); binding.inputDigits.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, new Integer[]{6, 7, 8, 9, 10, 11, 12}));
@ -199,9 +199,9 @@ public class MainActivity extends AppCompatActivity {
OTPAlgorithm algorithm = (OTPAlgorithm) binding.inputAlgorithm.getSelectedItem(); OTPAlgorithm algorithm = (OTPAlgorithm) binding.inputAlgorithm.getSelectedItem();
int digits = (int) binding.inputDigits.getSelectedItem(); int digits = (int) binding.inputDigits.getSelectedItem();
int period = Integer.parseInt(binding.inputPeriod.getText().toString()); int period = Integer.parseInt(binding.inputPeriod.getText().toString());
boolean checksum = binding.inputChecksum.isChecked();
// TODO: checksum OTPData data = new OTPData(name, OTPType.TOTP, secret, algorithm, digits, period, 0, checksum);
OTPData data = new OTPData(name, OTPType.TOTP, secret, algorithm, digits, period, 0, false);
String errorMessage = data.validate(); String errorMessage = data.validate();
if(errorMessage != null) { if(errorMessage != null) {
@ -219,8 +219,36 @@ public class MainActivity extends AppCompatActivity {
} }
private void showHOTPDialog() { private void showHOTPDialog() {
DialogInputCodeTotpBinding binding = DialogInputCodeTotpBinding.inflate(getLayoutInflater()); DialogInputCodeHotpBinding binding = DialogInputCodeHotpBinding.inflate(getLayoutInflater());
showCodeDialog(binding.getRoot(), () -> true); 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(), () -> {
Fragment fragment = NavigationUtil.getCurrentFragment(this);
if(!(fragment instanceof DynamicFragment)) return true;
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;
}
((DynamicFragment) fragment).addOTP(data);
return true;
}catch(NumberFormatException e) {
showErrorDialog("Invalid number entered");
return false;
}
});
} }
private void showCodeDialog(View view, DialogCallback ok) { private void showCodeDialog(View view, DialogCallback ok) {
@ -254,7 +282,17 @@ public class MainActivity extends AppCompatActivity {
new AlertDialog.Builder(this) new AlertDialog.Builder(this)
.setTitle("New Group") .setTitle("New Group")
.setView(t) .setView(t)
.setPositiveButton("Add", (view, which) -> {}) .setPositiveButton("Add", (view, which) -> {
if(t.getText().length() == 0) {
showErrorDialog("You need to input a name");
return;
}
Fragment frag = NavigationUtil.getCurrentFragment(this);
if(frag instanceof MenuFragment) {
((MenuFragment) frag).addGroup(t.getText().toString());
}
})
.setNegativeButton("Cancel", (view, which) -> {}) .setNegativeButton("Cancel", (view, which) -> {})
.show(); .show();
} }

View File

@ -22,7 +22,7 @@ import com.cringe_studios.cringe_authenticator.util.SettingsUtil;
import java.util.List; import java.util.List;
public class DynamicFragment extends Fragment { public class DynamicFragment extends NamedFragment {
public static final String BUNDLE_GROUP = "group"; public static final String BUNDLE_GROUP = "group";
@ -36,6 +36,11 @@ public class DynamicFragment extends Fragment {
private OTPListAdapter otpListAdapter; private OTPListAdapter otpListAdapter;
@Override
public String getName() {
return groupName;
}
@Override @Override
public void onCreate(@Nullable Bundle savedInstanceState) { public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@ -72,8 +77,7 @@ public class DynamicFragment extends Fragment {
} }
private void loadOTPs() { private void loadOTPs() {
SharedPreferences prefs = requireActivity().getSharedPreferences(SettingsUtil.GROUPS_PREFS_NAME, Context.MODE_PRIVATE); List<OTPData> data = SettingsUtil.getOTPs(requireContext(), groupName);
List<OTPData> data = SettingsUtil.getOTPs(prefs, groupName);
for(OTPData otp : data) { for(OTPData otp : data) {
otpListAdapter.add(otp); otpListAdapter.add(otp);
@ -81,8 +85,7 @@ public class DynamicFragment extends Fragment {
} }
public void addOTP(OTPData data) { public void addOTP(OTPData data) {
SharedPreferences prefs = requireActivity().getSharedPreferences(SettingsUtil.GROUPS_PREFS_NAME, Context.MODE_PRIVATE); SettingsUtil.addOTP(requireContext(), groupName, data);
SettingsUtil.addOTP(prefs, groupName, data);
otpListAdapter.add(data); otpListAdapter.add(data);
} }

View File

@ -11,10 +11,15 @@ import androidx.fragment.app.Fragment;
import com.cringe_studios.cringe_authenticator.databinding.FragmentHomeBinding; import com.cringe_studios.cringe_authenticator.databinding.FragmentHomeBinding;
import com.cringe_studios.cringe_authenticator.util.FabUtil; import com.cringe_studios.cringe_authenticator.util.FabUtil;
public class HomeFragment extends Fragment { public class HomeFragment extends NamedFragment {
private FragmentHomeBinding binding; private FragmentHomeBinding binding;
@Override
public String getName() {
return "Home";
}
@Override @Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
binding = FragmentHomeBinding.inflate(inflater, container, false); binding = FragmentHomeBinding.inflate(inflater, container, false);

View File

@ -3,6 +3,7 @@ package com.cringe_studios.cringe_authenticator.fragment;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -12,55 +13,71 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import com.cringe_studios.cringe_authenticator.OTPData;
import com.cringe_studios.cringe_authenticator.databinding.FragmentMenuBinding; import com.cringe_studios.cringe_authenticator.databinding.FragmentMenuBinding;
import com.cringe_studios.cringe_authenticator.databinding.MenuItemBinding; import com.cringe_studios.cringe_authenticator.databinding.MenuItemBinding;
import com.cringe_studios.cringe_authenticator.grouplist.GroupListAdapter;
import com.cringe_studios.cringe_authenticator.grouplist.GroupListItem;
import com.cringe_studios.cringe_authenticator.util.FabUtil; import com.cringe_studios.cringe_authenticator.util.FabUtil;
import com.cringe_studios.cringe_authenticator.util.NavigationUtil; import com.cringe_studios.cringe_authenticator.util.NavigationUtil;
import com.cringe_studios.cringe_authenticator.util.SettingsUtil;
public class MenuFragment extends Fragment { import java.util.List;
public class MenuFragment extends NamedFragment {
private FragmentMenuBinding binding; private FragmentMenuBinding binding;
private GroupListAdapter groupListAdapter;
@Override
public String getName() {
return "Menu";
}
@Nullable @Nullable
@Override @Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
binding = FragmentMenuBinding.inflate(inflater); binding = FragmentMenuBinding.inflate(inflater);
SharedPreferences pr = requireContext().getSharedPreferences("menu", Context.MODE_PRIVATE); groupListAdapter = new GroupListAdapter(requireContext(), group -> {
Bundle bundle = new Bundle();
bundle.putString(DynamicFragment.BUNDLE_GROUP, group);
NavigationUtil.navigate(this, DynamicFragment.class, bundle);
}, this::removeGroup);
String[] items = {"a", "b"}; binding.menuItems.setAdapter(groupListAdapter);
for(String item : items) { loadGroups();
MenuItemBinding itemBinding = MenuItemBinding.inflate(inflater, binding.menuItems, false);
itemBinding.button.setText(item);
itemBinding.button.setOnClickListener(view -> {
Bundle bundle = new Bundle();
bundle.putString(DynamicFragment.BUNDLE_GROUP, item);
NavigationUtil.navigate(this, DynamicFragment.class, bundle);
});
itemBinding.button.setOnLongClickListener(view -> {
new AlertDialog.Builder(requireContext())
.setTitle("Delete?")
.setMessage("Delete this?")
.setPositiveButton("Yes", (dialog, which) -> itemBinding.button.setVisibility(View.GONE))
.setNegativeButton("No", (dialog, which) -> {})
.show();
// TODO: better method?
// TODO: actually delete
return true;
});
binding.menuItems.addView(itemBinding.getRoot());
}
binding.editSwitch.setOnCheckedChangeListener((view, checked) -> { /*binding.editSwitch.setOnCheckedChangeListener((view, checked) -> {
// TODO: edit mode // TODO: edit mode
}); });*/
FabUtil.hideFabs(requireActivity()); FabUtil.hideFabs(requireActivity());
return binding.getRoot(); return binding.getRoot();
} }
private void loadGroups() {
List<String> items = SettingsUtil.getGroups(requireContext());
Log.i("AMOGUS", "items: " + items);
for(String item : items) {
groupListAdapter.add(item);
}
}
public void addGroup(String group) {
SettingsUtil.addGroup(requireContext(), group);
groupListAdapter.add(group);
}
public void removeGroup(String group) {
SettingsUtil.removeGroup(requireContext(), group);
groupListAdapter.remove(group);
}
@Override @Override
public void onDestroyView() { public void onDestroyView() {
super.onDestroyView(); super.onDestroyView();

View File

@ -0,0 +1,9 @@
package com.cringe_studios.cringe_authenticator.fragment;
import androidx.fragment.app.Fragment;
public abstract class NamedFragment extends Fragment {
public abstract String getName();
}

View File

@ -27,10 +27,15 @@ import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
public class SettingsFragment extends Fragment { public class SettingsFragment extends NamedFragment {
private FragmentSettingsBinding binding; private FragmentSettingsBinding binding;
@Override
public String getName() {
return "Settings";
}
@Nullable @Nullable
@Override @Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {

View File

@ -0,0 +1,93 @@
package com.cringe_studios.cringe_authenticator.grouplist;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.util.Consumer;
import androidx.recyclerview.widget.RecyclerView;
import com.cringe_studios.cringe_authenticator.OTPData;
import com.cringe_studios.cringe_authenticator.databinding.MenuItemBinding;
import com.cringe_studios.cringe_authenticator.fragment.DynamicFragment;
import com.cringe_studios.cringe_authenticator.util.NavigationUtil;
import com.cringe_studios.cringe_authenticator.util.SettingsUtil;
import java.util.ArrayList;
import java.util.List;
public class GroupListAdapter extends RecyclerView.Adapter<GroupListItem> {
private Context context;
private LayoutInflater inflater;
private List<String> items;
private Handler handler;
private Consumer<String> navigateToGroup;
private Consumer<String> removeGroup;
public GroupListAdapter(Context context, Consumer<String> navigateToGroup, Consumer<String> removeGroup) {
this.context = context;
this.navigateToGroup = navigateToGroup;
this.removeGroup = removeGroup;
this.inflater = LayoutInflater.from(context);
this.items = new ArrayList<>();
this.handler = new Handler(Looper.getMainLooper());
}
@NonNull
@Override
public GroupListItem onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
MenuItemBinding binding = MenuItemBinding.inflate(inflater, parent, false);
return new GroupListItem(binding);
}
@Override
public void onBindViewHolder(@NonNull GroupListItem holder, int position) {
String group = items.get(position);
holder.getBinding().button.setText(group);
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) -> {})
.show();
// TODO: better method?
// TODO: actually delete
return true;
});
}
@Override
public int getItemCount() {
return items.size();
}
public void add(String group) {
items.add(group);
notifyItemInserted(items.size() - 1);
}
public void remove(String group) {
int index = items.indexOf(group);
if(index == -1) return;
items.remove(group);
notifyItemRemoved(index);
}
}

View File

@ -0,0 +1,23 @@
package com.cringe_studios.cringe_authenticator.grouplist;
import android.view.MenuItem;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.cringe_studios.cringe_authenticator.databinding.MenuItemBinding;
public class GroupListItem extends RecyclerView.ViewHolder {
private MenuItemBinding binding;
public GroupListItem(@NonNull MenuItemBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
public MenuItemBinding getBinding() {
return binding;
}
}

View File

@ -68,6 +68,7 @@ public class OTPListAdapter extends RecyclerView.Adapter<OTPListItem> {
public void remove(OTPData data) { public void remove(OTPData data) {
int index = items.indexOf(data); int index = items.indexOf(data);
if(index == -1) return;
items.remove(data); items.remove(data);
notifyItemRemoved(index); notifyItemRemoved(index);
} }

View File

@ -3,28 +3,44 @@ package com.cringe_studios.cringe_authenticator.util;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import com.cringe_studios.cringe_authenticator.R; import com.cringe_studios.cringe_authenticator.R;
import com.cringe_studios.cringe_authenticator.fragment.NamedFragment;
import kotlin.Suppress;
public class NavigationUtil { public class NavigationUtil {
public static void navigate(AppCompatActivity activity, Class<? extends Fragment> fragmentClass, Bundle args) { public static void navigate(AppCompatActivity activity, Class<? extends NamedFragment> fragmentClass, Bundle args) {
activity.getSupportActionBar().setTitle(fragmentClass.getSimpleName()); FragmentManager manager = activity.getSupportFragmentManager().getPrimaryNavigationFragment().getChildFragmentManager();
navigate(activity.getSupportFragmentManager().getPrimaryNavigationFragment().getChildFragmentManager(), fragmentClass, args); NamedFragment fragment = instantiateFragment(manager, fragmentClass, args);
ActionBar bar = activity.getSupportActionBar();
navigate(manager, fragment, () -> {
if(bar != null) bar.setTitle(fragment.getName());
});
} }
public static void navigate(Fragment currentFragment, Class<? extends Fragment> fragmentClass, Bundle args) { public static void navigate(Fragment currentFragment, Class<? extends NamedFragment> fragmentClass, Bundle args) {
((AppCompatActivity) currentFragment.getActivity()).getSupportActionBar().setTitle(fragmentClass.getSimpleName()); navigate((AppCompatActivity) currentFragment.requireActivity(), fragmentClass, args);
navigate(currentFragment.getParentFragment().getChildFragmentManager(), fragmentClass, args);
} }
private static void navigate(FragmentManager manager, Class<? extends Fragment> fragmentClass, Bundle args) { @SuppressWarnings("unchecked")
private static <T extends Fragment> T instantiateFragment(FragmentManager manager, Class<? extends T> fragmentClass, Bundle args) {
T fragment = (T) manager.getFragmentFactory().instantiate(ClassLoader.getSystemClassLoader(), fragmentClass.getName());
if(args != null) fragment.setArguments(args);
return fragment;
}
private static void navigate(FragmentManager manager, Fragment fragment, Runnable onCommit) {
manager.beginTransaction() manager.beginTransaction()
.setReorderingAllowed(true) .setReorderingAllowed(true)
.replace(R.id.nav_host_fragment_content_main, fragmentClass, args) .replace(R.id.nav_host_fragment_content_main, fragment)
.runOnCommit(onCommit)
.commit(); .commit();
} }

View File

@ -41,21 +41,49 @@ public class SettingsUtil {
private static final Gson GSON = new Gson(); private static final Gson GSON = new Gson();
// TODO: refactor public static List<String> getGroups(Context ctx) {
public static List<OTPData> getOTPs(SharedPreferences prefs, String group) { SharedPreferences prefs = ctx.getSharedPreferences(GROUPS_PREFS_NAME, Context.MODE_PRIVATE);
String currentOTPs = prefs.getString("group." + group, "[]"); return Arrays.asList(GSON.fromJson(prefs.getString("groups", "[]"), String[].class));
}
public static void addGroup(Context ctx, String group) {
List<String> groups = new ArrayList<>(getGroups(ctx));
groups.add(group);
SharedPreferences prefs = ctx.getSharedPreferences(GROUPS_PREFS_NAME, Context.MODE_PRIVATE);
prefs.edit().putString("groups", GSON.toJson(groups)).apply();
}
public static void removeGroup(Context ctx, String group) {
List<String> groups = new ArrayList<>(getGroups(ctx));
groups.remove(group);
SharedPreferences prefs = ctx.getSharedPreferences(GROUPS_PREFS_NAME, Context.MODE_PRIVATE);
prefs.edit().putString("groups", GSON.toJson(groups)).apply();
deleteOTPs(ctx, group);
}
public static List<OTPData> getOTPs(Context ctx, String group) {
String currentOTPs = ctx.getSharedPreferences(GROUPS_PREFS_NAME, Context.MODE_PRIVATE).getString("group." + group, "[]");
return Arrays.asList(GSON.fromJson(currentOTPs, OTPData[].class)); return Arrays.asList(GSON.fromJson(currentOTPs, OTPData[].class));
} }
public static void addOTP(SharedPreferences prefs, String group, @NonNull OTPData data) { public static void addOTP(Context ctx, String group, @NonNull OTPData data) {
List<OTPData> otps = new ArrayList<>(getOTPs(prefs, group)); List<OTPData> otps = new ArrayList<>(getOTPs(ctx, group));
otps.add(data); otps.add(data);
prefs.edit() ctx.getSharedPreferences(GROUPS_PREFS_NAME, Context.MODE_PRIVATE).edit()
.putString("group." + group, GSON.toJson(otps.toArray(new OTPData[0]))) .putString("group." + group, GSON.toJson(otps.toArray(new OTPData[0])))
.apply(); .apply();
} }
private static void deleteOTPs(Context ctx, String group) {
ctx.getSharedPreferences(GROUPS_PREFS_NAME, Context.MODE_PRIVATE).edit()
.remove("group." + group)
.apply();
}
public static void setEnableIntroVideo(Context ctx, boolean enableIntroVideo) { public static void setEnableIntroVideo(Context ctx, boolean enableIntroVideo) {
SharedPreferences prefs = ctx.getSharedPreferences(GENERAL_PREFS_NAME, Context.MODE_PRIVATE); SharedPreferences prefs = ctx.getSharedPreferences(GENERAL_PREFS_NAME, Context.MODE_PRIVATE);
prefs.edit().putBoolean("enableIntroVideo", enableIntroVideo).apply(); prefs.edit().putBoolean("enableIntroVideo", enableIntroVideo).apply();

View File

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="10dp">
<EditText
android:id="@+id/input_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="text"
android:hint="Name"
android:autofillHints="" />
<EditText
android:id="@+id/input_secret"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="text"
android:hint="Secret"
android:autofillHints="" />
<Spinner
android:id="@+id/input_algorithm"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="10dp"
android:paddingBottom="10dp" />
<Spinner
android:id="@+id/input_digits"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="10dp"
android:paddingBottom="10dp" />
<EditText
android:id="@+id/input_counter"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="number"
android:hint="Counter"
android:autofillHints="" />
<CheckBox
android:id="@+id/input_checksum"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Add Checksum" />
</LinearLayout>

View File

@ -11,7 +11,8 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:ems="10" android:ems="10"
android:inputType="text" android:inputType="text"
android:hint="Name" /> android:hint="Name"
android:autofillHints="" />
<EditText <EditText
android:id="@+id/input_secret" android:id="@+id/input_secret"
@ -19,7 +20,8 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:ems="10" android:ems="10"
android:inputType="text" android:inputType="text"
android:hint="Secret" /> android:hint="Secret"
android:autofillHints="" />
<Spinner <Spinner
android:id="@+id/input_algorithm" android:id="@+id/input_algorithm"
@ -44,4 +46,10 @@
android:hint="Period" android:hint="Period"
android:autofillHints="" /> android:autofillHints="" />
<CheckBox
android:id="@+id/input_checksum"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Add Checksum" />
</LinearLayout> </LinearLayout>

View File

@ -11,7 +11,7 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:padding="16dp"> android:padding="16dp">
<LinearLayout <!--<LinearLayout
android:id="@+id/menuItems" android:id="@+id/menuItems"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="match_parent"
@ -25,7 +25,16 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Edit" /> android:text="Edit" />
</LinearLayout> </LinearLayout>-->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/menu_items"
android:layout_width="0dp"
android:layout_height="match_parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:orientation="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>

View File

@ -11,6 +11,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Button" android:text="Button"
android:background="@drawable/button_themed" /> android:background="@drawable/button_themed"
android:textAllCaps="false" />
</LinearLayout> </LinearLayout>