Merge branch 'develop' into feature/Merge_Data

This commit is contained in:
J-Jamet
2022-01-14 18:52:55 +01:00
59 changed files with 1164 additions and 280 deletions

View File

@@ -2,9 +2,11 @@ KeePassDX(3.1.0)
* Add breadcrumb
* Add path in search results #1148
* Add group info dialog #1177
* Manage colors #64 #913
* Fix UI in Android 8 #509
* Upgrade libs and SDK to 31 #833
* Stop asking WRITE_EXTERNAL_STORAGE permission
* Fix parser of database v1
KeePassDX(3.0.4)
* Fix autofill inline bugs #1173 #1165

View File

@@ -72,7 +72,7 @@ Other questions? You can read the [FAQ](https://github.com/Kunzisoft/KeePassDX/w
## License
Copyright © 2020 Jeremy Jamet / [Kunzisoft](https://www.kunzisoft.com).
Copyright © 2022 Jeremy Jamet / [Kunzisoft](https://www.kunzisoft.com).
This file is part of KeePassDX.

View File

@@ -99,14 +99,14 @@ android {
}
}
def room_version = "2.4.0"
def room_version = "2.4.1"
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "androidx.appcompat:appcompat:$android_appcompat_version"
implementation 'androidx.preference:preference-ktx:1.1.1'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.2'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01'
implementation 'androidx.documentfile:documentfile:1.0.1'
implementation 'androidx.biometric:biometric:1.1.0'
@@ -123,7 +123,7 @@ dependencies {
// Time
implementation 'joda-time:joda-time:2.10.13'
// Color
implementation 'com.github.Kunzisoft:AndroidClearChroma:2.4'
implementation 'com.github.Kunzisoft:AndroidClearChroma:2.6'
// Education
implementation 'com.getkeepsafe.taptargetview:taptargetview:1.13.3'
// Apache Commons

View File

@@ -36,7 +36,12 @@ import androidx.activity.result.ActivityResultLauncher
import androidx.activity.viewModels
import androidx.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.graphics.BlendModeColorFilterCompat
import androidx.core.graphics.BlendModeCompat
import androidx.core.graphics.ColorUtils
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.appbar.CollapsingToolbarLayout
import com.google.android.material.progressindicator.LinearProgressIndicator
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.fragments.EntryFragment
import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
@@ -58,7 +63,11 @@ import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.tasks.AttachmentFileBinderManager
import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.*
import com.kunzisoft.keepass.utils.MenuUtil
import com.kunzisoft.keepass.utils.UriUtil
import com.kunzisoft.keepass.utils.UuidUtil
import com.kunzisoft.keepass.view.changeControlColor
import com.kunzisoft.keepass.view.changeTitleColor
import com.kunzisoft.keepass.view.hideByFading
import com.kunzisoft.keepass.view.showActionErrorIfNeeded
import com.kunzisoft.keepass.viewmodels.EntryViewModel
@@ -68,9 +77,10 @@ class EntryActivity : DatabaseLockActivity() {
private var coordinatorLayout: CoordinatorLayout? = null
private var collapsingToolbarLayout: CollapsingToolbarLayout? = null
private var appBarLayout: AppBarLayout? = null
private var titleIconView: ImageView? = null
private var historyView: View? = null
private var entryProgress: ProgressBar? = null
private var entryProgress: LinearProgressIndicator? = null
private var lockView: View? = null
private var toolbar: Toolbar? = null
private var loadingView: ProgressBar? = null
@@ -93,7 +103,12 @@ class EntryActivity : DatabaseLockActivity() {
}
private var mIcon: IconImage? = null
private var mIconColor: Int = 0
private var mColorAccent: Int = 0
private var mControlColor: Int = 0
private var mColorPrimary: Int = 0
private var mColorBackground: Int = 0
private var mBackgroundColor: Int? = null
private var mForegroundColor: Int? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -108,6 +123,7 @@ class EntryActivity : DatabaseLockActivity() {
// Get views
coordinatorLayout = findViewById(R.id.toolbar_coordinator)
collapsingToolbarLayout = findViewById(R.id.toolbar_layout)
appBarLayout = findViewById(R.id.app_bar)
titleIconView = findViewById(R.id.entry_icon)
historyView = findViewById(R.id.history_container)
entryProgress = findViewById(R.id.entry_progress)
@@ -118,10 +134,19 @@ class EntryActivity : DatabaseLockActivity() {
collapsingToolbarLayout?.title = " "
toolbar?.title = " "
// Retrieve the textColor to tint the icon
val taIconColor = theme.obtainStyledAttributes(intArrayOf(R.attr.colorAccent))
mIconColor = taIconColor.getColor(0, Color.BLACK)
taIconColor.recycle()
// Retrieve the textColor to tint the toolbar
val taColorAccent = theme.obtainStyledAttributes(intArrayOf(R.attr.colorAccent))
val taControlColor = theme.obtainStyledAttributes(intArrayOf(R.attr.toolbarColorControl))
val taColorPrimary = theme.obtainStyledAttributes(intArrayOf(R.attr.colorPrimary))
val taColorBackground = theme.obtainStyledAttributes(intArrayOf(android.R.attr.windowBackground))
mColorAccent = taColorAccent.getColor(0, Color.BLACK)
mControlColor = taControlColor.getColor(0, Color.BLACK)
mColorPrimary = taColorPrimary.getColor(0, Color.BLACK)
mColorBackground = taColorBackground.getColor(0, Color.BLACK)
taColorAccent.recycle()
taControlColor.recycle()
taColorPrimary.recycle()
taColorBackground.recycle()
// Get Entry from UUID
try {
@@ -166,10 +191,8 @@ class EntryActivity : DatabaseLockActivity() {
// Assign history dedicated view
historyView?.visibility = if (entryIsHistory) View.VISIBLE else View.GONE
if (entryIsHistory) {
val taColorAccent = theme.obtainStyledAttributes(intArrayOf(R.attr.colorAccent))
collapsingToolbarLayout?.contentScrim =
ColorDrawable(taColorAccent.getColor(0, Color.BLACK))
taColorAccent.recycle()
ColorDrawable(mColorAccent)
}
val entryInfo = entryInfoHistory.entryInfo
@@ -184,15 +207,15 @@ class EntryActivity : DatabaseLockActivity() {
}
// Assign title icon
mIcon = entryInfo.icon
titleIconView?.let { iconView ->
mIconDrawableFactory?.assignDatabaseIcon(iconView, entryInfo.icon, mIconColor)
}
// Assign title text
val entryTitle =
if (entryInfo.title.isNotEmpty()) entryInfo.title else UuidUtil.toHexString(entryInfo.id)
collapsingToolbarLayout?.title = entryTitle
toolbar?.title = entryTitle
mUrl = entryInfo.url
// Assign colors
mBackgroundColor = entryInfo.backgroundColor
mForegroundColor = entryInfo.foregroundColor
loadingView?.hideByFading()
mEntryLoaded = true
@@ -215,7 +238,7 @@ class EntryActivity : DatabaseLockActivity() {
OtpType.TOTP -> {
entryProgress?.apply {
max = otpElement.period
progress = otpElement.secondsRemaining
setProgressCompat(otpElement.secondsRemaining, true)
visibility = View.VISIBLE
}
}
@@ -252,13 +275,6 @@ class EntryActivity : DatabaseLockActivity() {
super.onDatabaseRetrieved(database)
mEntryViewModel.loadDatabase(database)
// Assign title icon
mIcon?.let { icon ->
titleIconView?.let { iconView ->
mIconDrawableFactory?.assignDatabaseIcon(iconView, icon, mIconColor)
}
}
}
override fun onDatabaseActionFinished(
@@ -304,6 +320,29 @@ class EntryActivity : DatabaseLockActivity() {
super.onPause()
}
private fun applyToolbarColors() {
appBarLayout?.setBackgroundColor(mBackgroundColor ?: mColorPrimary)
collapsingToolbarLayout?.contentScrim = ColorDrawable(mBackgroundColor ?: mColorPrimary)
val backgroundDarker = if (mBackgroundColor != null) {
ColorUtils.blendARGB(mBackgroundColor!!, Color.WHITE, 0.1f)
} else {
mColorBackground
}
titleIconView?.background?.colorFilter = BlendModeColorFilterCompat
.createBlendModeColorFilterCompat(backgroundDarker, BlendModeCompat.SRC_IN)
mIcon?.let { icon ->
titleIconView?.let { iconView ->
mIconDrawableFactory?.assignDatabaseIcon(
iconView,
icon,
mForegroundColor ?: mColorAccent
)
}
}
toolbar?.changeControlColor(mForegroundColor ?: mControlColor)
collapsingToolbarLayout?.changeTitleColor(mForegroundColor ?: mControlColor)
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
super.onCreateOptionsMenu(menu)
if (mEntryLoaded) {
@@ -340,6 +379,7 @@ class EntryActivity : DatabaseLockActivity() {
if (mSpecialMode != SpecialMode.DEFAULT) {
menu?.findItem(R.id.menu_reload_database)?.isVisible = false
}
applyToolbarColors()
return super.onPrepareOptionsMenu(menu)
}

View File

@@ -74,6 +74,7 @@ import com.kunzisoft.keepass.tasks.AttachmentFileBinderManager
import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.UriUtil
import com.kunzisoft.keepass.view.*
import com.kunzisoft.keepass.viewmodels.ColorPickerViewModel
import com.kunzisoft.keepass.viewmodels.EntryEditViewModel
import org.joda.time.DateTime
import java.util.*
@@ -103,6 +104,8 @@ class EntryEditActivity : DatabaseLockActivity(),
private var mEntryLoaded: Boolean = false
private var mTemplatesSelectorAdapter: TemplatesSelectorAdapter? = null
private val mColorPickerViewModel: ColorPickerViewModel by viewModels()
private var mAllowCustomFields = false
private var mAllowOTP = false
@@ -243,6 +246,15 @@ class EntryEditActivity : DatabaseLockActivity(),
IconPickerActivity.launch(this@EntryEditActivity, iconImage, mIconSelectionActivityResultLauncher)
}
mEntryEditViewModel.requestColorSelection.observe(this) { color ->
ColorPickerDialogFragment.newInstance(color)
.show(supportFragmentManager, "ColorPickerFragment")
}
mColorPickerViewModel.colorPicked.observe(this) { color ->
mEntryEditViewModel.selectColor(color)
}
mEntryEditViewModel.requestDateTimeSelection.observe(this) { dateInstant ->
if (dateInstant.type == DateInstant.Type.TIME) {
// Launch the time picker

View File

@@ -25,6 +25,7 @@ import android.app.TimePickerDialog
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.graphics.PorterDuff
import android.os.*
import android.util.Log
import android.view.Menu
@@ -88,6 +89,8 @@ class GroupActivity : DatabaseLockActivity(),
private var coordinatorLayout: CoordinatorLayout? = null
private var lockView: View? = null
private var toolbar: Toolbar? = null
private var databaseNameContainer: ViewGroup? = null
private var databaseColorView: ImageView? = null
private var databaseNameView: TextView? = null
private var searchContainer: ViewGroup? = null
private var searchNumbers: TextView? = null
@@ -144,6 +147,8 @@ class GroupActivity : DatabaseLockActivity(),
numberChildrenView = findViewById(R.id.group_numbers)
addNodeButtonView = findViewById(R.id.add_node_button)
toolbar = findViewById(R.id.toolbar)
databaseNameContainer = findViewById(R.id.database_name_container)
databaseColorView = findViewById(R.id.database_color)
databaseNameView = findViewById(R.id.database_name)
searchContainer = findViewById(R.id.search_container)
searchNumbers = findViewById(R.id.search_numbers)
@@ -322,9 +327,6 @@ class GroupActivity : DatabaseLockActivity(),
}
}
assignGroupViewElements(currentGroup)
invalidateOptionsMenu()
loadingView?.hideByFading()
}
@@ -412,6 +414,16 @@ class GroupActivity : DatabaseLockActivity(),
// Search suggestion
database?.let {
databaseNameView?.text = if (it.name.isNotEmpty()) it.name else getString(R.string.database)
val customColor = it.customColor
if (customColor != null) {
databaseColorView?.visibility = View.VISIBLE
databaseColorView?.setColorFilter(
customColor,
PorterDuff.Mode.SRC_IN
)
} else {
databaseColorView?.visibility = View.GONE
}
mSearchSuggestionAdapter = SearchEntryCursorAdapter(this, it)
mBreadcrumbAdapter?.iconDrawableFactory = it.iconDrawableFactory
mOnSuggestionListener = object : SearchView.OnSuggestionListener {
@@ -566,12 +578,12 @@ class GroupActivity : DatabaseLockActivity(),
val title = group.title
searchString?.text = if (title.isNotEmpty()) title else ""
searchNumbers?.text = group.numberOfChildEntries.toString()
databaseNameView?.visibility = View.GONE
databaseNameContainer?.visibility = View.GONE
toolbarBreadcrumb?.navigationIcon = null
toolbarBreadcrumb?.collapse()
} else {
searchContainer?.visibility = View.GONE
databaseNameView?.visibility = View.VISIBLE
databaseNameContainer?.visibility = View.VISIBLE
// Refresh breadcrumb
if (toolbarBreadcrumb?.isVisible != true) {
toolbarBreadcrumb?.expand {
@@ -582,9 +594,8 @@ class GroupActivity : DatabaseLockActivity(),
setBreadcrumbNode(group)
}
}
// Hide button
initAddButton(group)
invalidateOptionsMenu()
}
private fun setBreadcrumbNode(group: Group?) {

View File

@@ -101,15 +101,6 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
private var mReadOnly: Boolean = false
private var mForceReadOnly: Boolean = false
set(value) {
infoContainerView?.visibility = if (value) {
mReadOnly = true
View.VISIBLE
} else {
View.GONE
}
field = value
}
private var mAutofillActivityResultLauncher: ActivityResultLauncher<Intent>? =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
@@ -204,10 +195,19 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
// Observe database file change
mDatabaseFileViewModel.databaseFileLoaded.observe(this) { databaseFile ->
// Force read only if the file does not exists
mForceReadOnly = databaseFile?.let {
val databaseFileNotExists = databaseFile?.let {
!it.databaseFileExists
} ?: true
infoContainerView?.visibility = if (databaseFileNotExists) {
mReadOnly = true
View.VISIBLE
} else {
View.GONE
}
mForceReadOnly = databaseFileNotExists
invalidateOptionsMenu()
// Post init uri with KeyFile only if needed

View File

@@ -0,0 +1,95 @@
package com.kunzisoft.keepass.activities.dialogs
import android.app.Dialog
import android.graphics.Color
import android.os.Bundle
import android.widget.CompoundButton
import androidx.annotation.ColorInt
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.activityViewModels
import com.kunzisoft.androidclearchroma.view.ChromaColorView
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.viewmodels.ColorPickerViewModel
class ColorPickerDialogFragment : DatabaseDialogFragment() {
private val mColorPickerViewModel: ColorPickerViewModel by activityViewModels()
private lateinit var enableSwitchView: CompoundButton
private lateinit var chromaColorView: ChromaColorView
private var mDefaultColor = Color.WHITE
private var mActivated = false
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
activity?.let { activity ->
val root = activity.layoutInflater.inflate(R.layout.fragment_color_picker, null)
enableSwitchView = root.findViewById(R.id.switch_element)
chromaColorView = root.findViewById(R.id.chroma_color_view)
if (savedInstanceState != null) {
if (savedInstanceState.containsKey(ARG_INITIAL_COLOR)) {
mDefaultColor = savedInstanceState.getInt(ARG_INITIAL_COLOR)
}
if (savedInstanceState.containsKey(ARG_ACTIVATED)) {
mActivated = savedInstanceState.getBoolean(ARG_ACTIVATED)
}
} else {
arguments?.apply {
if (containsKey(ARG_INITIAL_COLOR)) {
mDefaultColor = getInt(ARG_INITIAL_COLOR)
}
if (containsKey(ARG_ACTIVATED)) {
mActivated = getBoolean(ARG_ACTIVATED)
}
}
}
enableSwitchView.isChecked = mActivated
chromaColorView.currentColor = mDefaultColor
chromaColorView.setOnColorChangedListener {
if (!enableSwitchView.isChecked)
enableSwitchView.isChecked = true
}
val builder = AlertDialog.Builder(activity)
builder.setView(root)
.setPositiveButton(android.R.string.ok) { _, _ ->
val color: Int? = if (enableSwitchView.isChecked)
chromaColorView.currentColor
else
null
mColorPickerViewModel.pickColor(color)
}
.setNegativeButton(android.R.string.cancel) { _, _ ->
// Do nothing
}
return builder.create()
}
return super.onCreateDialog(savedInstanceState)
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putInt(ARG_INITIAL_COLOR, chromaColorView.currentColor)
outState.putBoolean(ARG_ACTIVATED, mActivated)
}
companion object {
private const val ARG_INITIAL_COLOR = "ARG_INITIAL_COLOR"
private const val ARG_ACTIVATED = "ARG_ACTIVATED"
fun newInstance(
@ColorInt initialColor: Int?,
): ColorPickerDialogFragment {
return ColorPickerDialogFragment().apply {
arguments = Bundle().apply {
putInt(ARG_INITIAL_COLOR, initialColor ?: Color.WHITE)
putBoolean(ARG_ACTIVATED, initialColor != null)
}
}
}
}
}

View File

@@ -99,6 +99,12 @@ class EntryEditFragment: DatabaseFragment() {
setOnIconClickListener {
mEntryEditViewModel.requestIconSelection(templateView.getIcon())
}
setOnBackgroundColorClickListener {
mEntryEditViewModel.requestBackgroundColorSelection(templateView.getBackgroundColor())
}
setOnForegroundColorClickListener {
mEntryEditViewModel.requestForegroundColorSelection(templateView.getForegroundColor())
}
setOnCustomEditionActionClickListener { field ->
mEntryEditViewModel.requestCustomFieldEdition(field)
}
@@ -147,6 +153,14 @@ class EntryEditFragment: DatabaseFragment() {
templateView.setIcon(iconImage)
}
mEntryEditViewModel.onBackgroundColorSelected.observe(this) { color ->
templateView.setBackgroundColor(color)
}
mEntryEditViewModel.onForegroundColorSelected.observe(this) { color ->
templateView.setForegroundColor(color)
}
mEntryEditViewModel.onPasswordSelected.observe(viewLifecycleOwner) { passwordField ->
templateView.setPasswordField(passwordField)
}

View File

@@ -28,7 +28,7 @@ import android.util.Log
import android.view.WindowManager
import androidx.annotation.StyleRes
import androidx.appcompat.app.AppCompatActivity
import com.kunzisoft.keepass.settings.NestedAppSettingsFragment.Companion.DATABASE_APPEARANCE_PREFERENCE_CHANGED
import com.kunzisoft.keepass.settings.NestedAppSettingsFragment.Companion.DATABASE_PREFERENCE_CHANGED
/**
* Stylish Hide Activity that apply a dynamic style and sets FLAG_SECURE to prevent screenshots / from
@@ -89,8 +89,8 @@ abstract class StylishActivity : AppCompatActivity() {
super.onResume()
if ((customStyle && Stylish.getThemeId(this) != this.themeId)
|| DATABASE_APPEARANCE_PREFERENCE_CHANGED) {
DATABASE_APPEARANCE_PREFERENCE_CHANGED = false
|| DATABASE_PREFERENCE_CHANGED) {
DATABASE_PREFERENCE_CHANGED = false
Log.d(this.javaClass.name, "Theme change detected, restarting activity")
recreateActivity()
}

View File

@@ -34,12 +34,15 @@ import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SortedList
import androidx.recyclerview.widget.SortedListAdapterCallback
import com.google.android.material.progressindicator.CircularProgressIndicator
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.SortNodeEnum
import com.kunzisoft.keepass.database.element.node.*
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.NodeVersionedInterface
import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.element.template.TemplateField
import com.kunzisoft.keepass.otp.OtpElement
import com.kunzisoft.keepass.otp.OtpType
@@ -87,9 +90,15 @@ class NodesAdapter (private val context: Context,
@ColorInt
private val mContentSelectionColor: Int
@ColorInt
private val mIconGroupColor: Int
private val mTextColorPrimary: Int
@ColorInt
private val mIconEntryColor: Int
private val mTextColor: Int
@ColorInt
private val mTextColorSecondary: Int
@ColorInt
private val mColorAccentLight: Int
@ColorInt
private val mTextColorInverse: Int
/**
* Determine if the adapter contains or not any element
@@ -110,12 +119,24 @@ class NodesAdapter (private val context: Context,
this.mContentSelectionColor = ContextCompat.getColor(context, R.color.white)
// Retrieve the color to tint the icon
val taTextColorPrimary = context.theme.obtainStyledAttributes(intArrayOf(android.R.attr.textColorPrimary))
this.mIconGroupColor = taTextColorPrimary.getColor(0, Color.BLACK)
this.mTextColorPrimary = taTextColorPrimary.getColor(0, Color.BLACK)
taTextColorPrimary.recycle()
// In two times to fix bug compilation
// To get text color
val taTextColor = context.theme.obtainStyledAttributes(intArrayOf(android.R.attr.textColor))
this.mIconEntryColor = taTextColor.getColor(0, Color.BLACK)
this.mTextColor = taTextColor.getColor(0, Color.BLACK)
taTextColor.recycle()
// To get text color secondary
val taTextColorSecondary = context.theme.obtainStyledAttributes(intArrayOf(android.R.attr.textColorSecondary))
this.mTextColorSecondary = taTextColorSecondary.getColor(0, Color.BLACK)
taTextColorSecondary.recycle()
// To get background color for selection
val taSelectionColor = context.theme.obtainStyledAttributes(intArrayOf(R.attr.colorAccentLight))
this.mColorAccentLight = taSelectionColor.getColor(0, Color.GRAY)
taSelectionColor.recycle()
// To get text color for selection
val taSelectionTextColor = context.theme.obtainStyledAttributes(intArrayOf(R.attr.textColorInverse))
this.mTextColorInverse = taSelectionTextColor.getColor(0, Color.WHITE)
taSelectionTextColor.recycle()
}
private fun assignPreferences() {
@@ -163,6 +184,8 @@ class NodesAdapter (private val context: Context,
if (oldItem is Entry && newItem is Entry) {
typeContentTheSame = oldItem.getVisualTitle() == newItem.getVisualTitle()
&& oldItem.username == newItem.username
&& oldItem.backgroundColor == newItem.backgroundColor
&& oldItem.foregroundColor == newItem.foregroundColor
&& oldItem.getOtpElement() == newItem.getOtpElement()
&& oldItem.containsAttachment() == newItem.containsAttachment()
} else if (oldItem is Group && newItem is Group) {
@@ -332,8 +355,8 @@ class NodesAdapter (private val context: Context,
val iconColor = if (holder.container.isSelected)
mContentSelectionColor
else when (subNode.type) {
Type.GROUP -> mIconGroupColor
Type.ENTRY -> mIconEntryColor
Type.GROUP -> mTextColorPrimary
Type.ENTRY -> mTextColor
}
holder.imageIdentifier?.setColorFilter(iconColor)
holder.icon.apply {
@@ -413,6 +436,50 @@ class NodesAdapter (private val context: Context,
holder.attachmentIcon?.visibility =
if (entry.containsAttachment()) View.VISIBLE else View.GONE
// Assign colors
val backgroundColor = entry.backgroundColor
if (!holder.container.isSelected) {
if (backgroundColor != null) {
holder.container.setBackgroundColor(backgroundColor)
} else {
holder.container.setBackgroundColor(Color.TRANSPARENT)
}
} else {
holder.container.setBackgroundColor(mColorAccentLight)
}
val foregroundColor = entry.foregroundColor
if (!holder.container.isSelected) {
if (foregroundColor != null) {
holder.text.setTextColor(foregroundColor)
holder.subText?.setTextColor(foregroundColor)
holder.otpToken?.setTextColor(foregroundColor)
holder.otpProgress?.setIndicatorColor(foregroundColor)
holder.attachmentIcon?.setColorFilter(foregroundColor)
holder.meta.setTextColor(foregroundColor)
holder.icon.apply {
database.iconDrawableFactory.assignDatabaseIcon(
this,
subNode.icon,
foregroundColor
)
}
} else {
holder.text.setTextColor(mTextColor)
holder.subText?.setTextColor(mTextColorSecondary)
holder.otpToken?.setTextColor(mTextColorSecondary)
holder.otpProgress?.setIndicatorColor(mTextColorSecondary)
holder.attachmentIcon?.setColorFilter(mTextColorSecondary)
holder.meta.setTextColor(mTextColor)
}
} else {
holder.text.setTextColor(mTextColorInverse)
holder.subText?.setTextColor(mTextColorInverse)
holder.otpToken?.setTextColor(mTextColorInverse)
holder.otpProgress?.setIndicatorColor(mTextColorInverse)
holder.attachmentIcon?.setColorFilter(mTextColorInverse)
holder.meta.setTextColor(mTextColorInverse)
}
database.stopManageEntry(entry)
}
@@ -445,13 +512,13 @@ class NodesAdapter (private val context: Context,
OtpType.HOTP -> {
holder?.otpProgress?.apply {
max = 100
progress = 100
setProgressCompat(100, true)
}
}
OtpType.TOTP -> {
holder?.otpProgress?.apply {
max = otpElement.period
progress = otpElement.secondsRemaining
setProgressCompat(otpElement.secondsRemaining, true)
}
}
null -> {}
@@ -515,7 +582,7 @@ class NodesAdapter (private val context: Context,
var meta: TextView = itemView.findViewById(R.id.node_meta)
var path: TextView? = itemView.findViewById(R.id.node_path)
var otpContainer: ViewGroup? = itemView.findViewById(R.id.node_otp_container)
var otpProgress: ProgressBar? = itemView.findViewById(R.id.node_otp_progress)
var otpProgress: CircularProgressIndicator? = itemView.findViewById(R.id.node_otp_progress)
var otpToken: TextView? = itemView.findViewById(R.id.node_otp_token)
var otpRunnable: OtpRunnable = OtpRunnable(otpContainer)
var numberChildren: TextView? = itemView.findViewById(R.id.node_child_numbers)

View File

@@ -22,8 +22,10 @@ package com.kunzisoft.keepass.database.element
import android.content.ContentResolver
import android.content.Context
import android.content.res.Resources
import android.graphics.Color
import android.net.Uri
import android.util.Log
import com.kunzisoft.androidclearchroma.ChromaUtil
import com.kunzisoft.keepass.database.action.node.NodeHandler
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine
@@ -225,31 +227,33 @@ class Database {
mDatabaseKDBX?.descriptionChanged = DateInstant()
}
val allowDefaultUsername: Boolean
get() = mDatabaseKDBX != null
// TODO get() = mDatabaseKDB != null || mDatabaseKDBX != null
var defaultUsername: String
get() {
return mDatabaseKDBX?.defaultUserName ?: "" // TODO mDatabaseKDB default username
return mDatabaseKDB?.defaultUserName ?: mDatabaseKDBX?.defaultUserName ?: ""
}
set(username) {
mDatabaseKDB?.defaultUserName = username
mDatabaseKDBX?.defaultUserName = username
mDatabaseKDBX?.defaultUserNameChanged = DateInstant()
}
val allowCustomColor: Boolean
get() = mDatabaseKDBX != null
// TODO get() = mDatabaseKDB != null || mDatabaseKDBX != null
// with format "#000000"
var customColor: String
var customColor: Int?
get() {
return mDatabaseKDBX?.color ?: "" // TODO mDatabaseKDB color
var colorInt: Int? = null
mDatabaseKDBX?.color?.let {
try {
colorInt = Color.parseColor(it)
} catch (e: Exception) {}
}
return mDatabaseKDB?.color ?: colorInt
}
set(value) {
// TODO Check color string
mDatabaseKDBX?.color = value
mDatabaseKDB?.color = value
mDatabaseKDBX?.color = if (value == null) {
""
} else {
ChromaUtil.getFormattedColorString(value, false)
}
}
val allowOTP: Boolean

View File

@@ -19,8 +19,10 @@
*/
package com.kunzisoft.keepass.database.element
import android.graphics.Color
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.androidclearchroma.ChromaUtil
import com.kunzisoft.keepass.database.element.binary.AttachmentPool
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.element.database.DatabaseVersioned
@@ -238,6 +240,42 @@ class Entry : Node, EntryVersionedInterface<Group> {
entryKDBX?.notes = value
}
var backgroundColor: Int?
get() {
var colorInt: Int? = null
entryKDBX?.backgroundColor?.let {
try {
colorInt = Color.parseColor(it)
} catch (e: Exception) {}
}
return colorInt
}
set(value) {
entryKDBX?.backgroundColor = if (value == null) {
""
} else {
ChromaUtil.getFormattedColorString(value, false)
}
}
var foregroundColor: Int?
get() {
var colorInt: Int? = null
entryKDBX?.foregroundColor?.let {
try {
colorInt = Color.parseColor(it)
} catch (e: Exception) {}
}
return colorInt
}
set(value) {
entryKDBX?.foregroundColor = if (value == null) {
""
} else {
ChromaUtil.getFormattedColorString(value, false)
}
}
private fun isTan(): Boolean {
return title == PMS_TAN_ENTRY && username.isNotEmpty()
}
@@ -419,6 +457,8 @@ class Entry : Node, EntryVersionedInterface<Group> {
entryInfo.expiryTime = expiryTime
entryInfo.url = url
entryInfo.notes = notes
entryInfo.backgroundColor = backgroundColor
entryInfo.foregroundColor = foregroundColor
entryInfo.customFields = getExtraFields().toMutableList()
// Add otpElement to generate token
entryInfo.otpModel = getOtpElement()?.otpModel
@@ -453,6 +493,8 @@ class Entry : Node, EntryVersionedInterface<Group> {
expiryTime = newEntryInfo.expiryTime
url = newEntryInfo.url
notes = newEntryInfo.notes
backgroundColor = newEntryInfo.backgroundColor
foregroundColor = newEntryInfo.foregroundColor
addExtraFields(newEntryInfo.customFields)
database?.attachmentPool?.let { binaryPool ->
newEntryInfo.attachments.forEach { attachment ->

View File

@@ -309,8 +309,9 @@ class Group : Node, GroupVersionedInterface<Group, Entry> {
val withoutMetaStream = filters.contains(ChildFilter.META_STREAM)
val showExpiredEntries = !filters.contains(ChildFilter.EXPIRED)
// TODO Change KDB parser to remove meta entries
return groupKDB?.getChildEntries()?.filter {
(!withoutMetaStream || (withoutMetaStream && !it.isMetaStream))
(!withoutMetaStream || (withoutMetaStream && !it.isMetaStream()))
&& (!it.isCurrentlyExpires or showExpiredEntries)
}?.map {
Entry(it)

View File

@@ -61,6 +61,10 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
return listOf(BACKUP_FOLDER_TITLE)
}
var defaultUserName: String = ""
var color: Int? = null
override val kdfEngine: KdfEngine
get() = kdfListV3[0]

View File

@@ -25,6 +25,7 @@ import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.binary.AttachmentPool
import com.kunzisoft.keepass.database.element.binary.BinaryData
import com.kunzisoft.keepass.database.element.group.GroupKDB
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
import com.kunzisoft.keepass.database.element.icon.IconImageStandard.Companion.KEY_ID
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
@@ -60,8 +61,7 @@ class EntryKDB : EntryVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
private var binaryDataId: Int? = null
// Determine if this is a MetaStream entry
val isMetaStream: Boolean
get() {
fun isMetaStream(): Boolean {
if (notes.isEmpty()) return false
if (binaryDescription != PMS_ID_BINDESC) return false
if (title.isEmpty()) return false
@@ -73,6 +73,32 @@ class EntryKDB : EntryVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
return icon.standard.id == KEY_ID
}
fun isMetaStreamDefaultUsername(): Boolean {
return isMetaStream() && notes == PMS_STREAM_DEFAULTUSER
}
private fun setMetaStream() {
binaryDescription = PMS_ID_BINDESC
title = PMS_ID_TITLE
username = PMS_ID_USER
url = PMS_ID_URL
icon.standard = IconImageStandard(KEY_ID)
}
fun setMetaStreamDefaultUsername() {
notes = PMS_STREAM_DEFAULTUSER
setMetaStream()
}
fun isMetaStreamDatabaseColor(): Boolean {
return isMetaStream() && notes == PMS_STREAM_DBCOLOR
}
fun setMetaStreamDatabaseColor() {
notes = PMS_STREAM_DBCOLOR
setMetaStream()
}
override fun initNodeId(): NodeId<UUID> {
return NodeIdUUID()
}
@@ -184,6 +210,13 @@ class EntryKDB : EntryVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
private const val PMS_ID_USER = "SYSTEM"
private const val PMS_ID_URL = "$"
const val PMS_STREAM_SIMPLESTATE = "Simple UI State"
const val PMS_STREAM_DEFAULTUSER = "Default User Name"
const val PMS_STREAM_SEARCHHISTORYITEM = "Search History Item"
const val PMS_STREAM_CUSTOMKVP = "Custom KVP"
const val PMS_STREAM_DBCOLOR = "Database Color"
const val PMS_STREAM_KPXICON2 = "KPX_CUSTOM_ICONS_2"
@JvmField
val CREATOR: Parcelable.Creator<EntryKDB> = object : Parcelable.Creator<EntryKDB> {
override fun createFromParcel(parcel: Parcel): EntryKDB {

View File

@@ -19,8 +19,6 @@
*/
package com.kunzisoft.keepass.database.file
import com.kunzisoft.keepass.utils.UnsignedInt
abstract class DatabaseHeader {
/**
@@ -33,8 +31,4 @@ abstract class DatabaseHeader {
*/
var encryptionIV = ByteArray(16)
companion object {
val PWM_DBSIG_1 = UnsignedInt(-0x655d26fd)
}
}

View File

@@ -34,7 +34,7 @@ class DatabaseHeaderKDB : DatabaseHeader() {
*/
var transformSeed = ByteArray(32)
var signature1 = UnsignedInt(0) // = PWM_DBSIG_1
var signature1 = UnsignedInt(0) // = DBSIG_1
var signature2 = UnsignedInt(0) // = DBSIG_2
var flags= UnsignedInt(0)
var version= UnsignedInt(0)
@@ -84,9 +84,9 @@ class DatabaseHeaderKDB : DatabaseHeader() {
companion object {
// DB sig from KeePass 1.03
val DBSIG_2 = UnsignedInt(-0x4ab4049b)
// DB sig from KeePass 1.03
val DBVER_DW = UnsignedInt(0x00030003)
val DBSIG_1 = UnsignedInt(-0x655d26fd) // 0x9AA2D903
val DBSIG_2 = UnsignedInt(-0x4ab4049b) // 0xB54BFB65
val DBVER_DW = UnsignedInt(0x00030004)
val FLAG_SHA2 = UnsignedInt(1)
val FLAG_RIJNDAEL = UnsignedInt(2)
@@ -97,7 +97,7 @@ class DatabaseHeaderKDB : DatabaseHeader() {
const val BUF_SIZE = 124
fun matchesHeader(sig1: UnsignedInt, sig2: UnsignedInt): Boolean {
return sig1.toKotlinInt() == PWM_DBSIG_1.toKotlinInt() && sig2.toKotlinInt() == DBSIG_2.toKotlinInt()
return sig1.toKotlinInt() == DBSIG_1.toKotlinInt() && sig2.toKotlinInt() == DBSIG_2.toKotlinInt()
}
fun compatibleHeaders(one: UnsignedInt, two: UnsignedInt): Boolean {

View File

@@ -311,8 +311,9 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
companion object {
val DBSIG_PRE2 = UnsignedInt(-0x4ab4049a)
val DBSIG_2 = UnsignedInt(-0x4ab40499)
val DBSIG_1 = UnsignedInt(-0x655d26fd) // 0x9AA2D903
val DBSIG_PRE2 = UnsignedInt(-0x4ab4049a) // 0xB54BFB66
val DBSIG_2 = UnsignedInt(-0x4ab40499) // 0xB54BFB67
private val FILE_VERSION_CRITICAL_MASK = UnsignedInt(-0x10000)
val FILE_VERSION_31 = UnsignedInt(0x00030001)
@@ -335,7 +336,7 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
}
fun matchesHeader(sig1: UnsignedInt, sig2: UnsignedInt): Boolean {
return sig1 == PWM_DBSIG_1 && (sig2 == DBSIG_PRE2 || sig2 == DBSIG_2)
return sig1 == DBSIG_1 && (sig2 == DBSIG_PRE2 || sig2 == DBSIG_2)
}
}
}

View File

@@ -20,6 +20,7 @@
package com.kunzisoft.keepass.database.file.input
import android.graphics.Color
import com.kunzisoft.encrypt.HashManager
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
import com.kunzisoft.keepass.database.element.DateInstant
@@ -29,7 +30,6 @@ import com.kunzisoft.keepass.database.element.group.GroupKDB
import com.kunzisoft.keepass.database.element.node.NodeIdInt
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.exception.*
import com.kunzisoft.keepass.database.file.DatabaseHeader
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
import com.kunzisoft.keepass.utils.*
@@ -87,7 +87,7 @@ class DatabaseInputKDB(database: DatabaseKDB)
if (fileSize != (contentSize + DatabaseHeaderKDB.BUF_SIZE))
throw IOException("Header corrupted")
if (header.signature1 != DatabaseHeader.PWM_DBSIG_1
if (header.signature1 != DatabaseHeaderKDB.DBSIG_1
|| header.signature2 != DatabaseHeaderKDB.DBSIG_2) {
throw SignatureDatabaseException()
}
@@ -285,6 +285,27 @@ class DatabaseInputKDB(database: DatabaseKDB)
}
newEntry?.let { entry ->
mDatabase.addEntryIndex(entry)
// Parse meta info
if (entry.isMetaStreamDefaultUsername()) {
var defaultUser = ""
entry.getBinary(mDatabase.attachmentPool)
?.getInputDataStream(mDatabase.binaryCache)?.use {
defaultUser = String(it.readBytes())
}
mDatabase.defaultUserName = defaultUser
} else if (entry.isMetaStreamDatabaseColor()) {
var color: Int? = null
entry.getBinary(mDatabase.attachmentPool)
?.getInputDataStream(mDatabase.binaryCache)?.use {
val reverseColor = UnsignedInt(it.readBytes4ToUInt()).toKotlinInt()
color = Color.rgb(
Color.blue(reverseColor),
Color.green(reverseColor),
Color.red(reverseColor)
)
}
mDatabase.color = color
}
currentEntryNumber++
newEntry = null
}

View File

@@ -68,7 +68,7 @@ constructor(private val databaseKDBX: DatabaseKDBX,
@Throws(IOException::class)
fun output() {
mos.write4BytesUInt(DatabaseHeader.PWM_DBSIG_1)
mos.write4BytesUInt(DatabaseHeaderKDBX.DBSIG_1)
mos.write4BytesUInt(DatabaseHeaderKDBX.DBSIG_2)
mos.write4BytesUInt(header.version)

View File

@@ -19,6 +19,7 @@
*/
package com.kunzisoft.keepass.database.file.output
import android.graphics.Color
import com.kunzisoft.encrypt.HashManager
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
import com.kunzisoft.keepass.database.element.database.DatabaseKDB
@@ -94,8 +95,7 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
} finally {
// Add again the virtual root group for better management
mDatabaseKDB.rootGroup = rootGroup
mGroupList.clear()
mEntryList.clear()
clearParser()
}
}
@@ -110,7 +110,7 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
override fun outputHeader(outputStream: OutputStream): DatabaseHeaderKDB {
// Build header
val header = DatabaseHeaderKDB()
header.signature1 = DatabaseHeader.PWM_DBSIG_1
header.signature1 = DatabaseHeaderKDB.DBSIG_1
header.signature2 = DatabaseHeaderKDB.DBSIG_2
header.flags = DatabaseHeaderKDB.FLAG_SHA2
@@ -211,9 +211,13 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
}
}
private fun sortNodesForOutput() {
private fun clearParser() {
mGroupList.clear()
mEntryList.clear()
}
private fun sortNodesForOutput() {
clearParser()
// Rebuild list according to sorting order removing any orphaned groups
// Do not keep root
mDatabaseKDB.rootGroup?.getChildGroups()?.let { rootSubGroups ->
@@ -228,8 +232,29 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
mGroupList.add(group)
for (childEntry in group.getChildEntries()) {
if (!childEntry.isMetaStreamDefaultUsername()
&& !childEntry.isMetaStreamDatabaseColor()) {
mEntryList.add(childEntry)
}
}
// Add MetaStream
if (mDatabaseKDB.defaultUserName.isNotEmpty()) {
val metaEntry = EntryKDB().apply {
setMetaStreamDefaultUsername()
setDefaultUsername(this)
}
mDatabaseKDB.addEntryTo(metaEntry, group)
mEntryList.add(metaEntry)
}
if (mDatabaseKDB.color != null) {
val metaEntry = EntryKDB().apply {
setMetaStreamDatabaseColor()
setDatabaseColor(this)
}
mDatabaseKDB.addEntryTo(metaEntry, group)
mEntryList.add(metaEntry)
}
// Recurse over children
for (childGroup in group.getChildGroups()) {
@@ -237,6 +262,30 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
}
}
private fun setDefaultUsername(entryKDB: EntryKDB) {
val binaryData = mDatabaseKDB.buildNewAttachment()
entryKDB.putBinary(binaryData, mDatabaseKDB.attachmentPool)
BufferedOutputStream(binaryData.getOutputDataStream(mDatabaseKDB.binaryCache)).use { outputStream ->
outputStream.write(mDatabaseKDB.defaultUserName.toByteArray())
}
}
private fun setDatabaseColor(entryKDB: EntryKDB) {
val binaryData = mDatabaseKDB.buildNewAttachment()
entryKDB.putBinary(binaryData, mDatabaseKDB.attachmentPool)
BufferedOutputStream(binaryData.getOutputDataStream(mDatabaseKDB.binaryCache)).use { outputStream ->
var reversColor = Color.BLACK
mDatabaseKDB.color?.let {
reversColor = Color.rgb(
Color.blue(it),
Color.green(it),
Color.red(it)
)
}
outputStream.write4BytesUInt(UnsignedInt(reversColor))
}
}
private fun getHeaderHashBuffer(headerDigest: ByteArray): ByteArray? {
return try {
val byteArrayOutputStream = ByteArrayOutputStream()

View File

@@ -48,7 +48,6 @@ import com.kunzisoft.keepass.database.file.DateKDBXUtil
import com.kunzisoft.keepass.stream.HashedBlockOutputStream
import com.kunzisoft.keepass.stream.HmacBlockOutputStream
import com.kunzisoft.keepass.utils.*
import org.joda.time.DateTime
import org.xmlpull.v1.XmlSerializer
import java.io.IOException
import java.io.OutputStream

View File

@@ -40,6 +40,8 @@ class EntryInfo : NodeInfo {
var password: String = ""
var url: String = ""
var notes: String = ""
var backgroundColor: Int? = null
var foregroundColor: Int? = null
var customFields: MutableList<Field> = mutableListOf()
var attachments: MutableList<Attachment> = mutableListOf()
var otpModel: OtpModel? = null
@@ -53,6 +55,10 @@ class EntryInfo : NodeInfo {
password = parcel.readString() ?: password
url = parcel.readString() ?: url
notes = parcel.readString() ?: notes
val readBgColor = parcel.readInt()
backgroundColor = if (readBgColor == -1) null else readBgColor
val readFgColor = parcel.readInt()
foregroundColor = if (readFgColor == -1) null else readFgColor
parcel.readList(customFields, Field::class.java.classLoader)
parcel.readList(attachments, Attachment::class.java.classLoader)
otpModel = parcel.readParcelable(OtpModel::class.java.classLoader) ?: otpModel
@@ -70,6 +76,8 @@ class EntryInfo : NodeInfo {
parcel.writeString(password)
parcel.writeString(url)
parcel.writeString(notes)
parcel.writeInt(backgroundColor ?: -1)
parcel.writeInt(foregroundColor ?: -1)
parcel.writeList(customFields)
parcel.writeList(attachments)
parcel.writeParcelable(otpModel, flags)
@@ -196,6 +204,8 @@ class EntryInfo : NodeInfo {
if (password != other.password) return false
if (url != other.url) return false
if (notes != other.notes) return false
if (backgroundColor != other.backgroundColor) return false
if (foregroundColor != other.foregroundColor) return false
if (customFields != other.customFields) return false
if (attachments != other.attachments) return false
if (otpModel != other.otpModel) return false
@@ -211,6 +221,8 @@ class EntryInfo : NodeInfo {
result = 31 * result + password.hashCode()
result = 31 * result + url.hashCode()
result = 31 * result + notes.hashCode()
result = 31 * result + backgroundColor.hashCode()
result = 31 * result + foregroundColor.hashCode()
result = 31 * result + customFields.hashCode()
result = 31 * result + attachments.hashCode()
result = 31 * result + (otpModel?.hashCode() ?: 0)

View File

@@ -452,7 +452,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
getString(R.string.show_uuid_key),
getString(R.string.enable_education_screens_key),
getString(R.string.reset_education_screens_key) -> {
DATABASE_APPEARANCE_PREFERENCE_CHANGED = true
DATABASE_PREFERENCE_CHANGED = true
}
}
return super.onPreferenceTreeClick(preference)
@@ -516,6 +516,6 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
companion object {
private const val TAG_PREF_FRAGMENT = "TAG_PREF_FRAGMENT"
var DATABASE_APPEARANCE_PREFERENCE_CHANGED = false
var DATABASE_PREFERENCE_CHANGED = false
}
}

View File

@@ -30,8 +30,8 @@ import androidx.preference.PreferenceCategory
import androidx.preference.SwitchPreference
import com.kunzisoft.androidclearchroma.ChromaUtil
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.legacy.DatabaseRetrieval
import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment
import com.kunzisoft.keepass.activities.legacy.DatabaseRetrieval
import com.kunzisoft.keepass.activities.legacy.resetAppTimeoutWhenViewTouchedOrFocused
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine
@@ -169,29 +169,20 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment(), DatabaseRetriev
// Database default username
dbDefaultUsernamePref = findPreference(getString(R.string.database_default_username_key))
if (database.allowDefaultUsername) {
dbDefaultUsernamePref?.summary = database.defaultUsername
} else {
dbDefaultUsernamePref?.isEnabled = false
// TODO dbGeneralPrefCategory?.removePreference(dbDefaultUsername)
}
// Database custom color
dbCustomColorPref = findPreference(getString(R.string.database_custom_color_key))
if (database.allowCustomColor) {
dbCustomColorPref?.apply {
try {
color = Color.parseColor(database.customColor)
summary = database.customColor
} catch (e: Exception) {
val customColor = database.customColor
if (customColor != null) {
color = customColor
summary = ChromaUtil.getFormattedColorString(customColor, false)
} else{
color = DialogColorPreference.DISABLE_COLOR
summary = ""
}
}
} else {
dbCustomColorPref?.isEnabled = false
// TODO dbGeneralPrefCategory?.removePreference(dbCustomColorPref)
}
// Version
findPreference<Preference>(getString(R.string.database_version_key))
@@ -352,12 +343,13 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment(), DatabaseRetriev
}
}
private val colorSelectedListener: ((Boolean, Int)-> Unit) = { enable, color ->
dbCustomColorPref?.summary = ChromaUtil.getFormattedColorString(color, false)
if (enable) {
private val colorSelectedListener: ((Int?)-> Unit) = { color ->
if (color != null) {
dbCustomColorPref?.color = color
dbCustomColorPref?.summary = ChromaUtil.getFormattedColorString(color, false)
} else {
dbCustomColorPref?.color = DialogColorPreference.DISABLE_COLOR
dbCustomColorPref?.summary = ""
}
}
@@ -430,7 +422,7 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment(), DatabaseRetriev
if (result.isSuccess) {
newColor
} else {
mDatabase?.customColor = oldColor
mDatabase?.customColor = Color.parseColor(oldColor)
oldColor
}
dbCustomColorPref?.summary = defaultColorToShow
@@ -689,6 +681,27 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment(), DatabaseRetriev
}
}
override fun onPreferenceTreeClick(preference: Preference?): Boolean {
// To reload group when database settings are modified
when (preference?.key) {
getString(R.string.database_name_key),
getString(R.string.database_description_key),
getString(R.string.database_default_username_key),
getString(R.string.database_custom_color_key),
getString(R.string.database_data_compression_key),
getString(R.string.database_data_remove_unlinked_attachments_key),
getString(R.string.recycle_bin_enable_key),
getString(R.string.recycle_bin_group_key),
getString(R.string.templates_group_enable_key),
getString(R.string.templates_group_uuid_key),
getString(R.string.max_history_items_key),
getString(R.string.max_history_size_key) -> {
NestedAppSettingsFragment.DATABASE_PREFERENCE_CHANGED = true
}
}
return super.onPreferenceTreeClick(preference)
}
companion object {
private const val TAG_PREF_FRAGMENT = "TAG_PREF_FRAGMENT"
}

View File

@@ -41,7 +41,7 @@ class DialogColorPreference @JvmOverloads constructor(context: Context,
}
override fun getDialogLayoutResource(): Int {
return R.layout.pref_dialog_input_color
return R.layout.fragment_color_picker
}
companion object {

View File

@@ -30,27 +30,57 @@ import android.view.Window
import android.widget.CompoundButton
import androidx.annotation.ColorInt
import androidx.appcompat.app.AlertDialog
import com.kunzisoft.androidclearchroma.ChromaUtil
import com.kunzisoft.androidclearchroma.IndicatorMode
import com.kunzisoft.androidclearchroma.colormode.ColorMode
import com.kunzisoft.androidclearchroma.fragment.ChromaColorFragment
import com.kunzisoft.androidclearchroma.fragment.ChromaColorFragment.*
import com.kunzisoft.androidclearchroma.view.ChromaColorView
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.ColorPickerDialogFragment
import com.kunzisoft.keepass.database.element.Database
class DatabaseColorPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat() {
private lateinit var rootView: View
private lateinit var enableSwitchView: CompoundButton
private var chromaColorFragment: ChromaColorFragment? = null
private lateinit var chromaColorView: ChromaColorView
var onColorSelectedListener: ((enable: Boolean, color: Int) -> Unit)? = null
var onColorSelectedListener: ((color: Int?) -> Unit)? = null
private var mDefaultColor = Color.WHITE
private var mActivated = false
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val alertDialogBuilder = AlertDialog.Builder(requireActivity())
rootView = requireActivity().layoutInflater.inflate(R.layout.pref_dialog_input_color, null)
rootView = requireActivity().layoutInflater.inflate(R.layout.fragment_color_picker, null)
enableSwitchView = rootView.findViewById(R.id.switch_element)
chromaColorView = rootView.findViewById(R.id.chroma_color_view)
if (savedInstanceState != null) {
if (savedInstanceState.containsKey(ARG_INITIAL_COLOR)) {
mDefaultColor = savedInstanceState.getInt(ARG_INITIAL_COLOR)
}
if (savedInstanceState.containsKey(ARG_ACTIVATED)) {
mActivated = savedInstanceState.getBoolean(ARG_ACTIVATED)
}
} else {
arguments?.apply {
if (containsKey(ARG_INITIAL_COLOR)) {
mDefaultColor = getInt(ARG_INITIAL_COLOR)
}
if (containsKey(ARG_ACTIVATED)) {
mActivated = getBoolean(ARG_ACTIVATED)
}
}
}
enableSwitchView.isChecked = mActivated
chromaColorView.currentColor = mDefaultColor
chromaColorView.setOnColorChangedListener {
if (!enableSwitchView.isChecked)
enableSwitchView.isChecked = true
}
alertDialogBuilder.setPositiveButton(android.R.string.ok) { _, _ ->
onDialogClosed(true)
@@ -68,8 +98,6 @@ class DatabaseColorPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialog
// request a window without the title
dialog.window?.requestFeature(Window.FEATURE_NO_TITLE)
dialog.setOnShowListener { measureLayout(it as Dialog) }
return dialog
}
@@ -77,73 +105,48 @@ class DatabaseColorPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialog
super.onDatabaseRetrieved(database)
database?.let {
val initColor = try {
var initColor = it.customColor
if (initColor != null) {
enableSwitchView.isChecked = true
Color.parseColor(it.customColor)
} catch (e: Exception) {
} else {
enableSwitchView.isChecked = false
DEFAULT_COLOR
initColor = DEFAULT_COLOR
}
chromaColorView.currentColor = initColor
arguments?.putInt(ARG_INITIAL_COLOR, initColor)
}
val fragmentManager = childFragmentManager
chromaColorFragment = fragmentManager.findFragmentByTag(TAG_FRAGMENT_COLORS) as ChromaColorFragment?
if (chromaColorFragment == null) {
chromaColorFragment = newInstance(arguments)
fragmentManager.beginTransaction().apply {
add(com.kunzisoft.androidclearchroma.R.id.color_dialog_container, chromaColorFragment!!, TAG_FRAGMENT_COLORS)
commit()
}
}
}
override fun onDialogClosed(database: Database?, positiveResult: Boolean) {
super.onDialogClosed(database, positiveResult)
if (positiveResult) {
val customColorEnable = enableSwitchView.isChecked
chromaColorFragment?.currentColor?.let { currentColor ->
onColorSelectedListener?.invoke(customColorEnable, currentColor)
val newColor: Int? = if (enableSwitchView.isChecked)
chromaColorView.currentColor
else
null
onColorSelectedListener?.invoke(newColor)
database?.let {
val newColor = if (customColorEnable) {
ChromaUtil.getFormattedColorString(currentColor, false)
} else {
""
}
val oldColor = database.customColor
database.customColor = newColor
saveColor(oldColor, newColor)
}
}
}
}
/**
* Set new dimensions to dialog
* @param ad dialog
*/
private fun measureLayout(ad: Dialog) {
val typedValue = TypedValue()
resources.getValue(com.kunzisoft.androidclearchroma.R.dimen.chroma_dialog_height_multiplier, typedValue, true)
val heightMultiplier = typedValue.float
val height = (ad.context.resources.displayMetrics.heightPixels * heightMultiplier).toInt()
resources.getValue(com.kunzisoft.androidclearchroma.R.dimen.chroma_dialog_width_multiplier, typedValue, true)
val widthMultiplier = typedValue.float
val width = (ad.context.resources.displayMetrics.widthPixels * widthMultiplier).toInt()
ad.window?.setLayout(width, height)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreateView(inflater, container, savedInstanceState)
return rootView
}
companion object {
private const val TAG_FRAGMENT_COLORS = "TAG_FRAGMENT_COLORS"
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putInt(ARG_INITIAL_COLOR, chromaColorView.currentColor)
outState.putBoolean(ARG_ACTIVATED, mActivated)
}
companion object {
private const val ARG_INITIAL_COLOR = "ARG_INITIAL_COLOR"
private const val ARG_ACTIVATED = "ARG_ACTIVATED"
@ColorInt
const val DEFAULT_COLOR: Int = Color.WHITE
@@ -151,9 +154,7 @@ class DatabaseColorPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialog
val fragment = DatabaseColorPreferenceDialogFragmentCompat()
val bundle = Bundle(1)
bundle.putString(ARG_KEY, key)
bundle.putInt(ARG_INITIAL_COLOR, Color.BLACK)
bundle.putInt(ARG_COLOR_MODE, ColorMode.RGB.ordinal)
bundle.putInt(ARG_INDICATOR_MODE, IndicatorMode.HEX.ordinal)
bundle.putInt(ARG_INITIAL_COLOR, DEFAULT_COLOR)
fragment.arguments = bundle
return fragment

View File

@@ -22,6 +22,7 @@ package com.kunzisoft.keepass.settings.preferencedialogfragment
import android.content.Context
import android.os.Bundle
import androidx.fragment.app.activityViewModels
import com.kunzisoft.androidclearchroma.ChromaUtil
import com.kunzisoft.keepass.activities.legacy.DatabaseRetrieval
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine
@@ -76,9 +77,17 @@ abstract class DatabaseSavePreferenceDialogFragmentCompat
// To inherit to save element in database
}
protected fun saveColor(oldColor: String,
newColor: String) {
mDatabaseViewModel.saveColor(oldColor, newColor, mDatabaseAutoSaveEnable)
protected fun saveColor(oldColor: Int?,
newColor: Int?) {
val oldColorString = if (oldColor != null)
ChromaUtil.getFormattedColorString(oldColor, false)
else
""
val newColorString = if (newColor != null)
ChromaUtil.getFormattedColorString(newColor, false)
else
""
mDatabaseViewModel.saveColor(oldColorString, newColorString, mDatabaseAutoSaveEnable)
}
protected fun saveCompression(oldCompression: CompressionAlgorithm,

View File

@@ -46,6 +46,10 @@ abstract class TemplateAbstractView<
protected var headerContainerView: ViewGroup
protected var entryIconView: ImageView
protected var backgroundColorView: View
protected var foregroundColorView: View
protected var backgroundColorButton: ImageView
protected var foregroundColorButton: ImageView
private var titleContainerView: ViewGroup
protected var templateContainerView: ViewGroup
private var customFieldsContainerView: SectionView
@@ -57,6 +61,10 @@ abstract class TemplateAbstractView<
headerContainerView = findViewById(R.id.template_header_container)
entryIconView = findViewById(R.id.template_icon_button)
backgroundColorView = findViewById(R.id.template_background_color)
foregroundColorView = findViewById(R.id.template_foreground_color)
backgroundColorButton = findViewById(R.id.template_background_color_button)
foregroundColorButton = findViewById(R.id.template_foreground_color_button)
titleContainerView = findViewById(R.id.template_title_container)
templateContainerView = findViewById(R.id.template_fields_container)
// To fix card view margin below Marshmallow

View File

@@ -5,13 +5,17 @@ import android.os.Build
import android.util.AttributeSet
import android.view.View
import androidx.annotation.IdRes
import androidx.core.graphics.BlendModeColorFilterCompat
import androidx.core.graphics.BlendModeCompat
import androidx.core.view.isVisible
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.Field
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.database.element.template.*
import com.kunzisoft.keepass.database.element.template.TemplateAttribute
import com.kunzisoft.keepass.database.element.template.TemplateAttributeAction
import com.kunzisoft.keepass.database.element.template.TemplateField
import com.kunzisoft.keepass.otp.OtpEntryFields
import org.joda.time.DateTime
@@ -51,7 +55,53 @@ class TemplateEditView @JvmOverloads constructor(context: Context,
fun setIcon(iconImage: IconImage) {
mEntryInfo?.icon = iconImage
populateIconMethod?.invoke(entryIconView, iconImage)
refreshIcon()
}
fun setOnBackgroundColorClickListener(onClickListener: OnClickListener) {
backgroundColorButton.setOnClickListener(onClickListener)
}
fun getBackgroundColor(): Int? {
return mEntryInfo?.backgroundColor
}
fun setBackgroundColor(color: Int?) {
applyBackgroundColor(color)
mEntryInfo?.backgroundColor = color
}
private fun applyBackgroundColor(color: Int?) {
if (color != null) {
backgroundColorView.background.colorFilter = BlendModeColorFilterCompat
.createBlendModeColorFilterCompat(color, BlendModeCompat.SRC_ATOP)
backgroundColorView.visibility = View.VISIBLE
} else {
backgroundColorView.visibility = View.GONE
}
}
fun setOnForegroundColorClickListener(onClickListener: OnClickListener) {
foregroundColorButton.setOnClickListener(onClickListener)
}
fun getForegroundColor(): Int? {
return mEntryInfo?.foregroundColor
}
fun setForegroundColor(color: Int?) {
applyForegroundColor(color)
mEntryInfo?.foregroundColor = color
}
private fun applyForegroundColor(color: Int?) {
if (color != null) {
foregroundColorView.background.colorFilter = BlendModeColorFilterCompat
.createBlendModeColorFilterCompat(color, BlendModeCompat.SRC_ATOP)
foregroundColorView.visibility = View.VISIBLE
} else {
foregroundColorView.visibility = View.GONE
}
}
override fun preProcessTemplate() {
@@ -196,6 +246,8 @@ class TemplateEditView @JvmOverloads constructor(context: Context,
override fun populateViewsWithEntryInfo(showEmptyFields: Boolean): List<ViewField> {
refreshIcon()
applyBackgroundColor(mEntryInfo?.backgroundColor)
applyForegroundColor(mEntryInfo?.foregroundColor)
return super.populateViewsWithEntryInfo(showEmptyFields)
}

View File

@@ -23,9 +23,7 @@ import android.animation.Animator
import android.animation.AnimatorSet
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Typeface
import android.graphics.*
import android.text.Selection
import android.text.Spannable
import android.text.SpannableString
@@ -37,6 +35,7 @@ import android.view.View
import android.view.animation.AccelerateDecelerateInterpolator
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.isVisible
import androidx.core.view.updatePadding
@@ -44,6 +43,16 @@ import com.google.android.material.snackbar.Snackbar
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable
import androidx.appcompat.view.menu.ActionMenuItemView
import android.widget.ImageView
import androidx.appcompat.widget.ActionMenuView
import androidx.core.graphics.drawable.DrawableCompat
import android.graphics.drawable.Drawable
import com.google.android.material.appbar.CollapsingToolbarLayout
/**
* Replace font by monospace, must be called after setText()
@@ -208,3 +217,49 @@ fun CoordinatorLayout.showActionErrorIfNeeded(result: ActionRunnable.Result) {
}
}
}
fun Toolbar.changeControlColor(color: Int) {
val colorFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP);
for (i in 0 until childCount) {
val view: View = getChildAt(i)
// Change the color of back button (or open drawer button).
if (view is ImageView) {
//Action Bar back button
view.drawable.colorFilter = colorFilter
}
if (view is ActionMenuView) {
view.post {
for (j in 0 until view.childCount) {
// Change the color of any ActionMenuViews - icons that
// are not back button, nor text, nor overflow menu icon.
val innerView: View = view.getChildAt(j)
if (innerView is ActionMenuItemView) {
innerView.compoundDrawables.forEach { drawable ->
//Important to set the color filter in separate thread,
//by adding it to the message queue
//Won't work otherwise.
drawable?.colorFilter = colorFilter
}
}
}
}
}
}
// Change the color of title and subtitle.
setTitleTextColor(color)
setSubtitleTextColor(color)
// Change the color of the Overflow Menu icon.
var drawable: Drawable? = overflowIcon
if (drawable != null) {
drawable = DrawableCompat.wrap(drawable)
DrawableCompat.setTint(drawable.mutate(), color)
overflowIcon = drawable
}
invalidate()
}
fun CollapsingToolbarLayout.changeTitleColor(color: Int) {
setCollapsedTitleTextColor(color)
setExpandedTitleColor(color)
invalidate()
}

View File

@@ -0,0 +1,15 @@
package com.kunzisoft.keepass.viewmodels
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
class ColorPickerViewModel: ViewModel() {
val colorPicked : LiveData<Int?> get() = _colorPicked
private val _colorPicked = MutableLiveData<Int?>()
fun pickColor(color: Int?) {
_colorPicked.value = color
}
}

View File

@@ -14,6 +14,14 @@ abstract class NodeEditViewModel : ViewModel() {
val onIconSelected : LiveData<IconImage> get() = _onIconSelected
private val _onIconSelected = SingleLiveEvent<IconImage>()
private var mColorRequest: ColorRequest = ColorRequest.BACKGROUND
val requestColorSelection : LiveData<Int?> get() = _requestColorSelection
private val _requestColorSelection = SingleLiveEvent<Int?>()
val onBackgroundColorSelected : LiveData<Int?> get() = _onBackgroundColorSelected
private val _onBackgroundColorSelected = SingleLiveEvent<Int?>()
val onForegroundColorSelected : LiveData<Int?> get() = _onForegroundColorSelected
private val _onForegroundColorSelected = SingleLiveEvent<Int?>()
val requestDateTimeSelection : LiveData<DateInstant> get() = _requestDateTimeSelection
private val _requestDateTimeSelection = SingleLiveEvent<DateInstant>()
val onDateSelected : LiveData<DataDate> get() = _onDateSelected
@@ -29,6 +37,23 @@ abstract class NodeEditViewModel : ViewModel() {
_onIconSelected.value = iconImage
}
fun requestBackgroundColorSelection(initialColor: Int?) {
mColorRequest = ColorRequest.BACKGROUND
_requestColorSelection.value = initialColor
}
fun requestForegroundColorSelection(initialColor: Int?) {
mColorRequest = ColorRequest.FOREGROUND
_requestColorSelection.value = initialColor
}
fun selectColor(color: Int?) {
when (mColorRequest) {
ColorRequest.BACKGROUND -> _onBackgroundColorSelected.value = color
ColorRequest.FOREGROUND -> _onForegroundColorSelected.value = color
}
}
fun requestDateTimeSelection(dateInstant: DateInstant) {
_requestDateTimeSelection.value = dateInstant
}
@@ -40,4 +65,8 @@ abstract class NodeEditViewModel : ViewModel() {
fun selectTime(hours: Int, minutes: Int) {
_onTimeSelected.value = DataTime(hours, minutes)
}
private enum class ColorRequest {
BACKGROUND, FOREGROUND
}
}

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<group>
<path
android:fillColor="#ffffff"
android:strokeWidth="0.99630105"
android:pathData="M 10,4 C 10,4 3.4004241,12.16731 4.0449219,16 4.4765841,18.566843 7.4158222,21 10,21 12.584196,21 15.523434,18.566843 15.955078,16 16.599576,12.16731 10,4 10,4 Z m 4,0 c 0,0 -0.59708,0.7527282 -1.386719,1.8476562 0.462543,0.6601114 0.953468,1.3798579 1.455078,2.1855469 0.803823,1.2911023 1.594188,2.7131219 2.15625,4.1015629 0.562116,1.388441 0.935799,2.740544 0.71875,4.03125 -0.268817,1.598597 -1.257809,3.011777 -2.517578,4.064453 -0.31833,0.265996 -0.657402,0.509805 -1.009765,0.726562 C 13.611687,20.98319 13.807101,21 14,21 16.584196,21 19.523434,18.566843 19.955078,16 20.599576,12.16731 14,4 14,4 Z m -4,2.5 c 0,0 0.497912,0.6283173 1.144531,1.53125 C 9.4508061,10.655343 7.697485,13.933867 8.0449219,16 8.262611,17.29447 9.126046,18.542028 10.253906,19.470703 10.169439,19.477241 10.083657,19.5 10,19.5 8.0238755,19.5 5.7754103,17.638661 5.4453125,15.675781 4.9524592,12.744903 10,6.5 10,6.5 Z" />
</group>
</vector>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<group>
<path
android:fillColor="#ffffff"
android:strokeWidth="0.99630105"
android:pathData="M 10,4 C 10,4 3.4004241,12.16731 4.0449219,16 4.4765841,18.566843 7.4158222,21 10,21 12.584196,21 15.523434,18.566843 15.955078,16 16.599576,12.16731 10,4 10,4 Z m 4,0 c 0,0 -0.597156,0.7527282 -1.386719,1.8476562 0.284669,0.4062608 0.575588,0.8441093 0.882813,1.3125 C 13.714676,6.8748215 14,6.5 14,6.5 c 0,0 5.04754,6.244903 4.554688,9.175781 -0.249068,1.481044 -1.591665,2.900275 -3.083985,3.509766 -0.31745,0.376905 -0.666337,0.728573 -1.044922,1.044922 -0.31833,0.265996 -0.657402,0.509805 -1.009765,0.726562 C 13.61168,20.98319 13.807101,21 14,21 16.584196,21 19.523434,18.566843 19.955078,16 20.599576,12.16731 14,4 14,4 Z" />
</group>
</vector>

View File

@@ -29,7 +29,8 @@
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="@dimen/toolbar_parallax_height">
android:layout_height="@dimen/toolbar_parallax_height"
android:background="?attr/colorPrimary">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/toolbar_layout"

View File

@@ -39,12 +39,26 @@
android:layout_below="@+id/special_mode_view"
android:background="?attr/colorPrimary"
android:theme="?attr/toolbarAppearance" >
<LinearLayout
android:id="@+id/database_name_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:id="@+id/database_color"
android:layout_width="12dp"
android:layout_height="12dp"
android:src="@drawable/background_rounded_square"
android:layout_marginRight="12dp"
android:layout_marginEnd="12dp"
android:contentDescription="@string/content_description_database_color"/>
<TextView
android:id="@+id/database_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="Database"
style="@style/KeepassDXStyle.TextAppearance.Title.TextOnPrimary" />
</LinearLayout>
<RelativeLayout
android:id="@+id/search_container"
android:layout_width="wrap_content"

View File

@@ -44,7 +44,8 @@
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:background="?attr/colorPrimary">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/toolbar_layout"
@@ -220,6 +221,7 @@
android:id="@+id/activity_password_info_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/activity_password_info_text"

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2019 Jeremy Jamet / Kunzisoft.
Copyright 2022 Jeremy Jamet / Kunzisoft.
This file is part of KeePassDX.
@@ -17,8 +17,7 @@
You should have received a copy of the GNU General Public License
along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
-->
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/edit"
@@ -27,10 +26,13 @@
android:layout_height="match_parent"
android:importantForAutofill="noExcludeDescendants"
tools:targetApi="o">
<FrameLayout
android:id="@+id/color_dialog_container"
<com.kunzisoft.androidclearchroma.view.ChromaColorView
android:id="@+id/chroma_color_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="300dp"
app:chromaInitialColor="@color/white"
app:chromaColorMode="RGB"
app:chromaIndicatorMode="HEX"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
@@ -41,7 +43,7 @@
android:layout_margin="20dp"
android:text="@string/enable"
android:background="@drawable/background_button_small"
android:textColor="?attr/textColorInverse"
android:textColor="@color/white"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:minHeight="48dp"/>

View File

@@ -22,7 +22,7 @@
android:id="@+id/icon_container"
android:layout_width="match_parent"
android:layout_height="80dp"
android:background="@drawable/background_item_selection">
style="@style/KeepassDXStyle.Selectable.Item">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/icon_image"
android:layout_width="wrap_content"

View File

@@ -27,52 +27,53 @@
style="@style/KeepassDXStyle.Selectable.Item">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/constraintLayout"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:background="?android:attr/selectableItemBackground"
android:minHeight="48dp"
app:layout_constraintWidth_percent="@dimen/content_percent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:background="?android:attr/selectableItemBackground" >
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_percent="@dimen/content_percent">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/node_icon"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginTop="@dimen/image_list_margin_horizontal"
android:layout_marginBottom="@dimen/image_list_margin_horizontal"
android:layout_marginLeft="@dimen/image_list_margin_vertical"
android:layout_marginStart="@dimen/image_list_margin_vertical"
android:layout_marginRight="@dimen/image_list_margin_vertical"
android:layout_marginEnd="@dimen/image_list_margin_vertical"
android:layout_gravity="center"
android:layout_marginStart="@dimen/image_list_margin_vertical"
android:layout_marginLeft="@dimen/image_list_margin_vertical"
android:layout_marginTop="@dimen/image_list_margin_horizontal"
android:layout_marginEnd="@dimen/image_list_margin_vertical"
android:layout_marginRight="@dimen/image_list_margin_vertical"
android:layout_marginBottom="@dimen/image_list_margin_horizontal"
android:scaleType="fitXY"
android:src="@drawable/ic_blank_32dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintLeft_toLeftOf="parent" />
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:id="@+id/node_container_info"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:layout_marginStart="@dimen/image_list_margin_vertical"
android:layout_marginLeft="@dimen/image_list_margin_vertical"
android:layout_marginEnd="12dp"
android:layout_marginRight="12dp"
android:orientation="vertical"
app:layout_constraintTop_toTopOf="parent"
android:paddingTop="4dp"
android:paddingBottom="4dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/node_icon"
app:layout_constraintLeft_toRightOf="@+id/node_icon"
app:layout_constraintEnd_toStartOf="@+id/node_options"
app:layout_constraintRight_toLeftOf="@+id/node_options">
app:layout_constraintLeft_toRightOf="@+id/node_icon"
app:layout_constraintRight_toLeftOf="@+id/node_options"
app:layout_constraintStart_toEndOf="@+id/node_icon"
app:layout_constraintTop_toTopOf="parent">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/node_text"
@@ -116,26 +117,26 @@
android:id="@+id/node_options"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="end"
android:layout_marginStart="12dp"
android:layout_marginLeft="12dp"
app:layout_constraintTop_toTopOf="parent"
android:gravity="end"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent">
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent">
<LinearLayout
android:id="@+id/node_otp_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:layout_marginRight="12dp"
android:padding="4dp"
android:orientation="horizontal"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:background="?android:attr/selectableItemBackground"
app:layout_constraintTop_toTopOf="parent"
android:orientation="horizontal"
android:padding="4dp"
app:layout_constraintBottom_toTopOf="@+id/node_attachment_icon"
app:layout_constraintEnd_toEndOf="parent">
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/node_otp_token"
@@ -144,20 +145,22 @@
android:layout_height="wrap_content"
android:layout_gravity="center"
tools:text="5136" />
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="4dp"
android:layout_marginLeft="4dp">
<ProgressBar
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/node_otp_progress"
style="@style/KeepassDXStyle.ProgressBar.Circle.Secondary"
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_gravity="center"
android:max="100"
android:progress="60" />
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:indicatorSize="16dp"
app:trackThickness="2dp"
app:indicatorDirectionCircular="counterclockwise"
android:layout_gravity="center" />
</FrameLayout>
</LinearLayout>
@@ -179,12 +182,12 @@
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?android:attr/listDivider"
android:layout_marginLeft="72dp"
android:layout_marginStart="72dp"
android:layout_marginRight="48dp"
android:layout_marginLeft="72dp"
android:layout_marginEnd="48dp"
app:layout_constraintBottom_toBottomOf="parent"/>
android:layout_marginRight="48dp"
android:background="?android:attr/listDivider"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -17,9 +17,9 @@
You should have received a copy of the GNU General Public License
along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:targetApi="o"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -29,12 +29,51 @@
android:id="@+id/template_header_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?attr/cardViewStyle"
android:visibility="gone">
style="?attr/cardViewStyle">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<View
android:id="@+id/template_background_color"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_margin="9dp"
android:layout_gravity="top|end"
android:background="@drawable/background_rounded_square" />
<View
android:id="@+id/template_foreground_color"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_margin="32dp"
android:layout_gravity="top|end"
android:background="@drawable/background_rounded_square" />
<ImageView
android:id="@+id/template_background_color_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|end"
android:layout_margin="3dp"
android:src="@drawable/ic_color_background_white_24dp"
android:contentDescription="@string/content_description_entry_background_color"
style="@style/KeepassDXStyle.ImageButton.Simple.Mini" />
<ImageView
android:id="@+id/template_foreground_color_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|end"
android:layout_margin="25dp"
android:src="@drawable/ic_color_foreground_white_24dp"
android:contentDescription="@string/content_description_entry_background_color"
style="@style/KeepassDXStyle.ImageButton.Simple.Mini" />
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="@dimen/card_view_padding">
<!-- Icon -->
@@ -42,10 +81,10 @@
android:id="@+id/template_icon_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:elevation="8dp"
android:layout_gravity="center_horizontal"
android:src="@drawable/ic_blank_32dp"
android:contentDescription="@string/content_description_entry_icon"
android:layout_gravity="center"/>
android:contentDescription="@string/content_description_entry_icon"/>
<!-- Title -->
<FrameLayout
@@ -53,6 +92,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</FrameLayout>
</androidx.cardview.widget.CardView>
<LinearLayout

View File

@@ -27,6 +27,7 @@
<attr name="cardBackgroundTransparentColor" format="reference|color" />
<attr name="iconPreferenceColor" format="reference|color" />
<attr name="textColorInverse" format="reference|color" />
<attr name="toolbarColorControl" format="reference|color" />
<attr name="educationCircleColor" format="reference|color" />
<attr name="educationTextColor" format="reference|color" />

View File

@@ -38,7 +38,7 @@
<color name="black_lighter">#101010</color>
<color name="black_light">#0e0e0e</color>
<color name="black_light_transparent">#E00E0E0E</color>
<color name="black">#000000</color>
<color name="black">#000001</color> <!-- #000001 To fix refresh theme -->
<color name="black_transparent">#E0000000</color>
<color name="grey_blue">#3c474c</color>

View File

@@ -22,6 +22,7 @@
<dimen name="image_button_margin">8dp</dimen>
<dimen name="image_list_margin_vertical">22dp</dimen>
<dimen name="image_list_margin_horizontal">8dp</dimen>
<dimen name="image_list_margin_vertical_back">15dp</dimen>
<dimen name="image_preview_height">144dp</dimen>
<dimen name="button_margin">6dp</dimen>
<dimen name="card_view_margin_horizontal">10dp</dimen>

View File

@@ -58,6 +58,9 @@
<string name="content_description_keyfile_checkbox">Keyfile checkbox</string>
<string name="content_description_repeat_toggle_password_visibility">Repeat toggle password visibility</string>
<string name="content_description_entry_icon">Entry icon</string>
<string name="content_description_database_color">Database color</string>
<string name="content_description_entry_foreground_color">Entry foreground color</string>
<string name="content_description_entry_background_color">Entry background color</string>
<string name="validate">Validate</string>
<string name="discard_changes">Discard changes?</string>
<string name="discard">Discard</string>

View File

@@ -31,6 +31,7 @@
<item name="android:textColorPrimary">@color/cyan_lightest</item>
<item name="android:textColorHintInverse">@color/cyan_lightest</item>
<item name="android:windowBackground">@color/white_darkest</item>
<item name="toolbarColorControl">@color/grey_light</item>
<item name="toolbarAppearance">@style/KeepassDXStyle.Toolbar.Clear</item>
<item name="toolbarHomeAppearance">@style/KeepassDXStyle.Toolbar.Home.Clear</item>
<item name="toolbarSpecialAppearance">@style/KeepassDXStyle.Toolbar.Special.Clear</item>

View File

@@ -33,6 +33,7 @@
<item name="android:textColorPrimary">@color/green_light</item>
<item name="android:textColorHintInverse">@color/green_light</item>
<item name="android:windowBackground">@color/white</item>
<item name="toolbarColorControl">@color/grey_light</item>
<item name="toolbarAppearance">@style/KeepassDXStyle.Toolbar.White</item>
<item name="toolbarHomeAppearance">@style/KeepassDXStyle.Toolbar.Home.White</item>
<item name="toolbarSpecialAppearance">@style/KeepassDXStyle.Toolbar.Special.White</item>

View File

@@ -101,6 +101,7 @@
<item name="android:textColorPrimary">@color/purple_lighter</item>
<item name="android:textColorHintInverse">@color/purple_lighter</item>
<item name="android:windowBackground">@color/black_purple_dark</item>
<item name="toolbarColorControl">@color/white</item>
<item name="toolbarAppearance">@style/KeepassDXStyle.Toolbar.Purple.Dark</item>
<item name="toolbarHomeAppearance">@style/KeepassDXStyle.Toolbar.Home.Purple.Dark</item>
<item name="toolbarSpecialAppearance">@style/KeepassDXStyle.Toolbar.Special.Purple.Dark</item>

View File

@@ -29,6 +29,7 @@
<item name="android:textColorPrimary">@color/blue_light</item>
<item name="android:textColorHintInverse">@color/blue_lighter</item>
<item name="android:windowBackground">@color/white_dark</item>
<item name="toolbarColorControl">@color/white</item>
<item name="toolbarAppearance">@style/KeepassDXStyle.Toolbar.Blue</item>
<item name="toolbarHomeAppearance">@style/KeepassDXStyle.Toolbar.Home.Blue</item>
<item name="toolbarSpecialAppearance">@style/KeepassDXStyle.Toolbar.Special.Blue</item>

View File

@@ -29,6 +29,7 @@
<item name="android:textColorPrimary">@color/red</item>
<item name="android:textColorHintInverse">@color/red_lighter</item>
<item name="android:windowBackground">@color/white_dark</item>
<item name="toolbarColorControl">@color/white</item>
<item name="toolbarAppearance">@style/KeepassDXStyle.Toolbar.Red</item>
<item name="toolbarHomeAppearance">@style/KeepassDXStyle.Toolbar.Home.Red</item>
<item name="toolbarSpecialAppearance">@style/KeepassDXStyle.Toolbar.Special.Red</item>

View File

@@ -78,6 +78,7 @@
<!-- Toolbar -->
<item name="toolbarStyle">@style/Widget.AppCompat.Toolbar</item>
<item name="toolbarColorControl">@color/white</item>
<item name="toolbarAppearance">@style/KeepassDXStyle.Toolbar.Light</item>
<item name="toolbarHomeAppearance">@style/KeepassDXStyle.Toolbar.Home.Light</item>
<item name="toolbarSpecialAppearance">@style/KeepassDXStyle.Toolbar.Special.Light</item>
@@ -141,6 +142,7 @@
<!-- Toolbar -->
<item name="toolbarStyle">@style/Widget.AppCompat.Toolbar</item>
<item name="toolbarColorControl">@color/white</item>
<item name="toolbarAppearance">@style/KeepassDXStyle.Toolbar.Night</item>
<item name="toolbarHomeAppearance">@style/KeepassDXStyle.Toolbar.Home.Night</item>
<item name="toolbarSpecialAppearance">@style/KeepassDXStyle.Toolbar.Special.Night</item>
@@ -357,7 +359,7 @@
<style name="KeepassDXStyle.TextAppearance.Info" parent="KeepassDXStyle.TextAppearance">
<item name="android:textSize">12sp</item>
<item name="android:textColor">@color/white</item>
<item name="android:background">@drawable/background_text_info</item>
<item name="android:background">@drawable/background_rounded_square</item>
<item name="backgroundTint">?attr/colorAccent</item>
</style>
@@ -436,6 +438,13 @@
<item name="android:colorForeground">@color/background_button_color_secondary</item>
<item name="tint">@color/background_button_color_secondary</item>
</style>
<style name="KeepassDXStyle.ImageButton.Simple.Mini" parent="KeepassDXStyle.ImageButton.Simple">
<item name="android:padding">12dp</item>
<item name="android:layout_width">36dp</item>
<item name="android:layout_height">36dp</item>
<item name="android:colorForeground">@color/background_button_color_secondary</item>
<item name="tint">?android:attr/textColor</item>
</style>
<!-- Item Style -->
<style name="KeepassDXStyle.Selectable.Item" parent="KeepassDXStyle.Light">
@@ -492,10 +501,6 @@
</style>
<!-- Progress bar -->
<style name="KeepassDXStyle.ProgressBar.Circle.Secondary" parent="Widget.AppCompat.ProgressBar.Horizontal">
<item name="android:progressDrawable">@drawable/foreground_progress_circle_secondary</item>
<item name="android:background">@null</item>
</style>
<style name="KeepassDXStyle.ProgressBar.Circle" parent="Widget.AppCompat.ProgressBar.Horizontal">
<item name="android:progressDrawable">@drawable/foreground_progress_circle</item>
<item name="android:background">@drawable/background_progress_circle</item>

View File

@@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24"
height="24"
id="svg4830"
version="1.1"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
inkscape:export-filename="/home/joker/Project/Scratcheck/TestExport.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90"
sodipodi:docname="ic_color_background.svg">
<defs
id="defs4832" />
<sodipodi:namedview
id="base"
pagecolor="#acacac"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="22.627416"
inkscape:cx="10.190215"
inkscape:cy="15.444172"
inkscape:current-layer="g4770"
showgrid="true"
inkscape:grid-bbox="true"
inkscape:document-units="px"
inkscape:window-width="1920"
inkscape:window-height="1016"
inkscape:window-x="1920"
inkscape:window-y="27"
inkscape:window-maximized="1">
<sodipodi:guide
position="0.99999471,22.999999"
orientation="22,0"
id="guide2987"
inkscape:locked="false" />
<sodipodi:guide
position="0.99999471,0.99999888"
orientation="0,22"
id="guide2989"
inkscape:locked="false" />
<sodipodi:guide
position="22.999995,0.99999888"
orientation="-22,0"
id="guide2991"
inkscape:locked="false" />
<sodipodi:guide
position="39,23"
orientation="0,-22"
id="guide2993"
inkscape:locked="false" />
<inkscape:grid
type="xygrid"
id="grid2989" />
</sodipodi:namedview>
<metadata
id="metadata4835">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:label="Layer 1"
inkscape:groupmode="layer"
transform="translate(0,-8)">
<g
id="g4770"
transform="matrix(1.7777778,0,0,1.7777778,-205.48441,-31.997877)">
<g
id="Layer_1"
transform="matrix(-0.00397893,0,0,0.00397893,125.58386,23.674135)" />
<path
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.99630105px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 10,4 C 10,4 3.4004241,12.16731 4.0449219,16 4.4765841,18.566843 7.4158222,21 10,21 12.584196,21 15.523434,18.566843 15.955078,16 16.599576,12.16731 10,4 10,4 Z m 4,0 c 0,0 -0.59708,0.7527282 -1.386719,1.8476562 0.462543,0.6601114 0.953468,1.3798579 1.455078,2.1855469 0.803823,1.2911023 1.594188,2.7131219 2.15625,4.1015629 0.562116,1.388441 0.935799,2.740544 0.71875,4.03125 -0.268817,1.598597 -1.257809,3.011777 -2.517578,4.064453 -0.31833,0.265996 -0.657402,0.509805 -1.009765,0.726562 C 13.611687,20.98319 13.807101,21 14,21 16.584196,21 19.523434,18.566843 19.955078,16 20.599576,12.16731 14,4 14,4 Z m -4,2.5 c 0,0 0.497912,0.6283173 1.144531,1.53125 C 9.4508061,10.655343 7.697485,13.933867 8.0449219,16 8.262611,17.29447 9.126046,18.542028 10.253906,19.470703 10.169439,19.477241 10.083657,19.5 10,19.5 8.0238755,19.5 5.7754103,17.638661 5.4453125,15.675781 4.9524592,12.744903 10,6.5 10,6.5 Z"
transform="matrix(0.56249999,0,0,0.56249999,115.58498,22.498806)"
id="path830"
inkscape:connector-curvature="0" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24"
height="24"
id="svg4830"
version="1.1"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
inkscape:export-filename="/home/joker/Project/Scratcheck/TestExport.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90"
sodipodi:docname="ic_color_foreground.svg">
<defs
id="defs4832" />
<sodipodi:namedview
id="base"
pagecolor="#acacac"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="22.627416"
inkscape:cx="1.4839624"
inkscape:cy="15.444172"
inkscape:current-layer="g4770"
showgrid="true"
inkscape:grid-bbox="true"
inkscape:document-units="px"
inkscape:window-width="1920"
inkscape:window-height="1016"
inkscape:window-x="1920"
inkscape:window-y="27"
inkscape:window-maximized="1">
<sodipodi:guide
position="0.99999471,22.999999"
orientation="22,0"
id="guide2987"
inkscape:locked="false" />
<sodipodi:guide
position="0.99999471,0.99999888"
orientation="0,22"
id="guide2989"
inkscape:locked="false" />
<sodipodi:guide
position="22.999995,0.99999888"
orientation="-22,0"
id="guide2991"
inkscape:locked="false" />
<sodipodi:guide
position="39,23"
orientation="0,-22"
id="guide2993"
inkscape:locked="false" />
<inkscape:grid
type="xygrid"
id="grid2989" />
</sodipodi:namedview>
<metadata
id="metadata4835">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:label="Layer 1"
inkscape:groupmode="layer"
transform="translate(0,-8)">
<g
id="g4770"
transform="matrix(1.7777778,0,0,1.7777778,-205.48441,-31.997877)">
<g
id="Layer_1"
transform="matrix(-0.00397893,0,0,0.00397893,125.58386,23.674135)" />
<path
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.99630105px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 10,4 C 10,4 3.4004241,12.16731 4.0449219,16 4.4765841,18.566843 7.4158222,21 10,21 12.584196,21 15.523434,18.566843 15.955078,16 16.599576,12.16731 10,4 10,4 Z m 4,0 c 0,0 -0.597156,0.7527282 -1.386719,1.8476562 0.284669,0.4062608 0.575588,0.8441093 0.882813,1.3125 C 13.714676,6.8748215 14,6.5 14,6.5 c 0,0 5.04754,6.244903 4.554688,9.175781 -0.249068,1.481044 -1.591665,2.900275 -3.083985,3.509766 -0.31745,0.376905 -0.666337,0.728573 -1.044922,1.044922 -0.31833,0.265996 -0.657402,0.509805 -1.009765,0.726562 C 13.61168,20.98319 13.807101,21 14,21 16.584196,21 19.523434,18.566843 19.955078,16 20.599576,12.16731 14,4 14,4 Z"
transform="matrix(0.56249999,0,0,0.56249999,115.58498,22.498806)"
id="path820"
inkscape:connector-curvature="0" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -2,8 +2,8 @@
buildscript {
ext.kotlin_version = '1.6.10'
ext.android_core_version = '1.7.0'
ext.android_appcompat_version = '1.4.0'
ext.android_material_version = '1.4.0'
ext.android_appcompat_version = '1.4.1'
ext.android_material_version = '1.5.0'
ext.android_test_version = '1.4.0'
repositories {
mavenCentral()

View File

@@ -1,6 +1,8 @@
* Add breadcrumb
* Add path in search results #1148
* Add group info dialog #1177
* Manage colors #64 #913
* Fix UI in Android 8 #509
* Upgrade libs and SDK to 31 #833
* Stop asking WRITE_EXTERNAL_STORAGE permission
* Fix parser of database v1

View File

@@ -1,6 +1,8 @@
* Ajout d'un fil d'ariane
* Ajout du chemin pour les résultats de recherche #1148
* Ajout d'un dialogue pour les informations de groupe #1177
* Gestion des couleurs #64 #913
* Correction de l'interface utilisateur dans Android 8 #509
* Mise à jour des librairies et du SDK vers 31 #833
* Ne demande plus la permission WRITE_EXTERNAL_STORAGE permission
* Correction du parseur de la base de données v1