Kotlinized icons

This commit is contained in:
J-Jamet
2019-07-19 16:03:40 +02:00
parent 4b659248f7
commit 59c45f5627
15 changed files with 562 additions and 672 deletions

View File

@@ -42,6 +42,7 @@ import com.kunzisoft.keepass.view.EntryContentsView
import com.kunzisoft.keepass.app.App import com.kunzisoft.keepass.app.App
import com.kunzisoft.keepass.database.element.EntryVersioned import com.kunzisoft.keepass.database.element.EntryVersioned
import com.kunzisoft.keepass.database.element.PwNodeId import com.kunzisoft.keepass.database.element.PwNodeId
import com.kunzisoft.keepass.icons.assignDatabaseIcon
import com.kunzisoft.keepass.notifications.NotificationEntryCopyManager import com.kunzisoft.keepass.notifications.NotificationEntryCopyManager
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.settings.PreferencesUtil.isFirstTimeAskAllowCopyPasswordAndProtectedFields import com.kunzisoft.keepass.settings.PreferencesUtil.isFirstTimeAskAllowCopyPasswordAndProtectedFields
@@ -140,7 +141,7 @@ class EntryActivity : LockingHideActivity() {
val database = App.currentDatabase val database = App.currentDatabase
database.startManageEntry(entry) database.startManageEntry(entry)
// Assign title icon // Assign title icon
database.drawFactory.assignDatabaseIconTo(this, titleIconView, entry.icon, iconColor) titleIconView?.assignDatabaseIcon(database.drawFactory, entry.icon, iconColor)
// Assign title text // Assign title text
titleView?.text = entry.getVisualTitle() titleView?.text = entry.getVisualTitle()

View File

@@ -35,7 +35,6 @@ import com.kunzisoft.keepass.activities.dialogs.GeneratePasswordDialogFragment
import com.kunzisoft.keepass.activities.dialogs.IconPickerDialogFragment import com.kunzisoft.keepass.activities.dialogs.IconPickerDialogFragment
import com.kunzisoft.keepass.activities.dialogs.IconPickerDialogFragment.Companion.KEY_ICON_STANDARD import com.kunzisoft.keepass.activities.dialogs.IconPickerDialogFragment.Companion.KEY_ICON_STANDARD
import com.kunzisoft.keepass.activities.lock.LockingHideActivity import com.kunzisoft.keepass.activities.lock.LockingHideActivity
import com.kunzisoft.keepass.view.EntryEditCustomField
import com.kunzisoft.keepass.app.App import com.kunzisoft.keepass.app.App
import com.kunzisoft.keepass.database.action.ProgressDialogSaveDatabaseThread import com.kunzisoft.keepass.database.action.ProgressDialogSaveDatabaseThread
import com.kunzisoft.keepass.database.action.node.ActionNodeValues import com.kunzisoft.keepass.database.action.node.ActionNodeValues
@@ -45,11 +44,14 @@ import com.kunzisoft.keepass.database.action.node.UpdateEntryRunnable
import com.kunzisoft.keepass.database.element.* import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.element.security.ProtectedString import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.education.EntryEditActivityEducation import com.kunzisoft.keepass.education.EntryEditActivityEducation
import com.kunzisoft.keepass.icons.assignDatabaseIcon
import com.kunzisoft.keepass.icons.assignDefaultDatabaseIcon
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.timeout.TimeoutHelper import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.MenuUtil import com.kunzisoft.keepass.utils.MenuUtil
import com.kunzisoft.keepass.utils.Util import com.kunzisoft.keepass.utils.Util
import com.kunzisoft.keepass.view.EntryEditCustomField
class EntryEditActivity : LockingHideActivity(), IconPickerDialogFragment.IconPickerListener, GeneratePasswordDialogFragment.GeneratePasswordListener { class EntryEditActivity : LockingHideActivity(), IconPickerDialogFragment.IconPickerListener, GeneratePasswordDialogFragment.GeneratePasswordListener {
@@ -140,7 +142,9 @@ class EntryEditActivity : LockingHideActivity(), IconPickerDialogFragment.IconPi
mEntry = mDatabase?.createEntry() mEntry = mDatabase?.createEntry()
mParent = mDatabase?.getGroupById(it) mParent = mDatabase?.getGroupById(it)
// Add the default icon // Add the default icon
mDatabase?.drawFactory?.assignDefaultDatabaseIconTo(this, entryIconView, iconColor) mDatabase?.drawFactory?.let { iconFactory ->
entryIconView?.assignDefaultDatabaseIcon(iconFactory, iconColor)
}
} }
// Close the activity if entry or parent can't be retrieve // Close the activity if entry or parent can't be retrieve
@@ -392,12 +396,8 @@ class EntryEditActivity : LockingHideActivity(), IconPickerDialogFragment.IconPi
} }
private fun assignIconView() { private fun assignIconView() {
mEntry?.icon?.let { if (mDatabase?.drawFactory != null && mEntry?.icon != null) {
mDatabase?.drawFactory?.assignDatabaseIconTo( entryIconView?.assignDatabaseIcon(mDatabase?.drawFactory!!, mEntry?.icon!!, iconColor)
this,
entryIconView,
it,
iconColor)
} }
} }

View File

@@ -58,6 +58,7 @@ import com.kunzisoft.keepass.database.action.ProgressDialogSaveDatabaseThread
import com.kunzisoft.keepass.database.action.node.* import com.kunzisoft.keepass.database.action.node.*
import com.kunzisoft.keepass.database.element.* import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.education.GroupActivityEducation import com.kunzisoft.keepass.education.GroupActivityEducation
import com.kunzisoft.keepass.icons.assignDatabaseIcon
import com.kunzisoft.keepass.magikeyboard.KeyboardEntryNotificationService import com.kunzisoft.keepass.magikeyboard.KeyboardEntryNotificationService
import com.kunzisoft.keepass.magikeyboard.KeyboardHelper import com.kunzisoft.keepass.magikeyboard.KeyboardHelper
import com.kunzisoft.keepass.magikeyboard.MagikIME import com.kunzisoft.keepass.magikeyboard.MagikIME
@@ -347,7 +348,8 @@ class GroupActivity : LockingActivity(),
// Assign the group icon depending of IconPack or custom icon // Assign the group icon depending of IconPack or custom icon
iconView?.visibility = View.VISIBLE iconView?.visibility = View.VISIBLE
mCurrentGroup?.let { mCurrentGroup?.let {
mDatabase?.drawFactory?.assignDatabaseIconTo(this, iconView, it.icon, mIconColor) if (mDatabase?.drawFactory != null)
iconView?.assignDatabaseIcon(mDatabase?.drawFactory!!, it.icon, mIconColor)
if (toolbar != null) { if (toolbar != null) {
if (mCurrentGroup?.containsParent() == true) if (mCurrentGroup?.containsParent() == true)

View File

@@ -34,6 +34,7 @@ import com.kunzisoft.keepass.app.App
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.GroupVersioned import com.kunzisoft.keepass.database.element.GroupVersioned
import com.kunzisoft.keepass.database.element.PwIcon import com.kunzisoft.keepass.database.element.PwIcon
import com.kunzisoft.keepass.icons.assignDatabaseIcon
class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconPickerListener { class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconPickerListener {
@@ -45,7 +46,7 @@ class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconP
private var nameGroup: String? = null private var nameGroup: String? = null
private var iconGroup: PwIcon? = null private var iconGroup: PwIcon? = null
private var iconButton: ImageView? = null private var iconButtonView: ImageView? = null
private var iconColor: Int = 0 private var iconColor: Int = 0
enum class EditGroupDialogAction { enum class EditGroupDialogAction {
@@ -76,7 +77,7 @@ class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconP
activity?.let { activity -> activity?.let { activity ->
val root = activity.layoutInflater.inflate(R.layout.group_edit, null) val root = activity.layoutInflater.inflate(R.layout.group_edit, null)
val nameField = root?.findViewById<TextView>(R.id.group_edit_name) val nameField = root?.findViewById<TextView>(R.id.group_edit_name)
iconButton = root?.findViewById(R.id.group_edit_icon_button) iconButtonView = root?.findViewById(R.id.group_edit_icon_button)
// Retrieve the textColor to tint the icon // Retrieve the textColor to tint the icon
val ta = activity.theme.obtainStyledAttributes(intArrayOf(android.R.attr.textColorPrimary)) val ta = activity.theme.obtainStyledAttributes(intArrayOf(android.R.attr.textColorPrimary))
@@ -133,7 +134,7 @@ class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconP
this@GroupEditDialogFragment.dialog.cancel() this@GroupEditDialogFragment.dialog.cancel()
} }
iconButton?.setOnClickListener { _ -> iconButtonView?.setOnClickListener { _ ->
fragmentManager?.let { fragmentManager?.let {
IconPickerDialogFragment().show(it, "IconPickerDialogFragment") IconPickerDialogFragment().show(it, "IconPickerDialogFragment")
} }
@@ -145,12 +146,9 @@ class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconP
} }
private fun assignIconView() { private fun assignIconView() {
mDatabase?.drawFactory if (mDatabase?.drawFactory != null && iconGroup != null) {
?.assignDatabaseIconTo( iconButtonView?.assignDatabaseIcon(mDatabase?.drawFactory!!, iconGroup!!, iconColor)
context, }
iconButton,
iconGroup,
iconColor)
} }
override fun iconPicked(bundle: Bundle) { override fun iconPicked(bundle: Bundle) {

View File

@@ -61,7 +61,7 @@ class IconPickerDialogFragment : DialogFragment() {
activity?.let { activity -> activity?.let { activity ->
val builder = AlertDialog.Builder(activity) val builder = AlertDialog.Builder(activity)
iconPack = IconPackChooser.getSelectedIconPack(context) iconPack = IconPackChooser.getSelectedIconPack(context!!)
// Inflate and set the layout for the dialog // Inflate and set the layout for the dialog
// Pass null as the parent view because its going in the dialog layout // Pass null as the parent view because its going in the dialog layout

View File

@@ -33,6 +33,7 @@ import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.app.App import com.kunzisoft.keepass.app.App
import com.kunzisoft.keepass.database.SortNodeEnum import com.kunzisoft.keepass.database.SortNodeEnum
import com.kunzisoft.keepass.database.element.* import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.icons.assignDatabaseIcon
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.utils.Util import com.kunzisoft.keepass.utils.Util
@@ -207,7 +208,7 @@ class NodeAdapter
Type.GROUP -> iconGroupColor Type.GROUP -> iconGroupColor
Type.ENTRY -> iconEntryColor Type.ENTRY -> iconEntryColor
} }
mDatabase.drawFactory.assignDatabaseIconTo(context, holder.icon, subNode.icon, iconColor) holder.icon?.assignDatabaseIcon(mDatabase.drawFactory, subNode.icon, iconColor)
// Assign text // Assign text
holder.text?.text = subNode.title holder.text?.text = subNode.title
// Assign click // Assign click

View File

@@ -33,6 +33,7 @@ import com.kunzisoft.keepass.database.cursor.EntryCursor
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.EntryVersioned import com.kunzisoft.keepass.database.element.EntryVersioned
import com.kunzisoft.keepass.database.element.PwIcon import com.kunzisoft.keepass.database.element.PwIcon
import com.kunzisoft.keepass.icons.assignDatabaseIcon
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import java.util.* import java.util.*
@@ -89,7 +90,7 @@ class SearchEntryCursorAdapter(context: Context, private val database: Database)
val viewHolder = view.tag as ViewHolder val viewHolder = view.tag as ViewHolder
// Assign image // Assign image
database.drawFactory.assignDatabaseIconTo(context, viewHolder.imageViewIcon, icon, iconColor) viewHolder.imageViewIcon?.assignDatabaseIcon(database.drawFactory, icon, iconColor)
// Assign title // Assign title
val showTitle = EntryVersioned.getVisualTitle(false, title, username, url, uuid.toString()) val showTitle = EntryVersioned.getVisualTitle(false, title, username, url, uuid.toString())

View File

@@ -1,335 +0,0 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.icons;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.support.v4.content.ContextCompat;
import android.support.v4.widget.ImageViewCompat;
import android.util.Log;
import android.widget.ImageView;
import com.kunzisoft.keepass.R;
import com.kunzisoft.keepass.database.element.PwIcon;
import com.kunzisoft.keepass.database.element.PwIconCustom;
import com.kunzisoft.keepass.database.element.PwIconStandard;
import org.apache.commons.collections.map.AbstractReferenceMap;
import org.apache.commons.collections.map.ReferenceMap;
/**
* Factory class who build database icons dynamically, can assign an icon of IconPack, or a custom icon to an ImageView with a tint
*/
public class IconDrawableFactory {
private static final String TAG = IconDrawableFactory.class.getName();
private static Drawable blank = null;
private static int blankWidth = -1;
private static int blankHeight = -1;
/** customIconMap
* Cache for icon drawable.
* Keys: UUID, Values: Drawables
*/
private ReferenceMap customIconMap = new ReferenceMap(AbstractReferenceMap.HARD, AbstractReferenceMap.WEAK);
/** standardIconMap
* Cache for icon drawable.
* Keys: Integer, Values: Drawables
*/
private ReferenceMap standardIconMap = new ReferenceMap(AbstractReferenceMap.HARD, AbstractReferenceMap.WEAK);
/**
* Assign a default database icon to an ImageView and tint it if needed
*
* @param context Context to build the drawable
* @param iconView ImageView that will host the drawable
* @param tintColor Use this color to tint tintable icon
*/
public void assignDefaultDatabaseIconTo(Context context, ImageView iconView, int tintColor) {
if (IconPackChooser.getSelectedIconPack(context).tintable()) {
assignDrawableTo(context,
iconView,
IconPackChooser.getSelectedIconPack(context).getDefaultIconId(),
true,
tintColor);
} else {
assignDrawableTo(context,
iconView,
IconPackChooser.getSelectedIconPack(context).getDefaultIconId(),
false,
Color.WHITE);
}
}
/**
* Assign a database icon to an ImageView and tint it if needed
*
* @param context Context to build the drawable
* @param iconView ImageView that will host the drawable
* @param icon The icon from the database
* @param tintColor Use this color to tint tintable icon
*/
public void assignDatabaseIconTo(Context context, ImageView iconView, PwIcon icon, int tintColor) {
if (IconPackChooser.getSelectedIconPack(context).tintable()) {
assignDrawableToImageView(getIconDrawable(context, icon, true, tintColor),
iconView,
true,
tintColor);
} else {
assignDrawableToImageView(getIconDrawable(context, icon, true, tintColor),
iconView,
false,
Color.WHITE);
}
}
/**
* Assign an image by its resourceId to an ImageView and tint it
*
* @param context Context to build the drawable
* @param imageView ImageView that will host the drawable
* @param iconId iconId from the resources
* @param tint true will tint the drawable with tintColor
* @param tintColor Use this color if tint is true
*/
public void assignDrawableTo(Context context, ImageView imageView, int iconId, boolean tint, int tintColor) {
assignDrawableToImageView(new SuperDrawable(getIconDrawable(context, iconId, tint, tintColor)),
imageView,
tint,
tintColor);
}
/**
* Utility method to assign a drawable to an ImageView and tint it
*/
private void assignDrawableToImageView(SuperDrawable superDrawable, ImageView imageView, boolean tint, int tintColor) {
if (imageView != null && superDrawable.drawable != null) {
imageView.setImageDrawable(superDrawable.drawable);
if (!superDrawable.custom && tint) {
ImageViewCompat.setImageTintList(imageView, ColorStateList.valueOf(tintColor));
} else {
ImageViewCompat.setImageTintList(imageView, null);
}
}
}
/**
* Get the drawable icon from cache or build it and add it to the cache if not exists yet
*
* @param context Context to build the drawable
* @param icon The icon from database
* @return The build drawable
*/
public Drawable getIconDrawable(Context context, PwIcon icon) {
return getIconDrawable(context, icon, false, Color.WHITE).drawable;
}
/**
* Get the drawable icon from cache or build it and add it to the cache if not exists yet then tint it if needed
*
* @param context Context to build the drawable
* @param icon The icon from database
* @param tint true will tint the drawable with tintColor
* @param tintColor Use this color if tint is true
* @return The build drawable
*/
public SuperDrawable getIconDrawable(Context context, PwIcon icon, boolean tint, int tintColor) {
if (icon instanceof PwIconStandard) {
return new SuperDrawable(getIconDrawable(context.getApplicationContext(), (PwIconStandard) icon, tint, tintColor));
} else {
return new SuperDrawable(getIconDrawable(context, (PwIconCustom) icon), true);
}
}
/**
* Build a blank drawable
* @param res Resource to build the drawable
*/
private static void initBlank(Resources res) {
if (blank==null) {
blankWidth = (int) res.getDimension(R.dimen.icon_size);
blankHeight = (int) res.getDimension(R.dimen.icon_size);
blank = new ColorDrawable(Color.TRANSPARENT);
blank.setBounds(0, 0, blankWidth, blankHeight);
}
}
/**
* Key class to retrieve a Drawable in the cache if it's tinted or not
*/
private class CacheKey {
int resId;
boolean isTint;
int color;
CacheKey(int resId, boolean isTint, int color) {
this.resId = resId;
this.isTint = isTint;
this.color = color;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CacheKey cacheKey = (CacheKey) o;
if (isTint)
return resId == cacheKey.resId &&
cacheKey.isTint &&
color == cacheKey.color;
else
return resId == cacheKey.resId &&
!cacheKey.isTint;
}
}
/**
* Get the drawable icon from cache or build it and add it to the cache if not exists yet
*
* @param context Context to make drawable
* @param icon Icon from database
* @param isTint Tint the drawable if true
* @param tintColor Use this color if tint is true
* @return The drawable
*/
private Drawable getIconDrawable(Context context, PwIconStandard icon, boolean isTint, int tintColor) {
int resId = IconPackChooser.getSelectedIconPack(context).iconToResId(icon.getIconId());
return getIconDrawable(context, resId, isTint, tintColor);
}
/**
* Get the drawable icon from cache or build it and add it to the cache if not exists yet
*
* @param context Context to make drawable
* @param iconId iconId from resources
* @param isTint Tint the drawable if true
* @param tintColor Use this color if tint is true
* @return The drawable
*/
private Drawable getIconDrawable(Context context, int iconId, boolean isTint, int tintColor) {
CacheKey newCacheKey = new CacheKey(iconId, isTint, tintColor);
Drawable draw = (Drawable) standardIconMap.get(newCacheKey);
if (draw == null) {
try {
draw = ContextCompat.getDrawable(context, iconId);
} catch (Exception e) {
Log.e(TAG, "Can't get icon", e);
}
if (draw != null) {
standardIconMap.put(newCacheKey, draw);
}
}
if (draw == null) {
if (blank == null)
initBlank(context.getResources());
draw = blank;
}
return draw;
}
/**
* Utility class to prevent a custom icon to be tint
*/
private class SuperDrawable {
Drawable drawable;
boolean custom;
SuperDrawable(Drawable drawable) {
this.drawable = drawable;
this.custom = false;
}
SuperDrawable(Drawable drawable, boolean custom) {
this.drawable = drawable;
this.custom = custom;
}
}
/**
* Build a custom icon from database
* @param context Context to build the drawable
* @param icon Icon from database
* @return The drawable
*/
private Drawable getIconDrawable(Context context, PwIconCustom icon) {
initBlank(context.getResources());
if (icon == null) {
return blank;
}
Drawable draw = (Drawable) customIconMap.get(icon.getUuid());
if (draw == null) {
Bitmap bitmap = BitmapFactory.decodeByteArray(icon.getImageData(), 0, icon.getImageData().length);
// Could not understand custom icon
if (bitmap == null) {
return blank;
}
bitmap = resize(bitmap);
draw = new BitmapDrawable(context.getResources(), bitmap);
customIconMap.put(icon.getUuid(), draw);
}
return draw;
}
/**
* Resize the custom icon to match the built in icons
*
* @param bitmap Bitmap to resize
* @return Bitmap resized
*/
private Bitmap resize(Bitmap bitmap) {
int width = bitmap.getWidth();
int height = bitmap.getHeight();
if (width == blankWidth && height == blankHeight) {
return bitmap;
}
return Bitmap.createScaledBitmap(bitmap, blankWidth, blankHeight, true);
}
/**
* Clear the cache of icons
*/
public void clearCache() {
standardIconMap.clear();
customIconMap.clear();
}
}

View File

@@ -0,0 +1,258 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.icons
import android.content.Context
import android.content.res.ColorStateList
import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Color
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.support.v4.content.res.ResourcesCompat
import android.support.v4.widget.ImageViewCompat
import android.util.Log
import android.widget.ImageView
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.PwIcon
import com.kunzisoft.keepass.database.element.PwIconCustom
import com.kunzisoft.keepass.database.element.PwIconStandard
import org.apache.commons.collections.map.AbstractReferenceMap
import org.apache.commons.collections.map.ReferenceMap
/**
* Factory class who build database icons dynamically, can assign an icon of IconPack, or a custom icon to an ImageView with a tint
*/
class IconDrawableFactory {
/** customIconMap
* Cache for icon drawable.
* Keys: UUID, Values: Drawables
*/
private val customIconMap = ReferenceMap(AbstractReferenceMap.HARD, AbstractReferenceMap.WEAK)
/** standardIconMap
* Cache for icon drawable.
* Keys: Integer, Values: Drawables
*/
private val standardIconMap = ReferenceMap(AbstractReferenceMap.HARD, AbstractReferenceMap.WEAK)
/**
* Utility method to assign a drawable to an ImageView and tint it
*/
fun assignDrawableToImageView(superDrawable: SuperDrawable, imageView: ImageView?, tint: Boolean, tintColor: Int) {
if (imageView != null) {
imageView.setImageDrawable(superDrawable.drawable)
if (!superDrawable.custom && tint) {
ImageViewCompat.setImageTintList(imageView, ColorStateList.valueOf(tintColor))
} else {
ImageViewCompat.setImageTintList(imageView, null)
}
}
}
/**
* Get the [SuperDrawable] [icon] (from cache, or build it and add it to the cache if not exists yet), then [tint] it with [tintColor] if needed
*/
fun getIconSuperDrawable(context: Context, icon: PwIcon, tint: Boolean = false, tintColor: Int = Color.WHITE): SuperDrawable {
return when (icon) {
is PwIconStandard -> {
val resId = IconPackChooser.getSelectedIconPack(context)?.iconToResId(icon.iconId) ?: R.drawable.ic_blank_32dp
getIconSuperDrawable(context, resId, tint, tintColor)
}
is PwIconCustom -> {
SuperDrawable(getIconDrawable(context.resources, icon), true)
}
else -> {
SuperDrawable(PatternIcon(context.resources).blankDrawable)
}
}
}
/**
* Get the [SuperDrawable] PwIconStandard from [iconId] (cache, or build it and add it to the cache if not exists yet)
* , then [tint] it with [tintColor] if needed
*/
fun getIconSuperDrawable(context: Context, iconId: Int, tint: Boolean, tintColor: Int): SuperDrawable {
return SuperDrawable(getIconDrawable(context.resources, iconId, tint, tintColor))
}
/**
* Key class to retrieve a Drawable in the cache if it's tinted or not
*/
private inner class CacheKey internal constructor(internal var resId: Int, internal var isTint: Boolean, internal var color: Int) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || javaClass != other.javaClass) return false
val cacheKey = other as CacheKey
return if (isTint)
resId == cacheKey.resId &&
cacheKey.isTint &&
color == cacheKey.color
else
resId == cacheKey.resId && !cacheKey.isTint
}
override fun hashCode(): Int {
var result = resId
result = 31 * result + isTint.hashCode()
result = 31 * result + color
return result
}
}
/**
* Build a custom [Drawable] from custom [icon]
*/
private fun getIconDrawable(resources: Resources, icon: PwIconCustom): Drawable {
val patternIcon = PatternIcon(resources)
var draw: Drawable? = customIconMap[icon.uuid] as Drawable?
if (draw == null) {
var bitmap: Bitmap? = BitmapFactory.decodeByteArray(icon.imageData, 0, icon.imageData.size)
// Could not understand custom icon
bitmap?.let { bitmapIcon ->
bitmap = resize(bitmapIcon, patternIcon)
draw = BitmapDrawable(resources, bitmap)
customIconMap[icon.uuid] = draw
return draw!!
}
} else {
return draw!!
}
return patternIcon.blankDrawable
}
/**
* Get the standard [Drawable] icon from [iconId] (cache or build it and add it to the cache if not exists yet)
* , then [tint] it with [tintColor] if needed
*/
private fun getIconDrawable(resources: Resources, iconId: Int, tint: Boolean, tintColor: Int): Drawable {
val newCacheKey = CacheKey(iconId, tint, tintColor)
var draw: Drawable? = standardIconMap[newCacheKey] as Drawable?
if (draw == null) {
try {
draw = ResourcesCompat.getDrawable(resources, iconId, null)
} catch (e: Exception) {
Log.e(TAG, "Can't get icon", e)
}
if (draw != null) {
standardIconMap[newCacheKey] = draw
}
}
if (draw == null) {
draw = PatternIcon(resources).blankDrawable
}
return draw
}
/**
* Resize the custom icon to match the built in icons
*
* @param bitmap Bitmap to resize
* @return Bitmap resized
*/
private fun resize(bitmap: Bitmap, dimensionPattern: PatternIcon): Bitmap {
val width = bitmap.width
val height = bitmap.height
return if (width == dimensionPattern.width && height == dimensionPattern.height) {
bitmap
} else Bitmap.createScaledBitmap(bitmap, dimensionPattern.width, dimensionPattern.height, true)
}
/**
* Clear the cache of icons
*/
fun clearCache() {
standardIconMap.clear()
customIconMap.clear()
}
private class PatternIcon
/**
* Build a blankDrawable drawable
* @param res Resource to build the drawable
*/(res: Resources) {
var blankDrawable: Drawable = ColorDrawable(Color.TRANSPARENT)
var width = -1
var height = -1
init {
width = res.getDimension(R.dimen.icon_size).toInt()
height = res.getDimension(R.dimen.icon_size).toInt()
blankDrawable.setBounds(0, 0, width, height)
}
}
/**
* Utility class to prevent a custom icon to be tint
*/
class SuperDrawable(var drawable: Drawable, var custom: Boolean = false)
companion object {
private val TAG = IconDrawableFactory::class.java.name
}
}
/**
* Assign a default database icon to an ImageView and tint it with [tintColor] if needed
*/
fun ImageView.assignDefaultDatabaseIcon(iconFactory: IconDrawableFactory, tintColor: Int = Color.WHITE) {
IconPackChooser.getSelectedIconPack(context)?.let { selectedIconPack ->
iconFactory.assignDrawableToImageView(
iconFactory.getIconSuperDrawable(context,
selectedIconPack.defaultIconId,
selectedIconPack.tintable(),
tintColor),
this,
selectedIconPack.tintable(),
tintColor)
}
}
/**
* Assign a database [icon] to an ImageView and tint it with [tintColor] if needed
*/
fun ImageView.assignDatabaseIcon(iconFactory: IconDrawableFactory, icon: PwIcon, tintColor: Int = Color.WHITE) {
IconPackChooser.getSelectedIconPack(context)?.let { selectedIconPack ->
iconFactory.assignDrawableToImageView(
iconFactory.getIconSuperDrawable(context,
icon,
true,
tintColor),
this,
selectedIconPack.tintable(),
tintColor)
}
}

View File

@@ -1,159 +0,0 @@
/*
* Copyright 2018 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.icons;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.util.SparseIntArray;
import com.kunzisoft.keepass.R;
import java.text.DecimalFormat;
/**
* Class who construct dynamically database icons contains in a separate library
*
* <p>It only supports icons with specific nomenclature <strong>[stringId]_[%2d]_32dp</strong>
* where [stringId] contains in a string xml attribute with id <strong>resource_id</strong> and
* [%2d] 2 numerical numbers between 00 and 68 included,
* </p>
* <p>See <i>icon-pack-classic</i> module as sample
* </p>
*
*/
public class IconPack {
private static final int NB_ICONS = 68;
private SparseIntArray icons;
private String resourceStringId;
private String name;
private boolean tintable;
private Resources resources;
/**
* Construct dynamically the icon pack provide by the string resource id
*
* @param context Context of the app to retrieve the resources
* @param resourceId String Id of the pack (ex : com.kunzisoft.keepass.icon.classic.R.string.resource_id)
*/
IconPack(Context context, int resourceId) {
resources = context.getResources();
icons = new SparseIntArray();
resourceStringId = context.getString(resourceId);
// If finish with a _ remove it
if (resourceStringId.lastIndexOf('_') == resourceStringId.length() - 1)
resourceStringId = resourceStringId.substring(0, resourceStringId.length() -1);
// Build the list of icons
int num = 0;
while(num <= NB_ICONS) {
// To construct the id with name_ic_XX_32dp (ex : classic_ic_08_32dp )
int resId = resources.getIdentifier(
resourceStringId + "_" + new DecimalFormat("00").format(num) + "_32dp",
"drawable",
context.getPackageName());
icons.put(num, resId);
num++;
}
// Get visual name
name = resources.getString(
resources.getIdentifier(
resourceStringId + "_" + "name",
"string",
context.getPackageName()
)
);
// If icons are tintable
tintable = resources.getBoolean(
resources.getIdentifier(
resourceStringId + "_" + "tintable",
"bool",
context.getPackageName()
)
);
}
/**
* Get the name of the IconPack
*
* @return String visual name of the pack
*/
public String getName() {
return name;
}
/**
* Get the id of the IconPack
*
* @return String id of the pack
*/
public String getId() {
return resourceStringId;
}
/**
* Determine if each icon in the pack can be tint
*
* @return true if icons are tintable
*/
public boolean tintable() {
return tintable;
}
/**
* Get the number of icons in this pack
*
* @return int Number of database icons
*/
public int numberOfIcons() {
return icons.size();
}
/**
* Icon as a resourceId
*
* @param iconId Icon database Id of the icon to retrieve
* @return int resourceId
*/
public int iconToResId(int iconId) {
return icons.get(iconId, R.drawable.ic_blank_32dp);
}
/**
* @return int Get the default icon resource id
*/
public int getDefaultIconId() {
return iconToResId(0);
}
/**
* Icon as a drawable
*
* @param iconId Icon database Id of the icon to retrieve
* @return int resourceId
*/
public Drawable getDrawable(int iconId) {
return resources.getDrawable(iconToResId(iconId));
}
}

View File

@@ -0,0 +1,143 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.icons
import android.content.res.Resources
import android.util.SparseIntArray
import com.kunzisoft.keepass.R
import java.text.DecimalFormat
/**
* Class who construct dynamically database icons contains in a separate library
*
*
* It only supports icons with specific nomenclature **[stringId]_[%2d]_32dp**
* where [stringId] contains in a string xml attribute with id **resource_id** and
* [%2d] 2 numerical numbers between 00 and 68 included,
*
*
* See *icon-pack-classic* module as sample
*
*
*/
class IconPack
/**
* Construct dynamically the icon pack provide by the string resource id
*
* @param packageName Context of the app to retrieve the resources
* @param packageName Context of the app to retrieve the resources
* @param resourceId String Id of the pack (ex : com.kunzisoft.keepass.icon.classic.R.string.resource_id)
*/
internal constructor(packageName: String, resources: Resources, resourceId: Int) {
private val icons: SparseIntArray = SparseIntArray()
/**
* Get the id of the IconPack
*
* @return String id of the pack
*/
var id: String? = null
private set
/**
* Get the name of the IconPack
*
* @return String visual name of the pack
*/
val name: String
private val tintable: Boolean
/**
* @return int Get the default icon resource id
*/
val defaultIconId: Int
get() = iconToResId(0)
init {
id = resources.getString(resourceId)
// If finish with a _ remove it
id?.let { idRes ->
if (idRes.lastIndexOf('_') == idRes.length - 1)
id = idRes.substring(0, idRes.length - 1)
}
// Build the list of icons
var num = 0
while (num <= NB_ICONS) {
// To construct the id with name_ic_XX_32dp (ex : classic_ic_08_32dp )
val resId = resources.getIdentifier(
id + "_" + DecimalFormat("00").format(num.toLong()) + "_32dp",
"drawable",
packageName)
icons.put(num, resId)
num++
}
// Get visual name
name = resources.getString(
resources.getIdentifier(
id + "_" + "name",
"string",
packageName
)
)
// If icons are tintable
tintable = resources.getBoolean(
resources.getIdentifier(
id + "_" + "tintable",
"bool",
packageName
)
)
}
/**
* Determine if each icon in the pack can be tint
*
* @return true if icons are tintable
*/
fun tintable(): Boolean {
return tintable
}
/**
* Get the number of icons in this pack
*
* @return int Number of database icons
*/
fun numberOfIcons(): Int {
return icons.size()
}
/**
* Icon as a resourceId
*
* @param iconId Icon database Id of the icon to retrieve
* @return int resourceId
*/
fun iconToResId(iconId: Int): Int {
return icons.get(iconId, R.drawable.ic_blank_32dp)
}
companion object {
private const val NB_ICONS = 68
}
}

View File

@@ -1,141 +0,0 @@
/*
* Copyright 2018 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.icons;
import android.content.Context;
import android.util.Log;
import com.kunzisoft.keepass.BuildConfig;
import com.kunzisoft.keepass.app.App;
import com.kunzisoft.keepass.settings.PreferencesUtil;
import java.util.ArrayList;
import java.util.List;
/**
* Utility class to built and select an IconPack dynamically by libraries importation
*
* @author J-Jamet
*/
public class IconPackChooser {
private static final String TAG = IconPackChooser.class.getName();
private static List<IconPack> iconPackList = new ArrayList<>();
private static IconPack iconPackSelected = null;
private static IconPackChooser sIconPackBuilder;
/**
* IconPackChooser as singleton
*/
private IconPackChooser(){
if (sIconPackBuilder != null){
throw new RuntimeException("Use build() method to get the single instance of this class.");
}
}
/**
* Built the icon pack chooser based on imports made in <i>build.gradle</i>
*
* <p>Dynamic import can be done for each flavor by prefixing the 'implementation' command with the name of the flavor.< br/>
* (ex : {@code libreImplementation project(path: ':icon-pack-classic')} <br />
* Each name of icon pack must be in {@code ICON_PACKS} in the build.gradle file</p>
*
* @param context Context to construct each pack with the resources
* @return An unique instance of {@link IconPackChooser}, recall {@link #build(Context)} provide the same instance
*/
@SuppressWarnings("JavaDoc")
public static IconPackChooser build(Context context) {
if (sIconPackBuilder == null) { //if there is no instance available... create new one
synchronized (IconPackChooser.class) {
if (sIconPackBuilder == null) {
sIconPackBuilder = new IconPackChooser();
for (String iconPackString : BuildConfig.ICON_PACKS) {
addOrCatchNewIconPack(context, iconPackString);
}
if (iconPackList.isEmpty()) {
Log.e(TAG, "Icon packs can't be load, retry with one by default");
addDefaultIconPack(context);
}
}
}
}
return sIconPackBuilder;
}
/**
* Construct dynamically the icon pack provide by the default string resource "resource_id"
*/
private static void addDefaultIconPack(Context context) {
int resourceId = context.getResources().getIdentifier("resource_id", "string", context.getPackageName());
iconPackList.add(new IconPack(context, resourceId));
}
/**
* Utility method to add new icon pack or catch exception if not retrieve
*/
private static void addOrCatchNewIconPack(Context context, String iconPackString) {
try {
iconPackList.add(new IconPack(context, context.getResources().getIdentifier(
iconPackString + "_resource_id",
"string",
context.getPackageName())));
} catch (Exception e) {
Log.w(TAG, "Icon pack "+ iconPackString +" can't be load");
}
}
public static void setSelectedIconPack(String iconPackIdString) {
for(IconPack iconPack : iconPackList) {
if (iconPack.getId().equals(iconPackIdString)) {
App.Companion.getCurrentDatabase().getDrawFactory().clearCache();
iconPackSelected = iconPack;
break;
}
}
}
/**
* Get the current IconPack used
*
* @param context Context to build the icon pack if not already build
* @return IconPack currently in usage
*/
public static IconPack getSelectedIconPack(Context context) {
build(context);
if (iconPackSelected == null)
setSelectedIconPack(PreferencesUtil.INSTANCE.getIconPackSelectedId(context));
return iconPackSelected;
}
/**
* Get the list of IconPack available
*
* @param context Context to build the icon pack if not already build
* @return IconPack available
*/
public static List<IconPack> getIconPackList(Context context) {
build(context);
return iconPackList;
}
}

View File

@@ -0,0 +1,128 @@
/*
* Copyright 2018 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.icons
import android.content.Context
import android.util.Log
import com.kunzisoft.keepass.BuildConfig
import com.kunzisoft.keepass.app.App
import com.kunzisoft.keepass.settings.PreferencesUtil
import java.util.ArrayList
/**
* Utility class to built and select an IconPack dynamically by libraries importation
*
* @author J-Jamet
*/
object IconPackChooser {
private val TAG = IconPackChooser::class.java.name
private val iconPackList = ArrayList<IconPack>()
private var iconPackSelected: IconPack? = null
private var isIconPackChooserBuilt: Boolean = false
/**
* Built the icon pack chooser based on imports made in *build.gradle*
*
*
* Dynamic import can be done for each flavor by prefixing the 'implementation' command with the name of the flavor.< br/>
* (ex : `libreImplementation project(path: ':icon-pack-classic')` <br></br>
* Each name of icon pack must be in `ICON_PACKS` in the build.gradle file
*
* @param context Context to construct each pack with the resources
* @return An unique instance of [IconPackChooser], recall [.build] provide the same instance
*/
fun build(context: Context) {
synchronized(IconPackChooser::class.java) {
if (!isIconPackChooserBuilt) {
isIconPackChooserBuilt = true
for (iconPackString in BuildConfig.ICON_PACKS) {
addOrCatchNewIconPack(context, iconPackString)
}
if (iconPackList.isEmpty()) {
Log.e(TAG, "Icon packs can't be load, retry with one by default")
addDefaultIconPack(context)
}
}
}
}
/**
* Construct dynamically the icon pack provide by the default string resource "resource_id"
*/
private fun addDefaultIconPack(context: Context) {
val resourceId = context.resources.getIdentifier("resource_id", "string", context.packageName)
iconPackList.add(IconPack(context.packageName, context.resources, resourceId))
}
/**
* Utility method to add new icon pack or catch exception if not retrieve
*/
private fun addOrCatchNewIconPack(context: Context, iconPackString: String) {
try {
iconPackList.add(IconPack(context.packageName, context.resources, context.resources.getIdentifier(
iconPackString + "_resource_id",
"string",
context.packageName)))
} catch (e: Exception) {
Log.w(TAG, "Icon pack $iconPackString can't be load")
}
}
fun setSelectedIconPack(iconPackIdString: String?) {
for (iconPack in iconPackList) {
if (iconPack.id == iconPackIdString) {
App.currentDatabase.drawFactory.clearCache()
iconPackSelected = iconPack
break
}
}
}
/**
* Get the current IconPack used
*
* @param context Context to build the icon pack if not already build
* @return IconPack currently in usage
*/
fun getSelectedIconPack(context: Context): IconPack? {
build(context)
if (iconPackSelected == null)
setSelectedIconPack(PreferencesUtil.getIconPackSelectedId(context))
return iconPackSelected
}
/**
* Get the list of IconPack available
*
* @param context Context to build the icon pack if not already build
* @return IconPack available
*/
fun getIconPackList(context: Context): List<IconPack> {
build(context)
return iconPackList
}
}

View File

@@ -1,8 +0,0 @@
package com.kunzisoft.keepass.icons;
public class IconPackUnknownException extends Exception{
IconPackUnknownException() {
super("Icon pack isn't defined");
}
}

View File

@@ -3,12 +3,9 @@ package com.kunzisoft.keepass.settings.preference
import android.content.Context import android.content.Context
import android.support.v7.preference.ListPreference import android.support.v7.preference.ListPreference
import android.util.AttributeSet import android.util.AttributeSet
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.icons.IconPack
import com.kunzisoft.keepass.icons.IconPackChooser import com.kunzisoft.keepass.icons.IconPackChooser
import java.util.*
import java.util.ArrayList
class IconPackListPreference @JvmOverloads constructor(context: Context, class IconPackListPreference @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null, attrs: AttributeSet? = null,
@@ -20,12 +17,16 @@ class IconPackListPreference @JvmOverloads constructor(context: Context,
val entries = ArrayList<String>() val entries = ArrayList<String>()
val values = ArrayList<String>() val values = ArrayList<String>()
for (iconPack in IconPackChooser.getIconPackList(context)) { for (iconPack in IconPackChooser.getIconPackList(context)) {
entries.add(iconPack.name) if (iconPack.id != null) {
values.add(iconPack.id) entries.add(iconPack.name)
values.add(iconPack.id!!)
}
} }
setEntries(entries.toTypedArray()) setEntries(entries.toTypedArray())
entryValues = values.toTypedArray() entryValues = values.toTypedArray()
setDefaultValue(IconPackChooser.getSelectedIconPack(context).id) IconPackChooser.getSelectedIconPack(context)?.let { selectedIconPack ->
setDefaultValue(selectedIconPack.id)
}
} }
} }