Pick icon from pack

This commit is contained in:
MrLetsplay 2023-10-01 12:56:49 +02:00
parent 0352d9c777
commit c100cd3cfb
Signed by: mr
SSH Key Fingerprint: SHA256:92jBH80vpXyaZHjaIl47pjRq+Yt7XGTArqQg1V7hSqg
11 changed files with 297 additions and 17 deletions

View File

@ -146,7 +146,7 @@ public class MainActivity extends BaseActivity {
try {
if(doc == null) return;
IconPackMetadata meta = IconUtil.importIconPack(this, doc);
IconPackMetadata meta = IconUtil.importIconPack(this, doc); // TODO: check if pack contains icons
DialogUtil.showErrorDialog(this, "Icon pack contains " + meta.getIcons().length + " icons");
} catch (IconPackException e) {
DialogUtil.showErrorDialog(this, "Failed to import icon pack", e);

View File

@ -78,14 +78,13 @@ public class EditOTPFragment extends NamedFragment {
binding.getRoot().setBackgroundResource(bg);
}
IconUtil.loadEffectiveImage(requireContext(), data, binding.inputImage, null);
binding.inputImage.setOnClickListener(v -> {
new StyledDialogBuilder(requireContext())
.setTitle("Choose Image")
.setItems(new String[]{"Image from icon pack", "Image from gallery", "No image", "Reset to default image"}, (d, which) -> {
switch(which) {
case 0:
// TODO: pick from icon pack
pickImageFromIconPack();
break;
case 1:
pickGalleryImage();
@ -143,6 +142,7 @@ public class EditOTPFragment extends NamedFragment {
@Override
public void afterTextChanged(Editable s) {
if(imageData != null && !imageData.equals(OTPData.IMAGE_DATA_NONE)) return;
updateImage();
}
};
@ -179,14 +179,23 @@ public class EditOTPFragment extends NamedFragment {
}
}
updateImage();
return binding.getRoot();
}
private void updateImage() {
if(imageData != null && !imageData.equals(OTPData.IMAGE_DATA_NONE)) return;
IconUtil.loadEffectiveImage(requireContext(), imageData, binding.inputIssuer.getText().toString(), binding.inputName.getText().toString(), binding.inputImage, null);
}
private void pickImageFromIconPack() {
// TODO: check if icon packs installed
new PickIconDrawerFragment(icon -> {
imageData = Base64.encodeToString(icon.getBytes(), Base64.DEFAULT);
updateImage();
}).show(requireActivity().getSupportFragmentManager(), null);
}
private void pickGalleryImage() {
((MainActivity) requireActivity()).promptPickIconImage(uri -> {
if(uri == null) return;

View File

@ -0,0 +1,59 @@
package com.cringe_studios.cringe_authenticator.fragment;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.util.Consumer;
import com.cringe_studios.cringe_authenticator.databinding.FragmentPickIconBinding;
import com.cringe_studios.cringe_authenticator.icon.Icon;
import com.cringe_studios.cringe_authenticator.icon.IconListAdapter;
import com.cringe_studios.cringe_authenticator.icon.IconUtil;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
public class PickIconDrawerFragment extends BottomSheetDialogFragment {
private FragmentPickIconBinding binding;
private Consumer<Icon> selected;
public PickIconDrawerFragment(Consumer<Icon> selected) {
this.selected = selected;
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
binding = FragmentPickIconBinding.inflate(inflater);
IconListAdapter adapter = new IconListAdapter(requireContext(), IconUtil.loadAllIcons(requireContext()), icon -> {
selected.accept(icon);
getParentFragmentManager().beginTransaction().remove(this).commit();
});
binding.pickIconSearch.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
adapter.filter(s.toString());
}
@Override
public void afterTextChanged(Editable s) {}
});
binding.pickIconList.setAdapter(adapter);
binding.pickIconList.setOnChildClickListener(adapter);
return binding.getRoot();
}
}

View File

@ -0,0 +1,121 @@
package com.cringe_studios.cringe_authenticator.icon;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseExpandableListAdapter;
import android.widget.ExpandableListView;
import androidx.core.util.Consumer;
import com.cringe_studios.cringe_authenticator.R;
import com.cringe_studios.cringe_authenticator.databinding.IconListCategoryBinding;
import com.cringe_studios.cringe_authenticator.databinding.IconListIconBinding;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
public class IconListAdapter extends BaseExpandableListAdapter implements ExpandableListView.OnChildClickListener {
private Context context;
private Map<String, List<Icon>> icons;
private List<String> categories;
private Map<String, List<Icon>> filteredIcons;
private Consumer<Icon> selected;
public IconListAdapter(Context context, Map<String, List<Icon>> icons, Consumer<Icon> selected) {
this.context = context;
this.icons = icons;
this.categories = new ArrayList<>(icons.keySet());
this.filteredIcons = new TreeMap<>(icons);
this.selected = selected;
}
public void filter(String query) {
Map<String, List<Icon>> filtered = new TreeMap<>();
for(String cat : categories) {
List<Icon> f = new ArrayList<>();
for(Icon i : icons.get(cat)) {
if(i.getMetadata().getName().toLowerCase().contains(query.toLowerCase())) {
f.add(i);
}
}
filtered.put(cat, f);
}
filteredIcons = filtered;
notifyDataSetChanged();
}
@Override
public int getGroupCount() {
return categories.size();
}
@Override
public int getChildrenCount(int groupPosition) {
return filteredIcons.get(categories.get(groupPosition)).size();
}
@Override
public String getGroup(int groupPosition) {
return categories.get(groupPosition);
}
@Override
public Icon getChild(int groupPosition, int childPosition) {
return filteredIcons.get(categories.get(groupPosition)).get(childPosition);
}
@Override
public long getGroupId(int groupPosition) {
return groupPosition;
}
@Override
public long getChildId(int groupPosition, int childPosition) {
return childPosition;
}
@Override
public boolean hasStableIds() {
return false;
}
@Override
public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
IconListCategoryBinding binding = IconListCategoryBinding.inflate(LayoutInflater.from(context));
binding.getRoot().setText((String) getGroup(groupPosition));
return binding.getRoot();
}
@Override
public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
IconListIconBinding binding = IconListIconBinding.inflate(LayoutInflater.from(context));
Icon icon = getChild(groupPosition, childPosition);
binding.iconListIconImage.setImageResource(R.drawable.cringeauth_white);
IconUtil.loadImage(binding.iconListIconImage, icon.getBytes(), v -> v.setImageDrawable(new ColorDrawable(Color.TRANSPARENT)));
binding.iconListIconText.setText(icon.getMetadata().getName());
return binding.getRoot();
}
@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return true;
}
@Override
public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) {
selected.accept(getChild(groupPosition, childPosition));
return true;
}
}

View File

@ -1,13 +1,24 @@
package com.cringe_studios.cringe_authenticator.icon;
import java.io.File;
public class IconMetadata {
private String name;
private String filename;
private String category;
private String[] issuer;
private IconMetadata() {}
public String getName() {
if(name != null) return name;
String fileName = new File(filename).getName();
int i = fileName.lastIndexOf('.');
return i == -1 ? fileName : fileName.substring(0, i);
}
public String getFilename() {
return filename;
}

View File

@ -21,7 +21,7 @@ public class IconPack {
public Icon findIconForIssuer(String issuer) {
for(Icon icon : icons) {
for(String i : icon.getMetadata().getIssuer()) {
if(issuer.equals(i)) {
if(issuer.equalsIgnoreCase(i)) {
return icon;
}
}

View File

@ -35,6 +35,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
@ -121,6 +122,26 @@ public class IconUtil {
throw new IconPackException("No pack.json");
}
public static Map<String, List<Icon>> loadAllIcons(Context context) {
List<IconPack> packs = loadAllIconPacks(context);
Map<String, List<Icon>> icons = new TreeMap<>();
for(IconPack pack : packs) {
for(Icon i : pack.getIcons()) {
String category = i.getMetadata().getCategory();
if(icons.containsKey(category)) {
icons.get(category).add(i);
}else {
List<Icon> is = new ArrayList<>();
is.add(i);
icons.put(category, is);
}
}
}
return icons;
}
public static List<IconPack> loadAllIconPacks(Context context) {
File iconPacksDir = getIconPacksDir(context);
@ -250,19 +271,24 @@ public class IconUtil {
}
}
loadImage(view, imageBytes, v -> v.setImageBitmap(IconUtil.generateCodeImage(issuer, name)));
}
public static void loadImage(SVGImageView view, byte[] imageBytes, Consumer<SVGImageView> fallback) {
if(imageBytes == null) {
view.setImageBitmap(IconUtil.generateCodeImage(issuer, name));
if(fallback != null) fallback.accept(view);
return;
}
Bitmap bm = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length);
if(bm != null) {
view.setImageBitmap(bm);
}else {
Bitmap bm = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length);
if(bm != null) {
view.setImageBitmap(bm);
}else {
try {
SVG svg = SVG.getFromInputStream(new ByteArrayInputStream(imageBytes));
view.setSVG(svg);
}catch(SVGParseException e) {
view.setImageBitmap(IconUtil.generateCodeImage(issuer, name));
}
try {
SVG svg = SVG.getFromInputStream(new ByteArrayInputStream(imageBytes));
view.setSVG(svg);
}catch(SVGParseException e) {
if(fallback != null) fallback.accept(view);
}
}
}

View File

@ -4,7 +4,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragment.HomeFragment">
tools:context=".fragment.MenuDrawerFragment">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<EditText
android:id="@+id/pick_icon_search"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autofillHints=""
android:hint="Search" />
<ExpandableListView
android:id="@+id/pick_icon_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:nestedScrollingEnabled="true" />
</LinearLayout>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingVertical="16dp"
android:paddingLeft="?android:attr/expandableListPreferredItemPaddingLeft"
tools:text="This is a category">
</TextView>

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingVertical="5dp"
android:paddingStart="?android:attr/expandableListPreferredChildPaddingLeft">
<com.caverock.androidsvg.SVGImageView
android:id="@+id/icon_list_icon_image"
android:layout_width="50dp"
android:layout_height="50dp"
android:src="@drawable/baseline_edit_24"
android:layout_marginEnd="10dp"/>
<TextView
android:id="@+id/icon_list_icon_text"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="start|center"
tools:text="This is an icon">
</TextView>
</LinearLayout>