Pick icon from pack
This commit is contained in:
parent
0352d9c777
commit
c100cd3cfb
@ -146,7 +146,7 @@ public class MainActivity extends BaseActivity {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if(doc == null) return;
|
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");
|
DialogUtil.showErrorDialog(this, "Icon pack contains " + meta.getIcons().length + " icons");
|
||||||
} catch (IconPackException e) {
|
} catch (IconPackException e) {
|
||||||
DialogUtil.showErrorDialog(this, "Failed to import icon pack", e);
|
DialogUtil.showErrorDialog(this, "Failed to import icon pack", e);
|
||||||
|
@ -78,14 +78,13 @@ public class EditOTPFragment extends NamedFragment {
|
|||||||
binding.getRoot().setBackgroundResource(bg);
|
binding.getRoot().setBackgroundResource(bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
IconUtil.loadEffectiveImage(requireContext(), data, binding.inputImage, null);
|
|
||||||
binding.inputImage.setOnClickListener(v -> {
|
binding.inputImage.setOnClickListener(v -> {
|
||||||
new StyledDialogBuilder(requireContext())
|
new StyledDialogBuilder(requireContext())
|
||||||
.setTitle("Choose Image")
|
.setTitle("Choose Image")
|
||||||
.setItems(new String[]{"Image from icon pack", "Image from gallery", "No image", "Reset to default image"}, (d, which) -> {
|
.setItems(new String[]{"Image from icon pack", "Image from gallery", "No image", "Reset to default image"}, (d, which) -> {
|
||||||
switch(which) {
|
switch(which) {
|
||||||
case 0:
|
case 0:
|
||||||
// TODO: pick from icon pack
|
pickImageFromIconPack();
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
pickGalleryImage();
|
pickGalleryImage();
|
||||||
@ -143,6 +142,7 @@ public class EditOTPFragment extends NamedFragment {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterTextChanged(Editable s) {
|
public void afterTextChanged(Editable s) {
|
||||||
|
if(imageData != null && !imageData.equals(OTPData.IMAGE_DATA_NONE)) return;
|
||||||
updateImage();
|
updateImage();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -179,14 +179,23 @@ public class EditOTPFragment extends NamedFragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateImage();
|
||||||
|
|
||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateImage() {
|
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);
|
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() {
|
private void pickGalleryImage() {
|
||||||
((MainActivity) requireActivity()).promptPickIconImage(uri -> {
|
((MainActivity) requireActivity()).promptPickIconImage(uri -> {
|
||||||
if(uri == null) return;
|
if(uri == null) return;
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,24 @@
|
|||||||
package com.cringe_studios.cringe_authenticator.icon;
|
package com.cringe_studios.cringe_authenticator.icon;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
public class IconMetadata {
|
public class IconMetadata {
|
||||||
|
|
||||||
|
private String name;
|
||||||
private String filename;
|
private String filename;
|
||||||
private String category;
|
private String category;
|
||||||
private String[] issuer;
|
private String[] issuer;
|
||||||
|
|
||||||
private IconMetadata() {}
|
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() {
|
public String getFilename() {
|
||||||
return filename;
|
return filename;
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ public class IconPack {
|
|||||||
public Icon findIconForIssuer(String issuer) {
|
public Icon findIconForIssuer(String issuer) {
|
||||||
for(Icon icon : icons) {
|
for(Icon icon : icons) {
|
||||||
for(String i : icon.getMetadata().getIssuer()) {
|
for(String i : icon.getMetadata().getIssuer()) {
|
||||||
if(issuer.equals(i)) {
|
if(issuer.equalsIgnoreCase(i)) {
|
||||||
return icon;
|
return icon;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,7 @@ import java.util.Collections;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.TreeMap;
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
import java.util.zip.ZipInputStream;
|
import java.util.zip.ZipInputStream;
|
||||||
|
|
||||||
@ -121,6 +122,26 @@ public class IconUtil {
|
|||||||
throw new IconPackException("No pack.json");
|
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) {
|
public static List<IconPack> loadAllIconPacks(Context context) {
|
||||||
File iconPacksDir = getIconPacksDir(context);
|
File iconPacksDir = getIconPacksDir(context);
|
||||||
|
|
||||||
@ -250,9 +271,15 @@ 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) {
|
if(imageBytes == null) {
|
||||||
view.setImageBitmap(IconUtil.generateCodeImage(issuer, name));
|
if(fallback != null) fallback.accept(view);
|
||||||
}else {
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Bitmap bm = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length);
|
Bitmap bm = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length);
|
||||||
if(bm != null) {
|
if(bm != null) {
|
||||||
view.setImageBitmap(bm);
|
view.setImageBitmap(bm);
|
||||||
@ -261,8 +288,7 @@ public class IconUtil {
|
|||||||
SVG svg = SVG.getFromInputStream(new ByteArrayInputStream(imageBytes));
|
SVG svg = SVG.getFromInputStream(new ByteArrayInputStream(imageBytes));
|
||||||
view.setSVG(svg);
|
view.setSVG(svg);
|
||||||
}catch(SVGParseException e) {
|
}catch(SVGParseException e) {
|
||||||
view.setImageBitmap(IconUtil.generateCodeImage(issuer, name));
|
if(fallback != null) fallback.accept(view);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:context=".fragment.HomeFragment">
|
tools:context=".fragment.MenuDrawerFragment">
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
23
app/src/main/res/layout/fragment_pick_icon.xml
Normal file
23
app/src/main/res/layout/fragment_pick_icon.xml
Normal 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>
|
9
app/src/main/res/layout/icon_list_category.xml
Normal file
9
app/src/main/res/layout/icon_list_category.xml
Normal 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>
|
22
app/src/main/res/layout/icon_list_icon.xml
Normal file
22
app/src/main/res/layout/icon_list_icon.xml
Normal 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>
|
Loading…
Reference in New Issue
Block a user