Merge branch 'release/3.1'

This commit is contained in:
J-Jamet
2022-01-18 12:44:44 +01:00
122 changed files with 2909 additions and 895 deletions

View File

@@ -1,3 +1,13 @@
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
* Fix parser of database v1 #1201
* Stop asking WRITE_EXTERNAL_STORAGE permission
KeePassDX(3.0.4)
* Fix autofill inline bugs #1173 #1165
* Small UI change

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

@@ -3,16 +3,16 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 30
buildToolsVersion "30.0.3"
compileSdkVersion 31
buildToolsVersion "31.0.0"
ndkVersion "21.4.7075529"
defaultConfig {
applicationId "com.kunzisoft.keepass"
minSdkVersion 15
targetSdkVersion 30
versionCode = 91
versionName = "3.0.4"
targetSdkVersion 31
versionCode = 92
versionName = "3.1.0"
multiDexEnabled true
testApplicationId = "com.kunzisoft.keepass.tests"
@@ -99,21 +99,21 @@ android {
}
}
def room_version = "2.3.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'
implementation 'androidx.media:media:1.4.3'
// Lifecycle - LiveData - ViewModel - Coroutines
implementation "androidx.core:core-ktx:$android_core_version"
implementation 'androidx.fragment:fragment-ktx:1.3.6'
implementation 'androidx.fragment:fragment-ktx:1.4.0'
implementation "com.google.android.material:material:$android_material_version"
// Database
implementation "androidx.room:room-runtime:$room_version"
@@ -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

@@ -10,15 +10,12 @@
android:anyDensity="true" />
<uses-permission
android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission
android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission
android:name="android.permission.USE_BIOMETRIC" />
<uses-permission
android:name="android.permission.VIBRATE"/>
<!-- Write permission until Android 10 -->
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28"
tools:ignore="ScopedStorage" />
<!-- Open apps from links -->
<uses-permission
android:name="android.permission.QUERY_ALL_PACKAGES"
@@ -30,12 +27,13 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:name="com.kunzisoft.keepass.app.App"
android:allowBackup="true"
android:fullBackupContent="@xml/backup_rules"
android:fullBackupContent="@xml/old_backup_rules"
android:dataExtractionRules="@xml/backup_rules"
android:backupAgent="com.kunzisoft.keepass.backup.SettingsBackupAgent"
android:largeHeap="true"
android:resizeableActivity="true"
android:theme="@style/KeepassDXStyle.Night"
tools:targetApi="n">
tools:targetApi="s">
<meta-data
android:name="com.google.android.backup.api_key"
android:value="${googleAndroidBackupAPIKey}" />

View File

@@ -233,9 +233,8 @@ class AutofillLauncherActivity : DatabaseModeActivity() {
}
})
},
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// TODO Mutable
PendingIntent.FLAG_CANCEL_CURRENT
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_CANCEL_CURRENT
} else {
PendingIntent.FLAG_CANCEL_CURRENT
})
@@ -248,9 +247,8 @@ class AutofillLauncherActivity : DatabaseModeActivity() {
EntrySelectionHelper.addSpecialModeInIntent(this, SpecialMode.REGISTRATION)
putExtra(KEY_REGISTER_INFO, registerInfo)
},
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// TODO Mutable
PendingIntent.FLAG_CANCEL_CURRENT
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_CANCEL_CURRENT
} else {
PendingIntent.FLAG_CANCEL_CURRENT
})

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 entryInfo.id.toString()
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
@@ -204,9 +227,9 @@ class EntryActivity : DatabaseLockActivity() {
}
mEntryViewModel.onOtpElementUpdated.observe(this) { otpElement ->
if (otpElement == null)
if (otpElement == null) {
entryProgress?.visibility = View.GONE
when (otpElement?.type) {
} else when (otpElement.type) {
// Only add token if HOTP
OtpType.HOTP -> {
entryProgress?.visibility = View.GONE
@@ -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,7 +25,7 @@ import android.app.TimePickerDialog
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.graphics.PorterDuff
import android.os.*
import android.util.Log
import android.view.Menu
@@ -41,12 +41,15 @@ import androidx.appcompat.view.ActionMode
import androidx.appcompat.widget.SearchView
import androidx.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.*
import com.kunzisoft.keepass.activities.fragments.GroupFragment
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.SpecialMode
import com.kunzisoft.keepass.activities.legacy.DatabaseLockActivity
import com.kunzisoft.keepass.adapters.BreadcrumbAdapter
import com.kunzisoft.keepass.adapters.SearchEntryCursorAdapter
import com.kunzisoft.keepass.autofill.AutofillComponent
import com.kunzisoft.keepass.autofill.AutofillHelper
@@ -60,6 +63,7 @@ import com.kunzisoft.keepass.model.GroupInfo
import com.kunzisoft.keepass.model.RegisterInfo
import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_GROUP_TASK
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.NEW_NODES_KEY
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.getListNodesFromBundle
import com.kunzisoft.keepass.settings.PreferencesUtil
@@ -77,6 +81,7 @@ class GroupActivity : DatabaseLockActivity(),
GroupFragment.NodeClickListener,
GroupFragment.NodesActionMenuListener,
GroupFragment.OnScrollListener,
GroupFragment.GroupRefreshedListener,
SortDialogFragment.SortSelectionListener {
// Views
@@ -84,18 +89,25 @@ 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
private var searchString: TextView? = null
private var toolbarBreadcrumb: Toolbar? = null
private var searchTitleView: View? = null
private var toolbarAction: ToolbarAction? = null
private var iconView: ImageView? = null
private var numberChildrenView: TextView? = null
private var addNodeButtonView: AddNodeButtonView? = null
private var groupNameView: TextView? = null
private var groupMetaView: TextView? = null
private var breadcrumbListView: RecyclerView? = null
private var loadingView: ProgressBar? = null
private val mGroupViewModel: GroupViewModel by viewModels()
private val mGroupEditViewModel: GroupEditViewModel by viewModels()
private var mBreadcrumbAdapter: BreadcrumbAdapter? = null
private var mGroupFragment: GroupFragment? = null
private var mRecyclingBinEnabled = false
private var mRecyclingBinIsCurrentGroup = false
@@ -123,8 +135,6 @@ class GroupActivity : DatabaseLockActivity(),
AutofillHelper.buildActivityResultLauncher(this)
else null
private var mIconColor: Int = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -134,13 +144,18 @@ class GroupActivity : DatabaseLockActivity(),
// Initialize views
rootContainerView = findViewById(R.id.activity_group_container_view)
coordinatorLayout = findViewById(R.id.group_coordinator)
iconView = findViewById(R.id.group_icon)
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)
searchString = findViewById(R.id.search_string)
toolbarBreadcrumb = findViewById(R.id.toolbar_breadcrumb)
searchTitleView = findViewById(R.id.search_title)
groupNameView = findViewById(R.id.group_name)
groupMetaView = findViewById(R.id.group_meta)
breadcrumbListView = findViewById(R.id.breadcrumb_list)
toolbarAction = findViewById(R.id.toolbar_action)
lockView = findViewById(R.id.lock_button)
loadingView = findViewById(R.id.loading)
@@ -152,10 +167,42 @@ class GroupActivity : DatabaseLockActivity(),
toolbar?.title = ""
setSupportActionBar(toolbar)
// Retrieve the textColor to tint the icon
val taTextColor = theme.obtainStyledAttributes(intArrayOf(R.attr.textColorInverse))
mIconColor = taTextColor.getColor(0, Color.WHITE)
taTextColor.recycle()
mBreadcrumbAdapter = BreadcrumbAdapter(this).apply {
// Open group on breadcrumb click
onItemClickListener = { node, _ ->
// If last item & not a virtual root group
val currentGroup = mCurrentGroup
if (currentGroup != null && node == currentGroup
&& (currentGroup != mDatabase?.rootGroup
|| mDatabase?.rootGroupIsVirtual == false)
) {
finishNodeAction()
launchDialogToShowGroupInfo(currentGroup)
} else {
if (mGroupFragment?.nodeActionSelectionMode == true) {
finishNodeAction()
}
mDatabase?.let { database ->
onNodeClick(database, node)
}
}
}
onLongItemClickListener = { node, position ->
val currentGroup = mCurrentGroup
if (currentGroup != null && node == currentGroup
&& (currentGroup != mDatabase?.rootGroup
|| mDatabase?.rootGroupIsVirtual == false)
) {
finishNodeAction()
launchDialogForGroupUpdate(currentGroup)
} else {
onItemClickListener?.invoke(node, position)
}
}
}
breadcrumbListView?.apply {
adapter = mBreadcrumbAdapter
}
// Retrieve group if defined at launch
manageIntent(intent)
@@ -213,23 +260,21 @@ class GroupActivity : DatabaseLockActivity(),
// Add listeners to the add buttons
addNodeButtonView?.setAddGroupClickListener {
GroupEditDialogFragment.create(GroupInfo().apply {
if (currentGroup.allowAddNoteInGroup) {
notes = ""
}
}).show(supportFragmentManager, GroupEditDialogFragment.TAG_CREATE_GROUP)
launchDialogForGroupCreation(currentGroup)
}
addNodeButtonView?.setAddEntryClickListener {
mDatabase?.let { database ->
EntrySelectionHelper.doSpecialAction(intent,
{
mGroupFragment?.mEntryActivityResultLauncher?.let { resultLauncher ->
EntryEditActivity.launchToCreate(
this@GroupActivity,
database,
currentGroup.nodeId,
resultLauncher
)
mCurrentGroup?.nodeId?.let { currentParentGroupId ->
mGroupFragment?.mEntryActivityResultLauncher?.let { resultLauncher ->
EntryEditActivity.launchToCreate(
this@GroupActivity,
database,
currentParentGroupId,
resultLauncher
)
}
}
},
{
@@ -282,9 +327,6 @@ class GroupActivity : DatabaseLockActivity(),
}
}
assignGroupViewElements(currentGroup)
invalidateOptionsMenu()
loadingView?.hideByFading()
}
@@ -371,7 +413,19 @@ 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 {
override fun onSuggestionClick(position: Int): Boolean {
mSearchSuggestionAdapter?.let { searchAdapter ->
@@ -446,16 +500,27 @@ class GroupActivity : DatabaseLockActivity(),
)
}
}
ACTION_DATABASE_UPDATE_GROUP_TASK -> {
if (result.isSuccess) {
try {
if (mCurrentGroup == newNodes[0] as Group)
reloadCurrentGroup()
} catch (e: Exception) {
Log.e(
TAG,
"Unable to perform action after group update",
e
)
}
}
}
}
coordinatorLayout?.showActionErrorIfNeeded(result)
if (!result.isSuccess) {
reloadCurrentGroup()
}
finishNodeAction()
refreshNumberOfChildren(mCurrentGroup)
}
/**
@@ -500,62 +565,44 @@ class GroupActivity : DatabaseLockActivity(),
super.onSaveInstanceState(outState)
}
override fun onGroupRefreshed() {
mCurrentGroup?.let { currentGroup ->
assignGroupViewElements(currentGroup)
}
}
private fun assignGroupViewElements(group: Group?) {
// Assign title
if (group != null) {
if (groupNameView != null) {
val title = group.title
groupNameView?.text = if (title.isNotEmpty()) title else getText(R.string.root)
groupNameView?.invalidate()
}
if (groupMetaView != null) {
val meta = group.nodeId.toString()
groupMetaView?.text = meta
if (meta.isNotEmpty()
&& !group.isVirtual
&& PreferencesUtil.showUUID(this)) {
groupMetaView?.visibility = View.VISIBLE
} else {
groupMetaView?.visibility = View.GONE
}
groupMetaView?.invalidate()
}
}
if (group?.isVirtual == true) {
searchTitleView?.visibility = View.VISIBLE
if (toolbar != null) {
toolbar?.navigationIcon = null
}
iconView?.visibility = View.GONE
searchContainer?.visibility = View.VISIBLE
val title = group.title
searchString?.text = if (title.isNotEmpty()) title else ""
searchNumbers?.text = group.numberOfChildEntries.toString()
databaseNameContainer?.visibility = View.GONE
toolbarBreadcrumb?.navigationIcon = null
toolbarBreadcrumb?.collapse()
} else {
searchTitleView?.visibility = View.GONE
// Assign the group icon depending of IconPack or custom icon
iconView?.visibility = View.VISIBLE
group?.let { currentGroup ->
iconView?.let { imageView ->
mIconDrawableFactory?.assignDatabaseIcon(
imageView,
currentGroup.icon,
mIconColor
)
}
if (toolbar != null) {
if (group.containsParent())
toolbar?.setNavigationIcon(R.drawable.ic_arrow_up_white_24dp)
else {
toolbar?.navigationIcon = null
}
searchContainer?.visibility = View.GONE
databaseNameContainer?.visibility = View.VISIBLE
// Refresh breadcrumb
if (toolbarBreadcrumb?.isVisible != true) {
toolbarBreadcrumb?.expand {
setBreadcrumbNode(group)
}
} else {
// Add breadcrumb
setBreadcrumbNode(group)
}
}
// Assign number of children
refreshNumberOfChildren(group)
// Hide button
initAddButton(group)
invalidateOptionsMenu()
}
private fun setBreadcrumbNode(group: Group?) {
mBreadcrumbAdapter?.apply {
setNode(group)
breadcrumbListView?.scrollToPosition(itemCount -1)
}
}
private fun initAddButton(group: Group?) {
@@ -577,18 +624,6 @@ class GroupActivity : DatabaseLockActivity(),
}
}
private fun refreshNumberOfChildren(group: Group?) {
numberChildrenView?.apply {
if (PreferencesUtil.showNumberEntries(context)) {
group?.refreshNumberOfChildEntries(Group.ChildFilter.getDefaults(context))
text = group?.numberOfChildEntries?.toString() ?: ""
visibility = View.VISIBLE
} else {
visibility = View.GONE
}
}
}
override fun onScrolled(dy: Int) {
if (actionNodeMode == null)
addNodeButtonView?.hideOrShowButtonOnScrollListener(dy)
@@ -815,12 +850,7 @@ class GroupActivity : DatabaseLockActivity(),
finishNodeAction()
when (node.type) {
Type.GROUP -> {
mOldGroupToUpdate = node as Group
GroupEditDialogFragment.update(mOldGroupToUpdate!!.getGroupInfo())
.show(
supportFragmentManager,
GroupEditDialogFragment.TAG_CREATE_GROUP
)
launchDialogForGroupUpdate(node as Group)
}
Type.ENTRY -> {
mGroupFragment?.mEntryActivityResultLauncher?.let { resultLauncher ->
@@ -837,6 +867,25 @@ class GroupActivity : DatabaseLockActivity(),
return true
}
private fun launchDialogToShowGroupInfo(group: Group) {
GroupDialogFragment.launch(group.getGroupInfo())
.show(supportFragmentManager, GroupDialogFragment.TAG_SHOW_GROUP)
}
private fun launchDialogForGroupCreation(group: Group) {
GroupEditDialogFragment.create(GroupInfo().apply {
if (group.allowAddNoteInGroup) {
notes = ""
}
}).show(supportFragmentManager, GroupEditDialogFragment.TAG_CREATE_GROUP)
}
private fun launchDialogForGroupUpdate(group: Group) {
mOldGroupToUpdate = group
GroupEditDialogFragment.update(group.getGroupInfo())
.show(supportFragmentManager, GroupEditDialogFragment.TAG_CREATE_GROUP)
}
override fun onCopyMenuClick(
database: Database,
nodes: List<Node>
@@ -1034,7 +1083,7 @@ class GroupActivity : DatabaseLockActivity(),
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
android.R.id.home -> {
onBackPressed()
// TODO change database
return true
}
R.id.menu_search ->

View File

@@ -21,7 +21,6 @@ package com.kunzisoft.keepass.activities
import android.app.Activity
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
@@ -41,7 +40,6 @@ import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.app.ActivityCompat
import androidx.fragment.app.commit
import com.google.android.material.snackbar.Snackbar
import com.kunzisoft.keepass.R
@@ -101,18 +99,8 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
private var mRememberKeyFile: Boolean = false
private var mExternalFileHelper: ExternalFileHelper? = null
private var mPermissionAsked = false
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)
@@ -139,7 +127,6 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
infoContainerView = findViewById(R.id.activity_password_info_container)
coordinatorLayout = findViewById(R.id.activity_password_coordinator_layout)
mPermissionAsked = savedInstanceState?.getBoolean(KEY_PERMISSION_ASKED) ?: mPermissionAsked
mReadOnly = if (savedInstanceState != null && savedInstanceState.containsKey(KEY_READ_ONLY)) {
savedInstanceState.getBoolean(KEY_READ_ONLY)
} else {
@@ -208,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
@@ -249,8 +245,6 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
mDatabaseFileViewModel.loadDatabaseFile(databaseFileUri)
}
checkPermission()
mDatabase?.let { database ->
launchGroupActivityIfLoaded(database)
}
@@ -510,7 +504,6 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
}
override fun onSaveInstanceState(outState: Bundle) {
outState.putBoolean(KEY_PERMISSION_ASKED, mPermissionAsked)
mDatabaseKeyFileUri?.let {
outState.putString(KEY_KEYFILE, it.toString())
}
@@ -613,35 +606,6 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
return true
}
// Check permission
private fun checkPermission() {
if (Build.VERSION.SDK_INT in 23..28
&& !mReadOnly
&& !mPermissionAsked) {
mPermissionAsked = true
// Check self permission to show or not the dialog
val writePermission = android.Manifest.permission.WRITE_EXTERNAL_STORAGE
val permissions = arrayOf(writePermission)
if (toolbar != null
&& ActivityCompat.checkSelfPermission(this, writePermission) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, permissions, WRITE_EXTERNAL_STORAGE_REQUEST)
}
}
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) {
WRITE_EXTERNAL_STORAGE_REQUEST -> {
if (grantResults.isEmpty() || grantResults[0] != PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE))
Toast.makeText(this, R.string.read_only_warning, Toast.LENGTH_LONG).show()
}
}
}
}
// To fix multiple view education
private var performedEductionInProgress = false
private fun launchEducation(menu: Menu) {
@@ -729,8 +693,6 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
private const val KEY_READ_ONLY = "KEY_READ_ONLY"
private const val KEY_PASSWORD = "password"
private const val KEY_LAUNCH_IMMEDIATELY = "launchImmediately"
private const val KEY_PERMISSION_ASKED = "KEY_PERMISSION_ASKED"
private const val WRITE_EXTERNAL_STORAGE_REQUEST = 647
private fun buildAndLaunchIntent(activity: Activity, databaseFile: Uri, keyFile: Uri?,
intentBuildLauncher: (Intent) -> Unit) {

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

@@ -0,0 +1,151 @@
/*
* Copyright 2021 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.dialogs
import android.app.Dialog
import android.graphics.Color
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.model.GroupInfo
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.utils.UuidUtil
import com.kunzisoft.keepass.view.DateTimeFieldView
class GroupDialogFragment : DatabaseDialogFragment() {
private var mPopulateIconMethod: ((ImageView, IconImage) -> Unit)? = null
private var mGroupInfo = GroupInfo()
private lateinit var iconView: ImageView
private var mIconColor: Int = 0
private lateinit var nameTextView: TextView
private lateinit var notesTextLabelView: TextView
private lateinit var notesTextView: TextView
private lateinit var expirationView: DateTimeFieldView
private lateinit var creationView: TextView
private lateinit var modificationView: TextView
private lateinit var uuidContainerView: ViewGroup
private lateinit var uuidReferenceView: TextView
override fun onDatabaseRetrieved(database: Database?) {
super.onDatabaseRetrieved(database)
mPopulateIconMethod = { imageView, icon ->
database?.iconDrawableFactory?.assignDatabaseIcon(imageView, icon, mIconColor)
}
mPopulateIconMethod?.invoke(iconView, mGroupInfo.icon)
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
activity?.let { activity ->
val root = activity.layoutInflater.inflate(R.layout.fragment_group, null)
iconView = root.findViewById(R.id.group_icon)
nameTextView = root.findViewById(R.id.group_name)
notesTextLabelView = root.findViewById(R.id.group_note_label)
notesTextView = root.findViewById(R.id.group_note)
expirationView = root.findViewById(R.id.group_expiration)
creationView = root.findViewById(R.id.group_created)
modificationView = root.findViewById(R.id.group_modified)
uuidContainerView = root.findViewById(R.id.group_UUID_container)
uuidReferenceView = root.findViewById(R.id.group_UUID_reference)
// Retrieve the textColor to tint the icon
val ta = activity.theme.obtainStyledAttributes(intArrayOf(R.attr.colorAccent))
mIconColor = ta.getColor(0, Color.WHITE)
ta.recycle()
if (savedInstanceState != null
&& savedInstanceState.containsKey(KEY_GROUP_INFO)) {
mGroupInfo = savedInstanceState.getParcelable(KEY_GROUP_INFO) ?: mGroupInfo
} else {
arguments?.apply {
if (containsKey(KEY_GROUP_INFO)) {
mGroupInfo = getParcelable(KEY_GROUP_INFO) ?: mGroupInfo
}
}
}
// populate info in views
val title = mGroupInfo.title
if (title.isEmpty()) {
nameTextView.visibility = View.GONE
} else {
nameTextView.text = title
nameTextView.visibility = View.VISIBLE
}
val notes = mGroupInfo.notes
if (notes == null || notes.isEmpty()) {
notesTextLabelView.visibility = View.GONE
notesTextView.visibility = View.GONE
} else {
notesTextView.text = notes
notesTextLabelView.visibility = View.VISIBLE
notesTextView.visibility = View.VISIBLE
}
expirationView.activation = mGroupInfo.expires
expirationView.dateTime = mGroupInfo.expiryTime
creationView.text = mGroupInfo.creationTime.getDateTimeString(resources)
modificationView.text = mGroupInfo.lastModificationTime.getDateTimeString(resources)
val uuid = UuidUtil.toHexString(mGroupInfo.id)
if (uuid == null || uuid.isEmpty()) {
uuidContainerView.visibility = View.GONE
} else {
uuidReferenceView.text = uuid
uuidContainerView.apply {
visibility = if (PreferencesUtil.showUUID(context)) View.VISIBLE else View.GONE
}
}
val builder = AlertDialog.Builder(activity)
builder.setView(root)
.setPositiveButton(android.R.string.ok){ _, _ ->
// Do nothing
}
return builder.create()
}
return super.onCreateDialog(savedInstanceState)
}
override fun onSaveInstanceState(outState: Bundle) {
outState.putParcelable(KEY_GROUP_INFO, mGroupInfo)
super.onSaveInstanceState(outState)
}
data class Error(val isError: Boolean, val messageId: Int?)
companion object {
const val TAG_SHOW_GROUP = "TAG_SHOW_GROUP"
private const val KEY_GROUP_INFO = "KEY_GROUP_INFO"
fun launch(groupInfo: GroupInfo): GroupDialogFragment {
val bundle = Bundle()
bundle.putParcelable(KEY_GROUP_INFO, groupInfo)
val fragment = GroupDialogFragment()
fragment.arguments = bundle
return fragment
}
}
}

View File

@@ -246,8 +246,8 @@ class GroupEditDialogFragment : DatabaseDialogFragment() {
companion object {
const val TAG_CREATE_GROUP = "TAG_CREATE_GROUP"
const val KEY_ACTION_ID = "KEY_ACTION_ID"
const val KEY_GROUP_INFO = "KEY_GROUP_INFO"
private const val KEY_ACTION_ID = "KEY_ACTION_ID"
private const val KEY_GROUP_INFO = "KEY_GROUP_INFO"
fun create(groupInfo: GroupInfo): GroupEditDialogFragment {
val bundle = Bundle()

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

@@ -42,7 +42,6 @@ class EntryFragment: DatabaseFragment() {
private var attachmentsAdapter: EntryAttachmentsItemsAdapter? = null
private lateinit var uuidContainerView: View
private lateinit var uuidView: TextView
private lateinit var uuidReferenceView: TextView
private var mClipboardHelper: ClipboardHelper? = null
@@ -88,7 +87,6 @@ class EntryFragment: DatabaseFragment() {
uuidContainerView.apply {
visibility = if (PreferencesUtil.showUUID(context)) View.VISIBLE else View.GONE
}
uuidView = view.findViewById(R.id.entry_UUID)
uuidReferenceView = view.findViewById(R.id.entry_UUID_reference)
mEntryViewModel.entryInfoHistory.observe(viewLifecycleOwner) { entryInfoHistory ->
@@ -200,7 +198,6 @@ class EntryFragment: DatabaseFragment() {
}
private fun assignUUID(uuid: UUID?) {
uuidView.text = uuid?.toString()
uuidReferenceView.text = UuidUtil.toHexString(uuid)
}

View File

@@ -20,7 +20,6 @@
package com.kunzisoft.keepass.activities.fragments
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.view.*
@@ -34,12 +33,11 @@ import com.kunzisoft.keepass.activities.EntryEditActivity
import com.kunzisoft.keepass.activities.dialogs.SortDialogFragment
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.SpecialMode
import com.kunzisoft.keepass.adapters.NodeAdapter
import com.kunzisoft.keepass.adapters.NodesAdapter
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.SortNodeEnum
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable
@@ -50,10 +48,11 @@ class GroupFragment : DatabaseFragment(), SortDialogFragment.SortSelectionListen
private var nodeClickListener: NodeClickListener? = null
private var onScrollListener: OnScrollListener? = null
private var groupRefreshed: GroupRefreshedListener? = null
private var mNodesRecyclerView: RecyclerView? = null
private var mLayoutManager: LinearLayoutManager? = null
private var mAdapter: NodeAdapter? = null
private var mAdapter: NodesAdapter? = null
private val mGroupViewModel: GroupViewModel by activityViewModels()
@@ -102,12 +101,14 @@ class GroupFragment : DatabaseFragment(), SortDialogFragment.SortSelectionListen
override fun onAttach(context: Context) {
super.onAttach(context)
// TODO Change to ViewModel
try {
nodeClickListener = context as NodeClickListener
} catch (e: ClassCastException) {
// The activity doesn't implement the interface, throw exception
throw ClassCastException(context.toString()
+ " must implement " + NodeAdapter.NodeClickCallback::class.java.name)
+ " must implement " + NodesAdapter.NodeClickCallback::class.java.name)
}
try {
@@ -115,14 +116,24 @@ class GroupFragment : DatabaseFragment(), SortDialogFragment.SortSelectionListen
} catch (e: ClassCastException) {
onScrollListener = null
// Context menu can be omit
Log.w(TAG, context.toString()
Log.w(
TAG, context.toString()
+ " must implement " + RecyclerView.OnScrollListener::class.java.name)
}
try {
groupRefreshed = context as GroupRefreshedListener
} catch (e: ClassCastException) {
// The activity doesn't implement the interface, throw exception
throw ClassCastException(context.toString()
+ " must implement " + GroupRefreshedListener::class.java.name)
}
}
override fun onDetach() {
nodeClickListener = null
onScrollListener = null
groupRefreshed = null
super.onDetach()
}
@@ -138,8 +149,8 @@ class GroupFragment : DatabaseFragment(), SortDialogFragment.SortSelectionListen
contextThemed?.let { context ->
database?.let { database ->
mAdapter = NodeAdapter(context, database).apply {
setOnNodeClickListener(object : NodeAdapter.NodeClickCallback {
mAdapter = NodesAdapter(context, database).apply {
setOnNodeClickListener(object : NodesAdapter.NodeClickCallback {
override fun onNodeClick(database: Database, node: Node) {
if (nodeActionSelectionMode) {
if (listActionNodes.contains(node)) {
@@ -195,7 +206,7 @@ class GroupFragment : DatabaseFragment(), SortDialogFragment.SortSelectionListen
super.onCreateView(inflater, container, savedInstanceState)
// To apply theme
return inflater.cloneInContext(contextThemed)
.inflate(R.layout.fragment_group, container, false)
.inflate(R.layout.fragment_nodes, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@@ -260,6 +271,8 @@ class GroupFragment : DatabaseFragment(), SortDialogFragment.SortSelectionListen
} else {
notFoundView?.visibility = View.GONE
}
groupRefreshed?.onGroupRefreshed()
}
override fun onSortSelected(sortNodeEnum: SortNodeEnum,
@@ -292,15 +305,17 @@ class GroupFragment : DatabaseFragment(), SortDialogFragment.SortSelectionListen
val sortDialogFragment: SortDialogFragment =
if (mRecycleBinEnable) {
SortDialogFragment.getInstance(
PreferencesUtil.getListSort(context),
PreferencesUtil.getAscendingSort(context),
PreferencesUtil.getGroupsBeforeSort(context),
PreferencesUtil.getRecycleBinBottomSort(context))
PreferencesUtil.getListSort(context),
PreferencesUtil.getAscendingSort(context),
PreferencesUtil.getGroupsBeforeSort(context),
PreferencesUtil.getRecycleBinBottomSort(context)
)
} else {
SortDialogFragment.getInstance(
PreferencesUtil.getListSort(context),
PreferencesUtil.getAscendingSort(context),
PreferencesUtil.getGroupsBeforeSort(context))
PreferencesUtil.getListSort(context),
PreferencesUtil.getAscendingSort(context),
PreferencesUtil.getGroupsBeforeSort(context)
)
}
sortDialogFragment.show(childFragmentManager, "sortDialog")
@@ -447,6 +462,10 @@ class GroupFragment : DatabaseFragment(), SortDialogFragment.SortSelectionListen
fun onScrolled(dy: Int)
}
interface GroupRefreshedListener {
fun onGroupRefreshed()
}
companion object {
private val TAG = GroupFragment::class.java.name
}

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

@@ -0,0 +1,150 @@
package com.kunzisoft.keepass.adapters
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Color
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.icons.IconDrawableFactory
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.view.strikeOut
class BreadcrumbAdapter(val context: Context)
: RecyclerView.Adapter<BreadcrumbAdapter.BreadcrumbGroupViewHolder>() {
private val inflater: LayoutInflater = LayoutInflater.from(context)
var iconDrawableFactory: IconDrawableFactory? = null
@SuppressLint("NotifyDataSetChanged")
set(value) {
field = value
notifyDataSetChanged()
}
private var mNodeBreadcrumb: MutableList<Node?> = mutableListOf()
var onItemClickListener: ((item: Node, position: Int)->Unit)? = null
var onLongItemClickListener: ((item: Node, position: Int)->Unit)? = null
private var mShowNumberEntries = false
private var mShowUUID = false
private var mIconColor: Int = 0
init {
mShowNumberEntries = PreferencesUtil.showNumberEntries(context)
mShowUUID = PreferencesUtil.showUUID(context)
// Retrieve the textColor to tint the icon
val taTextColor = context.theme.obtainStyledAttributes(intArrayOf(R.attr.textColorInverse))
mIconColor = taTextColor.getColor(0, Color.WHITE)
taTextColor.recycle()
}
@SuppressLint("NotifyDataSetChanged")
fun setNode(node: Node?) {
mNodeBreadcrumb.clear()
node?.let {
var currentNode = it
mNodeBreadcrumb.add(0, currentNode)
while (currentNode.containsParent()) {
currentNode.parent?.let { parent ->
currentNode = parent
mNodeBreadcrumb.add(0, currentNode)
}
}
}
notifyDataSetChanged()
}
override fun getItemViewType(position: Int): Int {
return when (position) {
mNodeBreadcrumb.size - 1 -> 0
else -> 1
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BreadcrumbGroupViewHolder {
return BreadcrumbGroupViewHolder(inflater.inflate(
when (viewType) {
0 -> R.layout.item_group
else -> R.layout.item_breadcrumb
}, parent, false)
)
}
override fun onBindViewHolder(holder: BreadcrumbGroupViewHolder, position: Int) {
val node = mNodeBreadcrumb[position]
holder.groupNameView.apply {
text = node?.title ?: ""
strikeOut(node?.isCurrentlyExpires ?: false)
}
holder.itemView.apply {
setOnClickListener {
node?.let {
onItemClickListener?.invoke(it, position)
}
}
setOnLongClickListener {
node?.let {
onLongItemClickListener?.invoke(it, position)
}
true
}
}
if (node?.type == Type.GROUP) {
(node as Group).let { group ->
holder.groupIconView?.let { imageView ->
iconDrawableFactory?.assignDatabaseIcon(
imageView,
group.icon,
mIconColor
)
}
holder.groupNumbersView?.apply {
if (mShowNumberEntries) {
group.refreshNumberOfChildEntries(Group.ChildFilter.getDefaults(context))
text = group.numberOfChildEntries.toString()
visibility = View.VISIBLE
} else {
visibility = View.GONE
}
}
holder.groupMetaView?.apply {
val meta = group.nodeId.toVisualString()
visibility = if (meta != null
&& !group.isVirtual
&& mShowUUID
) {
text = meta
View.VISIBLE
} else {
View.GONE
}
}
}
}
}
override fun getItemCount(): Int {
return mNodeBreadcrumb.size
}
inner class BreadcrumbGroupViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var groupIconView: ImageView? = itemView.findViewById(R.id.group_icon)
var groupNumbersView: TextView? = itemView.findViewById(R.id.group_numbers)
var groupNameView: TextView = itemView.findViewById(R.id.group_name)
var groupMetaView: TextView? = itemView.findViewById(R.id.group_meta)
}
}

View File

@@ -34,6 +34,7 @@ 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
@@ -55,9 +56,9 @@ import java.util.*
* Create node list adapter with contextMenu or not
* @param context Context to use
*/
class NodeAdapter (private val context: Context,
private val database: Database)
: RecyclerView.Adapter<NodeAdapter.NodeViewHolder>() {
class NodesAdapter (private val context: Context,
private val database: Database)
: RecyclerView.Adapter<NodesAdapter.NodeViewHolder>() {
private var mNodeComparator: Comparator<NodeVersionedInterface<Group>>? = null
private val mNodeSortedListCallback: NodeSortedListCallback
@@ -79,6 +80,8 @@ class NodeAdapter (private val context: Context,
private var mShowOTP: Boolean = false
private var mShowUUID: Boolean = false
private var mEntryFilters = arrayOf<Group.ChildFilter>()
private var mOldVirtualGroup = false
private var mVirtualGroup = false
private var mActionNodesList = LinkedList<Node>()
private var mNodeClickCallback: NodeClickCallback? = null
@@ -87,9 +90,15 @@ class NodeAdapter (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 NodeAdapter (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() {
@@ -145,6 +166,8 @@ class NodeAdapter (private val context: Context,
* Rebuild the list by clear and build children from the group
*/
fun rebuildList(group: Group) {
mOldVirtualGroup = mVirtualGroup
mVirtualGroup = group.isVirtual
assignPreferences()
mNodeSortedList.replaceAll(group.getFilteredChildren(mEntryFilters))
}
@@ -155,14 +178,19 @@ class NodeAdapter (private val context: Context,
}
override fun areContentsTheSame(oldItem: Node, newItem: Node): Boolean {
if (mOldVirtualGroup != mVirtualGroup)
return false
var typeContentTheSame = true
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) {
typeContentTheSame = oldItem.numberOfChildEntries == newItem.numberOfChildEntries
&& oldItem.notes == newItem.notes
}
return typeContentTheSame
&& oldItem.nodeId == newItem.nodeId
@@ -327,8 +355,8 @@ class NodeAdapter (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 {
@@ -348,14 +376,24 @@ class NodeAdapter (private val context: Context,
}
// Add meta text to show UUID
holder.meta.apply {
if (mShowUUID) {
text = subNode.nodeId.toString()
val nodeId = subNode.nodeId?.toVisualString()
if (mShowUUID && nodeId != null) {
text = nodeId
setTextSize(mTextSizeUnit, mMetaTextDefaultDimension, mPrefSizeMultiplier)
visibility = View.VISIBLE
} else {
visibility = View.GONE
}
}
// Add path to virtual group
if (mVirtualGroup) {
holder.path?.apply {
text = subNode.getPathString()
visibility = View.VISIBLE
}
} else {
holder.path?.visibility = View.GONE
}
// Specific elements for entry
if (subNode.type == Type.ENTRY) {
@@ -398,6 +436,50 @@ class NodeAdapter (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)
}
@@ -430,15 +512,16 @@ class NodeAdapter (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 -> {}
}
holder?.otpToken?.apply {
text = otpElement?.token
@@ -497,8 +580,9 @@ class NodeAdapter (private val context: Context,
var text: TextView = itemView.findViewById(R.id.node_text)
var subText: TextView? = itemView.findViewById(R.id.node_subtext)
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)
@@ -506,6 +590,6 @@ class NodeAdapter (private val context: Context,
}
companion object {
private val TAG = NodeAdapter::class.java.name
private val TAG = NodesAdapter::class.java.name
}
}

View File

@@ -36,7 +36,11 @@ abstract class ActionNodeDatabaseRunnable(
abstract fun nodeAction()
override fun onStartRun() {
nodeAction()
try {
nodeAction()
} catch (e: Exception) {
setError(e)
}
super.onStartRun()
}

View File

@@ -42,6 +42,9 @@ class UpdateGroupRunnable constructor(
// Update group with new values
mNewGroup.touch(modified = true, touchParents = true)
if (database.rootGroup == mOldGroup) {
database.rootGroup = mNewGroup
}
// Only change data in index
database.updateGroup(mNewGroup)
}
@@ -50,6 +53,9 @@ class UpdateGroupRunnable constructor(
override fun nodeFinish(): ActionNodesValues {
if (!result.isSuccess) {
// If we fail to save, back out changes to global structure
if (database.rootGroup == mNewGroup) {
database.rootGroup = mOldGroup
}
database.updateGroup(mOldGroup)
}

View File

@@ -22,10 +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.os.Build
import android.util.Log
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
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
@@ -226,31 +226,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
@@ -364,7 +366,7 @@ class Database {
mDatabaseKDBX?.masterKey = masterKey
}
val rootGroup: Group?
var rootGroup: Group?
get() {
mDatabaseKDB?.rootGroup?.let {
return Group(it)
@@ -374,6 +376,25 @@ class Database {
}
return null
}
set(value) {
value?.groupKDB?.let { rootKDB ->
mDatabaseKDB?.rootGroup = rootKDB
}
value?.groupKDBX?.let { rootKDBX ->
mDatabaseKDBX?.rootGroup = rootKDBX
}
}
val rootGroupIsVirtual: Boolean
get() {
mDatabaseKDB?.let {
return true
}
mDatabaseKDBX?.let {
return false
}
return true
}
/**
* Do not modify groups here, used for read only

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

@@ -31,6 +31,7 @@ import com.kunzisoft.keepass.database.element.node.*
import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.model.GroupInfo
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.utils.UuidUtil
import java.util.*
import kotlin.collections.ArrayList
@@ -308,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)
@@ -453,6 +455,7 @@ class Group : Node, GroupVersionedInterface<Group, Entry> {
fun getGroupInfo(): GroupInfo {
val groupInfo = GroupInfo()
groupInfo.id = groupKDBX?.nodeId?.id
groupInfo.title = title
groupInfo.icon = icon
groupInfo.creationTime = creationTime

View File

@@ -44,15 +44,13 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
get() = "V1"
init {
// New manual root because KDB contains multiple root groups (here available with getRootGroups())
rootGroup = createGroup().apply {
icon.standard = getStandardIcon(IconImageStandard.DATABASE_ID)
}
kdfListV3.add(KdfFactory.aesKdf)
}
private fun getGroupById(groupId: Int): GroupKDB? {
if (groupId == -1)
return null
return getGroupById(NodeIdInt(groupId))
}
val backupGroup: GroupKDB?
get() {
return retrieveBackup()
@@ -63,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]
@@ -77,11 +79,6 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
return list
}
val rootGroups: List<GroupKDB>
get() {
return rootGroup?.getChildGroups() ?: ArrayList()
}
override val passwordEncoding: String
get() = "ISO-8859-1"

View File

@@ -414,37 +414,37 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
}
fun getEntryByTitle(title: String, recursionLevel: Int): EntryKDBX? {
return this.entryIndexes.values.find { entry ->
return findEntry { entry ->
entry.decodeTitleKey(recursionLevel).equals(title, true)
}
}
fun getEntryByUsername(username: String, recursionLevel: Int): EntryKDBX? {
return this.entryIndexes.values.find { entry ->
return findEntry { entry ->
entry.decodeUsernameKey(recursionLevel).equals(username, true)
}
}
fun getEntryByURL(url: String, recursionLevel: Int): EntryKDBX? {
return this.entryIndexes.values.find { entry ->
return findEntry { entry ->
entry.decodeUrlKey(recursionLevel).equals(url, true)
}
}
fun getEntryByPassword(password: String, recursionLevel: Int): EntryKDBX? {
return this.entryIndexes.values.find { entry ->
return findEntry { entry ->
entry.decodePasswordKey(recursionLevel).equals(password, true)
}
}
fun getEntryByNotes(notes: String, recursionLevel: Int): EntryKDBX? {
return this.entryIndexes.values.find { entry ->
return findEntry { entry ->
entry.decodeNotesKey(recursionLevel).equals(notes, true)
}
}
fun getEntryByCustomData(customDataValue: String): EntryKDBX? {
return entryIndexes.values.find { entry ->
return findEntry { entry ->
entry.customData.containsItemWithValue(customDataValue)
}
}

View File

@@ -67,7 +67,7 @@ abstract class DatabaseVersioned<
var changeDuplicateId = false
private var groupIndexes = LinkedHashMap<NodeId<GroupId>, Group>()
protected var entryIndexes = LinkedHashMap<NodeId<EntryId>, Entry>()
private var entryIndexes = LinkedHashMap<NodeId<EntryId>, Entry>()
abstract val version: String
@@ -89,6 +89,7 @@ abstract class DatabaseVersioned<
set(value) {
field = value
value?.let {
removeGroupIndex(it)
addGroupIndex(it)
}
}
@@ -198,12 +199,6 @@ abstract class DatabaseVersioned<
* -------------------------------------
*/
fun doForEachGroupInIndex(action: (Group) -> Unit) {
for (group in groupIndexes) {
action.invoke(group.value)
}
}
/**
* Determine if an id number is already in use
*
@@ -219,14 +214,7 @@ abstract class DatabaseVersioned<
return groupIndexes.values
}
fun setGroupIndexes(groupList: List<Group>) {
this.groupIndexes.clear()
for (currentGroup in groupList) {
this.groupIndexes[currentGroup.nodeId] = currentGroup
}
}
fun getGroupById(id: NodeId<GroupId>): Group? {
open fun getGroupById(id: NodeId<GroupId>): Group? {
return this.groupIndexes[id]
}
@@ -250,16 +238,6 @@ abstract class DatabaseVersioned<
this.groupIndexes.remove(group.nodeId)
}
fun numberOfGroups(): Int {
return groupIndexes.size
}
fun doForEachEntryInIndex(action: (Entry) -> Unit) {
for (entry in entryIndexes) {
action.invoke(entry.value)
}
}
fun isEntryIdUsed(id: NodeId<EntryId>): Boolean {
return entryIndexes.containsKey(id)
}
@@ -272,6 +250,10 @@ abstract class DatabaseVersioned<
return this.entryIndexes[id]
}
fun findEntry(predicate: (Entry) -> Boolean): Entry? {
return this.entryIndexes.values.find(predicate)
}
fun addEntryIndex(entry: Entry) {
val entryId = entry.nodeId
if (entryIndexes.containsKey(entryId)) {
@@ -292,10 +274,6 @@ abstract class DatabaseVersioned<
this.entryIndexes.remove(entry.nodeId)
}
fun numberOfEntries(): Int {
return entryIndexes.size
}
open fun clearCache() {
this.groupIndexes.clear()
this.entryIndexes.clear()

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,18 +61,43 @@ class EntryKDB : EntryVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
private var binaryDataId: Int? = null
// Determine if this is a MetaStream entry
val isMetaStream: Boolean
get() {
if (notes.isEmpty()) return false
if (binaryDescription != PMS_ID_BINDESC) return false
if (title.isEmpty()) return false
if (title != PMS_ID_TITLE) return false
if (username.isEmpty()) return false
if (username != PMS_ID_USER) return false
if (url.isEmpty()) return false
if (url != PMS_ID_URL) return false
return icon.standard.id == KEY_ID
}
fun isMetaStream(): Boolean {
if (notes.isEmpty()) return false
if (binaryDescription != PMS_ID_BINDESC) return false
if (title.isEmpty()) return false
if (title != PMS_ID_TITLE) return false
if (username.isEmpty()) return false
if (username != PMS_ID_USER) return false
if (url.isEmpty()) return false
if (url != PMS_ID_URL) return false
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

@@ -81,6 +81,7 @@ class IconImageStandard : IconImageDraw {
const val CREDIT_CARD_ID = 37
const val TRASH_ID = 43
const val FOLDER_ID = 48
const val DATABASE_ID = 50
const val LIST_ID = 57
const val BUILD_ID = 59
const val STAR_ID = 61

View File

@@ -32,6 +32,19 @@ interface Node: NodeVersionedInterface<Group> {
fun removeParent() {
parent = null
}
fun getPathString(): String {
val pathNodes = mutableListOf<Node>()
var currentNode = this
pathNodes.add(0, currentNode)
while (currentNode.containsParent()) {
currentNode.parent?.let { parent ->
currentNode = parent
pathNodes.add(0, currentNode)
}
}
return pathNodes.joinToString("/") { it.title }
}
}
/**

View File

@@ -44,4 +44,6 @@ abstract class NodeId<Id> : Parcelable {
override fun hashCode(): Int {
return id?.hashCode() ?: 0
}
abstract fun toVisualString(): String?
}

View File

@@ -63,6 +63,10 @@ class NodeIdInt : NodeId<Int> {
return id.toString()
}
override fun toVisualString(): String? {
return null
}
companion object {
@JvmField
val CREATOR: Parcelable.Creator<NodeIdInt> = object : Parcelable.Creator<NodeIdInt> {

View File

@@ -64,6 +64,10 @@ class NodeIdUUID : NodeId<UUID> {
return UuidUtil.toHexString(id) ?: id.toString()
}
override fun toVisualString(): String {
return toString()
}
companion object {
@JvmField
val CREATOR: Parcelable.Creator<NodeIdUUID> = object : Parcelable.Creator<NodeIdUUID> {

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
@@ -30,7 +31,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.*
@@ -98,7 +98,7 @@ class DatabaseInputKDB(cacheDirectory: File,
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()
}
@@ -153,10 +153,6 @@ class DatabaseInputKDB(cacheDirectory: File,
)
)
// New manual root because KDB contains multiple root groups (here available with getRootGroups())
val newRoot = mDatabase.createGroup()
mDatabase.rootGroup = newRoot
// Import all nodes
val groupLevelList = HashMap<GroupKDB, Int>()
var newGroup: GroupKDB? = null
@@ -303,7 +299,34 @@ class DatabaseInputKDB(cacheDirectory: File,
newGroup = null
}
newEntry?.let { entry ->
mDatabase.addEntryIndex(entry)
// Parse meta info
when {
entry.isMetaStreamDefaultUsername() -> {
var defaultUser = ""
entry.getBinary(mDatabase.attachmentPool)
?.getInputDataStream(mDatabase.binaryCache)?.use {
defaultUser = String(it.readBytes())
}
mDatabase.defaultUserName = defaultUser
}
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
}
// TODO manager other meta stream
else -> {
mDatabase.addEntryIndex(entry)
}
}
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,9 +19,11 @@
*/
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
import com.kunzisoft.keepass.database.element.entry.EntryKDB
import com.kunzisoft.keepass.database.element.group.GroupKDB
import com.kunzisoft.keepass.database.exception.DatabaseOutputException
import com.kunzisoft.keepass.database.file.DatabaseHeader
@@ -34,7 +36,6 @@ import java.io.ByteArrayOutputStream
import java.io.IOException
import java.io.OutputStream
import java.security.*
import java.util.*
import javax.crypto.Cipher
import javax.crypto.CipherOutputStream
@@ -44,6 +45,9 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
private var headerHashBlock: ByteArray? = null
private var mGroupList = mutableListOf<GroupKDB>()
private var mEntryList = mutableListOf<EntryKDB>()
@Throws(DatabaseOutputException::class)
fun getFinalKey(header: DatabaseHeader): ByteArray? {
try {
@@ -61,7 +65,7 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
// and remove any orphaned nodes that are no longer part of the tree hierarchy
// also remove the virtual root not present in kdb
val rootGroup = mDatabaseKDB.rootGroup
sortGroupsForOutput()
sortNodesForOutput()
val header = outputHeader(mOutputStream)
@@ -91,6 +95,7 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
} finally {
// Add again the virtual root group for better management
mDatabaseKDB.rootGroup = rootGroup
clearParser()
}
}
@@ -105,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
@@ -120,8 +125,9 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
}
header.version = DatabaseHeaderKDB.DBVER_DW
header.numGroups = UnsignedInt(mDatabaseKDB.numberOfGroups())
header.numEntries = UnsignedInt(mDatabaseKDB.numberOfEntries())
// To remove root
header.numGroups = UnsignedInt(mGroupList.size)
header.numEntries = UnsignedInt(mEntryList.size)
header.numKeyEncRounds = UnsignedInt.fromKotlinLong(mDatabaseKDB.numberKeyEncryptionRounds)
setIVs(header)
@@ -194,31 +200,89 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
}
// Groups
mDatabaseKDB.doForEachGroupInIndex { group ->
GroupOutputKDB(group, outputStream).output()
mGroupList.forEach { group ->
if (group != mDatabaseKDB.rootGroup) {
GroupOutputKDB(group, outputStream).output()
}
}
// Entries
mDatabaseKDB.doForEachEntryInIndex { entry ->
mEntryList.forEach { entry ->
EntryOutputKDB(mDatabaseKDB, entry, outputStream).output()
}
}
private fun sortGroupsForOutput() {
val groupList = ArrayList<GroupKDB>()
// Rebuild list according to sorting order removing any orphaned groups
for (rootGroup in mDatabaseKDB.rootGroups) {
sortGroup(rootGroup, groupList)
}
mDatabaseKDB.setGroupIndexes(groupList)
private fun clearParser() {
mGroupList.clear()
mEntryList.clear()
}
private fun sortGroup(group: GroupKDB, groupList: MutableList<GroupKDB>) {
private fun sortNodesForOutput() {
clearParser()
// Rebuild list according to sorting order removing any orphaned groups
// Do not keep root
mDatabaseKDB.rootGroup?.getChildGroups()?.let { rootSubGroups ->
for (rootGroup in rootSubGroups) {
sortGroup(rootGroup)
}
}
}
private fun sortGroup(group: GroupKDB) {
// Add current tree
groupList.add(group)
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()) {
sortGroup(childGroup, groupList)
sortGroup(childGroup)
}
}
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))
}
}

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

@@ -38,9 +38,9 @@ class PasswordActivityEducation(activity: Activity)
activity.getString(R.string.education_unlock_summary))
.outerCircleColorInt(getCircleColor())
.outerCircleAlpha(getCircleAlpha())
.icon(ContextCompat.getDrawable(activity, R.mipmap.ic_launcher_round))
.icon(ContextCompat.getDrawable(activity, R.drawable.ic_lock_open_white_24dp))
.textColorInt(getTextColor())
.tintTarget(false)
.tintTarget(true)
.cancelable(true),
object : TapTargetView.Listener() {
override fun onTargetClick(view: TapTargetView) {

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

@@ -1,12 +1,15 @@
package com.kunzisoft.keepass.model
import android.os.Parcel
import android.os.ParcelUuid
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
import com.kunzisoft.keepass.database.element.icon.IconImageStandard.Companion.FOLDER_ID
import java.util.*
class GroupInfo : NodeInfo {
var id: UUID? = null
var notes: String? = null
init {
@@ -16,11 +19,14 @@ class GroupInfo : NodeInfo {
constructor(): super()
constructor(parcel: Parcel): super(parcel) {
id = parcel.readParcelable<ParcelUuid>(ParcelUuid::class.java.classLoader)?.uuid ?: id
notes = parcel.readString()
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
super.writeToParcel(parcel, flags)
val uuid = if (id != null) ParcelUuid(id) else null
parcel.writeParcelable(uuid, flags)
parcel.writeString(notes)
}
@@ -29,6 +35,7 @@ class GroupInfo : NodeInfo {
if (other !is GroupInfo) return false
if (!super.equals(other)) return false
if (id != other.id) return false
if (notes != other.notes) return false
return true
@@ -36,6 +43,7 @@ class GroupInfo : NodeInfo {
override fun hashCode(): Int {
var result = super.hashCode()
result = 31 * result + (id?.hashCode() ?: 0)
result = 31 * result + (notes?.hashCode() ?: 0)
return result
}

View File

@@ -4,6 +4,8 @@ import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.utils.UuidUtil
import java.util.*
open class NodeInfo() : Parcelable {

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
@@ -165,28 +165,19 @@ 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)
}
dbDefaultUsernamePref?.summary = database.defaultUsername
// 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) {
color = DialogColorPreference.DISABLE_COLOR
summary = ""
}
dbCustomColorPref?.apply {
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
@@ -348,12 +339,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 = ""
}
}
@@ -426,7 +418,7 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment(), DatabaseRetriev
if (result.isSuccess) {
newColor
} else {
mDatabase?.customColor = oldColor
mDatabase?.customColor = Color.parseColor(oldColor)
oldColor
}
dbCustomColorPref?.summary = defaultColorToShow
@@ -681,6 +673,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)
database?.let {
val newColor = if (customColorEnable) {
ChromaUtil.getFormattedColorString(currentColor, false)
} else {
""
}
val oldColor = database.customColor
database.customColor = newColor
saveColor(oldColor, newColor)
}
val newColor: Int? = if (enableSwitchView.isChecked)
chromaColorView.currentColor
else
null
onColorSelectedListener?.invoke(newColor)
database?.let {
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

@@ -19,7 +19,13 @@
*/
package com.kunzisoft.keepass.settings.preferencedialogfragment
import android.app.AlarmManager
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.provider.Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM
import android.util.Log
import android.view.View
import android.widget.NumberPicker
import com.kunzisoft.keepass.R
@@ -62,6 +68,30 @@ class DurationDialogFragmentCompat : InputPreferenceDialogFragmentCompat() {
}
}
override fun onResume() {
super.onResume()
(context?.applicationContext?.getSystemService(Context.ALARM_SERVICE) as AlarmManager?)?.let { alarmManager ->
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
&& !alarmManager.canScheduleExactAlarms()) {
setExplanationText(R.string.warning_exact_alarm)
setExplanationButton(R.string.permission) {
// Open the exact alarm permission screen
try {
startActivity(Intent().apply {
action = ACTION_REQUEST_SCHEDULE_EXACT_ALARM
})
} catch (e: Exception) {
Log.e(TAG, "Unable to open exact alarm permission screen", e)
}
}
} else {
explanationText = ""
setExplanationButton("") {}
}
}
}
private fun durationToDaysHoursMinutesSeconds(duration: Long) {
if (duration < 0) {
mEnabled = false
@@ -164,6 +194,7 @@ class DurationDialogFragmentCompat : InputPreferenceDialogFragmentCompat() {
}
companion object {
private const val TAG = "DurationDialogFrgCmpt"
private const val ENABLE_KEY = "ENABLE_KEY"
private const val DAYS_KEY = "DAYS_KEY"
private const val HOURS_KEY = "HOURS_KEY"

View File

@@ -22,6 +22,7 @@ package com.kunzisoft.keepass.settings.preferencedialogfragment
import android.view.View
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.widget.Button
import android.widget.CompoundButton
import android.widget.EditText
import android.widget.TextView
@@ -35,6 +36,7 @@ abstract class InputPreferenceDialogFragmentCompat : PreferenceDialogFragmentCom
private var inputTextView: EditText? = null
private var textUnitView: TextView? = null
private var textExplanationView: TextView? = null
private var explanationButton: Button? = null
private var switchElementView: CompoundButton? = null
private var mOnInputTextEditorActionListener: TextView.OnEditorActionListener? = null
@@ -100,6 +102,27 @@ abstract class InputPreferenceDialogFragmentCompat : PreferenceDialogFragmentCom
explanationText = getString(explanationTextId)
}
val explanationButtonText: String?
get() = explanationButton?.text?.toString() ?: ""
fun setExplanationButton(explanationButtonText: String?, clickListener: View.OnClickListener) {
explanationButton?.apply {
if (explanationButtonText != null && explanationButtonText.isNotEmpty()) {
text = explanationButtonText
visibility = View.VISIBLE
setOnClickListener(clickListener)
} else {
text = ""
visibility = View.GONE
setOnClickListener(null)
}
}
}
fun setExplanationButton(@StringRes explanationButtonTextId: Int, clickListener: View.OnClickListener) {
setExplanationButton(getString(explanationButtonTextId), clickListener)
}
override fun onBindDialogView(view: View) {
super.onBindDialogView(view)
@@ -128,6 +151,8 @@ abstract class InputPreferenceDialogFragmentCompat : PreferenceDialogFragmentCom
textUnitView?.visibility = View.GONE
textExplanationView = view.findViewById(R.id.explanation_text)
textExplanationView?.visibility = View.GONE
explanationButton = view.findViewById(R.id.explanation_button)
explanationButton?.visibility = View.GONE
switchElementView = view.findViewById(R.id.switch_element)
switchElementView?.visibility = View.GONE
}

View File

@@ -66,9 +66,26 @@ object TimeoutHelper {
val triggerTime = System.currentTimeMillis() + timeout
Log.d(TAG, "TimeoutHelper start")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
alarmManager.setExact(AlarmManager.RTC, triggerTime, getLockPendingIntent(context))
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
&& !alarmManager.canScheduleExactAlarms()) {
alarmManager.set(
AlarmManager.RTC,
triggerTime,
getLockPendingIntent(context)
)
} else {
alarmManager.setExact(
AlarmManager.RTC,
triggerTime,
getLockPendingIntent(context)
)
}
} else {
alarmManager.set(AlarmManager.RTC, triggerTime, getLockPendingIntent(context))
alarmManager.set(
AlarmManager.RTC,
triggerTime,
getLockPendingIntent(context)
)
}
}
}

View File

@@ -72,11 +72,29 @@ class LockReceiver(var lockAction: () -> Unit) : BroadcastReceiver() {
)
// Launch the effective action after a small time
val first: Long = System.currentTimeMillis() + context.getString(R.string.timeout_screen_off).toLong()
val alarmManager = context.getSystemService(ALARM_SERVICE) as AlarmManager?
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
alarmManager?.setExact(AlarmManager.RTC_WAKEUP, first, mLockPendingIntent)
} else {
alarmManager?.set(AlarmManager.RTC_WAKEUP, first, mLockPendingIntent)
(context.getSystemService(ALARM_SERVICE) as AlarmManager?)?.let { alarmManager ->
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
&& !alarmManager.canScheduleExactAlarms()) {
alarmManager.set(
AlarmManager.RTC_WAKEUP,
first,
mLockPendingIntent
)
} else {
alarmManager.setExact(
AlarmManager.RTC_WAKEUP,
first,
mLockPendingIntent
)
}
} else {
alarmManager.set(
AlarmManager.RTC_WAKEUP,
first,
mLockPendingIntent
)
}
}
} else {
cancelLockPendingIntent(context)

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
@@ -396,37 +404,61 @@ abstract class TemplateAbstractView<
// Icon already populate
val titleView: TEntryFieldView? = findViewWithTag(FIELD_TITLE_TAG)
titleView?.value?.let {
mEntryInfo?.title = it
try {
val titleView: TEntryFieldView? = findViewWithTag(FIELD_TITLE_TAG)
titleView?.value?.let {
mEntryInfo?.title = it
}
} catch (e: Exception) {
Log.e(TAG, "Unable to populate title view", e)
}
val userNameView: TEntryFieldView? = findViewWithTag(FIELD_USERNAME_TAG)
userNameView?.value?.let {
mEntryInfo?.username = it
try {
val userNameView: TEntryFieldView? = findViewWithTag(FIELD_USERNAME_TAG)
userNameView?.value?.let {
mEntryInfo?.username = it
}
} catch (e: Exception) {
Log.e(TAG, "Unable to populate username view", e)
}
val passwordView: TEntryFieldView? = findViewWithTag(FIELD_PASSWORD_TAG)
passwordView?.value?.let {
mEntryInfo?.password = it
try {
val passwordView: TEntryFieldView? = findViewWithTag(FIELD_PASSWORD_TAG)
passwordView?.value?.let {
mEntryInfo?.password = it
}
} catch (e: Exception) {
Log.e(TAG, "Unable to populate password view", e)
}
val urlView: TEntryFieldView? = findViewWithTag(FIELD_URL_TAG)
urlView?.value?.let {
mEntryInfo?.url = it
try {
val urlView: TEntryFieldView? = findViewWithTag(FIELD_URL_TAG)
urlView?.value?.let {
mEntryInfo?.url = it
}
} catch (e: Exception) {
Log.e(TAG, "Unable to populate url view", e)
}
val expirationView: TDateTimeView? = findViewWithTag(FIELD_EXPIRES_TAG)
expirationView?.activation?.let {
mEntryInfo?.expires = it
}
expirationView?.dateTime?.let {
mEntryInfo?.expiryTime = it
try {
val expirationView: TDateTimeView? = findViewWithTag(FIELD_EXPIRES_TAG)
expirationView?.activation?.let {
mEntryInfo?.expires = it
}
expirationView?.dateTime?.let {
mEntryInfo?.expiryTime = it
}
} catch (e: Exception) {
Log.e(TAG, "Unable to populate expiration view", e)
}
val notesView: TEntryFieldView? = findViewWithTag(FIELD_NOTES_TAG)
notesView?.value?.let {
mEntryInfo?.notes = it
try {
val notesView: TEntryFieldView? = findViewWithTag(FIELD_NOTES_TAG)
notesView?.value?.let {
mEntryInfo?.notes = it
}
} catch (e: Exception) {
Log.e(TAG, "Unable to populate notes view", e)
}
retrieveCustomFieldsFromView(templateFieldNotEmpty, retrieveDefaultValues)

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

@@ -73,7 +73,7 @@ class TextFieldView @JvmOverloads constructor(context: Context,
}
private val valueView = AppCompatTextView(context).apply {
setTextAppearance(context,
R.style.KeepassDXStyle_TextAppearance_TextEntryItem)
R.style.KeepassDXStyle_TextAppearance_TextNode)
layoutParams = LayoutParams(
LayoutParams.MATCH_PARENT,
LayoutParams.WRAP_CONTENT).also {

View File

@@ -80,7 +80,8 @@ class ToolbarAction @JvmOverloads constructor(context: Context,
mActionModeCallback = null
}
fun invalidateMenu() {
override fun invalidateMenu() {
super.invalidateMenu()
open()
mActionModeCallback?.onPrepareActionMode(actionMode, menu)
}

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()
@@ -207,4 +216,50 @@ fun CoordinatorLayout.showActionErrorIfNeeded(result: ActionRunnable.Result) {
Snackbar.make(this, message, Snackbar.LENGTH_LONG).asError().show()
}
}
}
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

@@ -31,11 +31,80 @@
android:layout_height="wrap_content"
android:theme="?attr/toolbarSpecialAppearance" />
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:title="@string/app_name"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
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"
android:layout_height="wrap_content"
android:visibility="gone"
android:orientation="vertical">
<TextView
android:id="@+id/search_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/search_results"
style="@style/KeepassDXStyle.TextAppearance.Default.TextOnPrimary" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/search_numbers"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="3"
android:layout_margin="4dp"
android:layout_below="@+id/search_title"
style="@style/KeepassDXStyle.TextAppearance.Info" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/search_string"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/search"
android:maxLines="1"
android:ellipsize="end"
android:layout_marginTop="2dp"
android:layout_marginStart="4dp"
android:layout_marginLeft="4dp"
android:layout_marginEnd="4dp"
android:layout_marginRight="4dp"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_below="@+id/search_title"
android:layout_toRightOf="@+id/search_numbers"
android:layout_toEndOf="@+id/search_numbers"
style="@style/KeepassDXStyle.TextAppearance.Title.TextOnPrimary" />
</RelativeLayout>
</androidx.appcompat.widget.Toolbar>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/group_coordinator"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/special_mode_view"
android:layout_below="@+id/toolbar"
android:layout_above="@+id/toolbar_action">
<com.google.android.material.appbar.AppBarLayout
@@ -43,84 +112,22 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:targetApi="lollipop"
android:elevation="4dp"
android:fitsSystemWindows="true">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:title="@string/app_name"
android:id="@+id/toolbar_breadcrumb"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:theme="?attr/toolbarAppearance"
android:elevation="4dp"
app:layout_scrollFlags="scroll|snap|enterAlways"
tools:targetApi="lollipop">
<LinearLayout
android:id="@+id/group_header"
android:layout_width="wrap_content"
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/breadcrumb_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/toolbar"
android:orientation="vertical">
<TextView android:id="@+id/search_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/search_results"
android:visibility="gone"
style="@style/KeepassDXStyle.TextAppearance.Default.TextOnPrimary" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:baselineAligned="false">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/group_icon"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_gravity="end|center_vertical"
android:layout_marginStart="6dp"
android:layout_marginEnd="6dp"
android:scaleType="fitXY" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/group_numbers"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="3"
style="@style/KeepassDXStyle.TextAppearance.Info" />
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="start|center_vertical"
android:layout_marginLeft="14dp"
android:layout_marginStart="14dp"
android:layout_weight="1">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/group_name"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/root"
android:maxLines="2"
android:ellipsize="end"
style="@style/KeepassDXStyle.TextAppearance.Title.TextOnPrimary" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/group_meta"
android:layout_height="match_parent"
android:layout_width="match_parent"
tools:text="7543A7EAB2EA7CFD1394F1615EBEB08C"
android:lines="1"
android:singleLine="true"
style="@style/KeepassDXStyle.TextAppearance.Meta.TextOnPrimary" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
android:orientation="horizontal" />
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.AppBarLayout>
@@ -159,6 +166,7 @@
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ProgressBar
android:id="@+id/loading"
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"
@@ -57,7 +58,8 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:minHeight="144dp"
android:layout_marginTop="?attr/actionBarSize">
android:layout_marginTop="?attr/actionBarSize"
android:background="?attr/colorPrimary">
<ImageView
android:layout_width="96dp"
android:layout_height="96dp"
@@ -219,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

@@ -35,6 +35,6 @@
android:layout_margin="@dimen/button_margin"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:text="@string/file_browser" />
android:text="@string/file_browser"
style="@style/Widget.AppCompat.Button.Small"/>
</LinearLayout>

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

@@ -77,7 +77,7 @@
android:id="@+id/entry_created"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/KeepassDXStyle.TextAppearance.TextEntryItem" />
style="@style/KeepassDXStyle.TextAppearance.TextNode" />
<!-- Modified -->
<androidx.appcompat.widget.AppCompatTextView
@@ -90,7 +90,7 @@
android:id="@+id/entry_modified"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/KeepassDXStyle.TextAppearance.TextEntryItem" />
style="@style/KeepassDXStyle.TextAppearance.TextNode" />
<!-- Accessed -->
<androidx.appcompat.widget.AppCompatTextView
@@ -127,16 +127,6 @@
android:layout_height="wrap_content"
android:text="@string/entry_UUID"
style="@style/KeepassDXStyle.TextAppearance.LabelTextStyle" />
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/entry_UUID"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textIsSelectable="true"
style="@style/KeepassDXStyle.TextAppearance.LabelTextStyle" />
</HorizontalScrollView>
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
@@ -145,7 +135,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textIsSelectable="true"
style="@style/KeepassDXStyle.TextAppearance.TextEntryItem" />
style="@style/KeepassDXStyle.TextAppearance.TextNode" />
</HorizontalScrollView>
</LinearLayout>
</androidx.cardview.widget.CardView>

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2019 Jeremy Jamet / Kunzisoft.
This file is part of KeePassDX.
KeePassDX is free software: you can redistribute it and/or modify
@@ -17,38 +17,128 @@
You should have received a copy of the GNU General Public License
along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
-->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/nodes_list"
android:contentDescription="@string/content_description_node_children"
android:scrollbars="vertical"
android:scrollbarStyle="insideOverlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:attr/windowBackground"
android:paddingBottom="?attr/actionBarSize"
android:clipToPadding="false" />
<LinearLayout
android:id="@+id/not_found_container"
android:layout_gravity="center"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/not_found_img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/img_not_found"/>
<TextView
android:id="@+id/not_found_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/no_results"/>
</LinearLayout>
</FrameLayout>
<androidx.core.widget.NestedScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:importantForAutofill="noExcludeDescendants">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary">
<LinearLayout
android:id="@+id/title_block"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="@dimen/default_margin"
android:background="@drawable/background_repeat"
android:gravity="center"
style="@style/KeepassDXStyle.TextAppearance.Default">
<!-- Icon -->
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/group_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="@dimen/default_margin"
android:src="@drawable/ic_blank_32dp"
style="@style/KeepassDXStyle.Icon"/>
<!-- Name -->
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/group_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
style="@style/KeepassDXStyle.Expanded.Title" />
</LinearLayout>
</FrameLayout>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/default_margin"
tools:targetApi="o">
<!-- Note -->
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/group_note_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/entry_notes"
style="@style/KeepassDXStyle.TextAppearance.LabelTextStyle" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/group_note"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textMultiLine"
style="@style/KeepassDXStyle.TextAppearance.TextNode" />
<!-- Expiration -->
<com.kunzisoft.keepass.view.DateTimeFieldView
android:id="@+id/group_expiration"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<!-- Created -->
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/group_created_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/entry_created"
style="@style/KeepassDXStyle.TextAppearance.LabelTextStyle" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/group_created"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/KeepassDXStyle.TextAppearance.TextNode" />
<!-- Modified -->
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/group_modified_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/entry_modified"
style="@style/KeepassDXStyle.TextAppearance.LabelTextStyle" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/group_modified"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/KeepassDXStyle.TextAppearance.TextNode" />
<!-- UUID -->
<LinearLayout
android:id="@+id/group_UUID_container"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/group_UUID_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/entry_UUID"
style="@style/KeepassDXStyle.TextAppearance.LabelTextStyle" />
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/group_UUID_reference"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textIsSelectable="true"
style="@style/KeepassDXStyle.TextAppearance.TextNode" />
</HorizontalScrollView>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>

View File

@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2019 Jeremy Jamet / Kunzisoft.
This file is part of KeePassDX.
KeePassDX 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.
KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
-->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/nodes_list"
android:contentDescription="@string/content_description_node_children"
android:scrollbars="vertical"
android:scrollbarStyle="insideOverlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:attr/windowBackground"
android:paddingBottom="?attr/actionBarSize"
android:clipToPadding="false" />
<LinearLayout
android:id="@+id/not_found_container"
android:layout_gravity="center"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/not_found_img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/img_not_found"/>
<TextView
android:id="@+id/not_found_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/no_results"/>
</LinearLayout>
</FrameLayout>

View File

@@ -54,7 +54,7 @@
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/item_attachment_title"
style="@style/KeepassDXStyle.TextAppearance.TextEntryItem"
style="@style/KeepassDXStyle.TextAppearance.TextNode"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_toStartOf="@+id/item_attachment_size_container"

View File

@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2021 Jeremy Jamet / Kunzisoft.
This file is part of KeePassDX.
KeePassDX 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.
KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/breadcrumb_group"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal"
android:gravity="center_vertical"
android:baselineAligned="false"
android:background="?android:attr/selectableItemBackground">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/group_icon"
android:layout_width="26dp"
android:layout_height="26dp"
android:layout_marginStart="6dp"
android:layout_marginLeft="6dp"
android:layout_marginEnd="6dp"
android:layout_marginRight="6dp"
android:layout_gravity="center_vertical"
android:contentDescription="@string/hint_icon_name"
android:scaleType="fitXY" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/group_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:text="@string/root"
android:maxLines="2"
android:ellipsize="end"
style="@style/KeepassDXStyle.TextAppearance.SubTitle.TextOnPrimary"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"/>
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/group_separator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_arrow_right_white_24dp"
android:tint="?attr/textColorInverse"
android:importantForAccessibility="no"
android:layout_marginEnd="6dp"
android:layout_marginRight="6dp"
style="@style/KeepassDXStyle.TextAppearance.SubTitle.TextOnPrimary"
android:textStyle="bold"
tools:targetApi="jelly_bean" />
</LinearLayout>

View File

@@ -0,0 +1,76 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2021 Jeremy Jamet / Kunzisoft.
This file is part of KeePassDX.
KeePassDX 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.
KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/breadcrumb_group"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal"
android:gravity="center_vertical"
android:baselineAligned="false"
android:background="?android:attr/selectableItemBackground">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="6dp"
android:layout_marginRight="6dp"
android:gravity="center_vertical">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/group_icon"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_gravity="end|center_vertical"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:scaleType="fitXY" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/group_numbers"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="3"
style="@style/KeepassDXStyle.TextAppearance.Info" />
</RelativeLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="start|center_vertical"
android:layout_marginEnd="6dp"
android:layout_marginRight="6dp"
android:layout_weight="1">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/group_name"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/root"
android:maxLines="2"
android:ellipsize="end"
style="@style/KeepassDXStyle.TextAppearance.Title.TextOnPrimary" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/group_meta"
android:layout_height="match_parent"
android:layout_width="match_parent"
tools:text="7543A7EAB2EA7CFD1394F1615EBEB08C"
android:lines="1"
android:singleLine="true"
style="@style/KeepassDXStyle.TextAppearance.Meta.TextOnPrimary" />
</LinearLayout>
</LinearLayout>

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

@@ -29,7 +29,7 @@
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/entry_history_last_modified"
tools:text = "Last Modified"
style="@style/KeepassDXStyle.TextAppearance.TextEntryItem"
style="@style/KeepassDXStyle.TextAppearance.TextNode"
android:gravity="center"
android:layout_width="0dp"
android:layout_height="wrap_content"
@@ -38,7 +38,7 @@
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/entry_history_title"
tools:text = "Title"
style="@style/KeepassDXStyle.TextAppearance.TextEntryItem"
style="@style/KeepassDXStyle.TextAppearance.TextNode"
android:maxLines="6"
android:ellipsize="end"
android:gravity="center"
@@ -49,7 +49,7 @@
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/entry_history_username"
tools:text = "Username"
style="@style/KeepassDXStyle.TextAppearance.TextEntryItem"
style="@style/KeepassDXStyle.TextAppearance.TextNode"
android:maxLines="6"
android:ellipsize="end"
android:gravity="center"

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"
@@ -101,32 +102,41 @@
android:lines="1"
android:singleLine="true"
tools:text="7543A7EAB2EA7CFD1394F1615EBEB08C" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/node_path"
style="@style/KeepassDXStyle.TextAppearance.Entry.Meta"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="2"
android:visibility="gone"
tools:text="Database / Group A / Group B" />
</LinearLayout>
<LinearLayout
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"
@@ -135,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>
@@ -170,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,133 +17,155 @@
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"
xmlns:tools="http://schemas.android.com/tools"
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/edit"
android:orientation="vertical"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:importantForAutofill="noExcludeDescendants"
tools:targetApi="o">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/explanation_text"
android:layout_height="wrap_content"
android:layout_width="0dp"
android:gravity="center"
android:layout_marginBottom="8dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintRight_toRightOf="parent"
style="@style/KeepassDXStyle.TextAppearance.SmallTitle"/>
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/switch_element"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/enable"
android:layout_marginStart="20dp"
android:layout_marginLeft="20dp"
android:layout_marginEnd="20dp"
android:layout_marginRight="20dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/explanation_text" />
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/edit"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/switch_element">
<LinearLayout
android:id="@+id/duration_days_picker"
android:layout_width="wrap_content"
android:importantForAutofill="noExcludeDescendants"
tools:targetApi="o">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/explanation_text"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_width="0dp"
android:gravity="center"
android:layout_marginStart="20dp"
android:layout_marginLeft="20dp"
android:layout_marginEnd="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="8dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintEnd_toStartOf="@+id/duration_hours_picker"
app:layout_constraintRight_toLeftOf="@+id/duration_hours_picker">
<NumberPicker
android:id="@+id/days_picker"
android:scrollbarFadeDuration="0"
android:scrollbarDefaultDelayBeforeFade="0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:src="@drawable/ic_day_white_24dp"
app:tint="?android:attr/textColor"
android:contentDescription="@string/digits" />
</LinearLayout>
<LinearLayout
android:id="@+id/duration_hours_picker"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
tools:ignore="HardcodedText"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toEndOf="@+id/duration_days_picker"
app:layout_constraintLeft_toRightOf="@+id/duration_days_picker"
app:layout_constraintEnd_toStartOf="@+id/duration_time_picker"
app:layout_constraintRight_toLeftOf="@+id/duration_time_picker">
<NumberPicker
android:id="@+id/hours_picker"
android:scrollbarFadeDuration="0"
android:scrollbarDefaultDelayBeforeFade="0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:textStyle="bold"
android:text=":" />
</LinearLayout>
<LinearLayout
android:id="@+id/duration_time_picker"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
tools:ignore="HardcodedText"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toEndOf="@+id/duration_hours_picker"
app:layout_constraintLeft_toRightOf="@+id/duration_hours_picker"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintRight_toRightOf="parent">
<NumberPicker
android:id="@+id/minutes_picker"
android:scrollbarFadeDuration="0"
android:scrollbarDefaultDelayBeforeFade="0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:textSize="28sp"
android:text="'"/>
<NumberPicker
android:id="@+id/seconds_picker"
android:scrollbarFadeDuration="0"
android:scrollbarDefaultDelayBeforeFade="0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:textSize="28sp"
android:text="''"/>
</LinearLayout>
app:layout_constraintRight_toRightOf="parent"
style="@style/KeepassDXStyle.TextAppearance.SmallTitle"/>
<Button
android:id="@+id/explanation_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginStart="20dp"
android:layout_marginLeft="20dp"
android:layout_marginEnd="20dp"
android:layout_marginRight="20dp"
tools:text="permission"
style="@style/Widget.AppCompat.Button.Small"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/explanation_text" />
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/switch_element"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/enable"
android:layout_marginTop="8dp"
android:layout_marginStart="20dp"
android:layout_marginLeft="20dp"
android:layout_marginEnd="20dp"
android:layout_marginRight="20dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/explanation_button" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/switch_element">
<LinearLayout
android:id="@+id/duration_days_picker"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintEnd_toStartOf="@+id/duration_hours_picker"
app:layout_constraintRight_toLeftOf="@+id/duration_hours_picker">
<NumberPicker
android:id="@+id/days_picker"
android:scrollbarFadeDuration="0"
android:scrollbarDefaultDelayBeforeFade="0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:src="@drawable/ic_day_white_24dp"
app:tint="?android:attr/textColor"
android:contentDescription="@string/digits" />
</LinearLayout>
<LinearLayout
android:id="@+id/duration_hours_picker"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
tools:ignore="HardcodedText"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toEndOf="@+id/duration_days_picker"
app:layout_constraintLeft_toRightOf="@+id/duration_days_picker"
app:layout_constraintEnd_toStartOf="@+id/duration_time_picker"
app:layout_constraintRight_toLeftOf="@+id/duration_time_picker">
<NumberPicker
android:id="@+id/hours_picker"
android:scrollbarFadeDuration="0"
android:scrollbarDefaultDelayBeforeFade="0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:textStyle="bold"
android:text=":" />
</LinearLayout>
<LinearLayout
android:id="@+id/duration_time_picker"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
tools:ignore="HardcodedText"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toEndOf="@+id/duration_hours_picker"
app:layout_constraintLeft_toRightOf="@+id/duration_hours_picker"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintRight_toRightOf="parent">
<NumberPicker
android:id="@+id/minutes_picker"
android:scrollbarFadeDuration="0"
android:scrollbarDefaultDelayBeforeFade="0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:textSize="28sp"
android:text="'"/>
<NumberPicker
android:id="@+id/seconds_picker"
android:scrollbarFadeDuration="0"
android:scrollbarDefaultDelayBeforeFade="0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:textSize="28sp"
android:text="''"/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

View File

@@ -26,6 +26,6 @@
android:id="@+id/date_time_value"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/KeepassDXStyle.TextAppearance.TextEntryItem" />
style="@style/KeepassDXStyle.TextAppearance.TextNode" />
</LinearLayout>
</LinearLayout>

View File

@@ -20,7 +20,7 @@
android:focusable="false"
android:cursorVisible="false"
android:focusableInTouchMode="false"
style="@style/KeepassDXStyle.TextAppearance.TextEntryItem"
style="@style/KeepassDXStyle.TextAppearance.TextNode"
tools:text="2020-03-04 05:00" />
</com.google.android.material.textfield.TextInputLayout>
<androidx.appcompat.widget.SwitchCompat

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,30 +29,70 @@
android:id="@+id/template_header_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?attr/cardViewStyle"
android:visibility="gone">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/card_view_padding">
style="?attr/cardViewStyle">
<!-- Icon -->
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/template_icon_button"
<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_margin="4dp"
android:src="@drawable/ic_blank_32dp"
android:contentDescription="@string/content_description_entry_icon"
android:layout_gravity="center"/>
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" />
<!-- Title -->
<FrameLayout
android:id="@+id/template_title_container"
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="@dimen/card_view_padding">
<!-- Icon -->
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/template_icon_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:elevation="8dp"
android:layout_gravity="center_horizontal"
android:src="@drawable/ic_blank_32dp"
android:contentDescription="@string/content_description_entry_icon"/>
<!-- Title -->
<FrameLayout
android:id="@+id/template_title_container"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</FrameLayout>
</androidx.cardview.widget.CardView>
<LinearLayout

View File

@@ -88,7 +88,7 @@
<string name="space">المساحة</string>
<string name="search_label">البحث</string>
<string name="sort_menu">الفرز</string>
<string name="sort_ascending">تصاعدياً</string>
<string name="sort_ascending">تصاعدي</string>
<string name="sort_title">العنوان</string>
<string name="sort_username">اسم المستخدِم</string>
<string name="sort_creation_time">تاريخ الإنشاء</string>
@@ -472,7 +472,7 @@
<string name="error_import_app_properties">خطأ أثناء استيراد خصائص التطبيق</string>
<string name="error_export_app_properties">خطأ أثناء تصدير خصائص التطبيق</string>
<string name="warning_database_info_changed">غُيِّرت معلومات قاعدة البيانات من خارج هذا التطبيق.</string>
<string name="warning_database_info_changed_options">اكتب فوق التعديلات الخارجية عن طريق حفظ قاعدة البيانات أو أعد تحميلها لتضمين هذه التغييرات.</string>
<string name="warning_database_info_changed_options">اكتب فوق التعديلات الخارجية عن طريق حفظ قاعدة البيانات أو أعد تحميلها لجلب آخر التغييرات.</string>
<string name="open_advanced_unlock_prompt_store_credential">افتح محث فك القفل المتقدم لتخزين بيانات الاعتماد</string>
<string name="open_advanced_unlock_prompt_unlock_database">افتح محث فك القفل المتقدم لفتح قاعدة البيانات</string>
<string name="credential_before_click_advanced_unlock_button">اكتب كلمة السر، وأنقر هذا الزر.</string>
@@ -483,4 +483,10 @@
<string name="keyboard_previous_fill_in_title">إجراء لمس تلقائي</string>
<string name="keyboard_previous_lock_title">اقفل قاعدة البيانات</string>
<string name="education_advanced_unlock_title">فك القفل المتقدم لقاعدة البيانات</string>
<string name="hint_icon_name">اسم الأيقونة</string>
<string name="autofill_manual_selection_title">اختيار يدوي</string>
<string name="description_app_properties">خصائص KeePassDX لإدارة إعدادات التطبيقات</string>
<string name="warning_empty_recycle_bin">هل تريد حذف جميع العقد نهائيًا من سلة المهملات؟</string>
<string name="autofill_save_search_info_title">احفظ معلومات البحث</string>
<string name="autofill_ask_to_save_data_title">اسأل لحفظ البيانات</string>
</resources>

View File

@@ -29,4 +29,16 @@
<string name="discard">বাতিল</string>
<string name="validate">সত্যায়ন</string>
<string name="content_description_background">পটভূমি</string>
<string name="entry_cancel">বাতিল</string>
<string name="entry_notes">দ্রষ্টব্য</string>
<string name="entry_created">তৈরিকৃত</string>
<string name="entry_expires">মেয়াদ</string>
<string name="entry_UUID">বিশেষ শনাক্তকরণ সংকেত(ইউইউআইডি)</string>
<string name="entry_history">ইতিহাস</string>
<string name="entry_attachments">সংযুক্তি</string>
<string name="entry_keyfile">চাবিনথি(কিফাইল)</string>
<string name="entry_modified">পরিবর্তিত</string>
<string name="clipboard_cleared">ক্লিপবোর্ড পরিষ্কার করা হয়েছে</string>
<string name="entry_accessed">শেষ ব্যবহার</string>
<string name="clipboard_error">কিছু ডিভাইস অ্যাপ্লিকেশনগুলোকে ক্লিপবোর্ড ব্যবহার করতে দেয় না।</string>
</resources>

View File

@@ -602,4 +602,7 @@
<string name="autofill_select_entry">Vyberte položku…</string>
<string name="autofill_manual_selection_summary">Zobrazit možnosti umožňující uživateli si vybrat položku z databáze</string>
<string name="autofill_manual_selection_title">Ruční výběr</string>
<string name="hint_icon_name">Jméno symbolu</string>
<string name="warning_exact_alarm">Nepovolili jste aplikaci použít přesný alarm. Výsledkem je, že funkce požadující časovač nebudou provedeny v přesný okamžik.</string>
<string name="permission">Povolení</string>
</resources>

View File

@@ -449,7 +449,7 @@
<string name="autofill_application_id_blocklist_summary">Blokeringsliste der forhindrer automatisk udfyldning af programmer</string>
<string name="autofill_application_id_blocklist_title">Blokeringsliste for program</string>
<string name="keyboard_previous_fill_in_summary">Skift automatisk tilbage til det forrige tastatur efter udførelse af \"Automatisk tastehandling\"</string>
<string name="keyboard_previous_fill_in_title">Auto nøglehandling</string>
<string name="keyboard_previous_fill_in_title">Automatisk tastehandling</string>
<string name="keyboard_previous_database_credentials_summary">Skift automatisk tilbage til det forrige tastatur på databasens legitimationsskærm</string>
<string name="keyboard_previous_database_credentials_title">Skærmbilledet til databaselegitimationsoplysninger</string>
<string name="keyboard_change">Skifte tastatur</string>

View File

@@ -160,7 +160,7 @@
<string name="clipboard_notifications_title">Zwischenablage-Benachrichtigung</string>
<string name="clipboard_notifications_summary">Benachrichtigungen zur Zwischenablage anzeigen, um beim Betrachten eines Eintrags Felder kopieren zu können</string>
<string name="lock_database_screen_off_title">Bildschirmsperre</string>
<string name="lock_database_screen_off_summary">Datenbank sperren, einige Sekunden nachdem der Bildschirm ausgeschaltet wird</string>
<string name="lock_database_screen_off_summary">Datenbank einige Sekunden, nachdem der Bildschirm ausgeschaltet ist, sperren</string>
<string name="create_keepass_file">Neue Datenbank erstellen</string>
<string name="assign_master_key">Hauptschlüssel zuweisen</string>
<string name="path">Pfad</string>
@@ -253,7 +253,7 @@
<string name="education_sort_title">Sortierung der Einträge</string>
<string name="education_sort_summary">Auswählen, wie Einträge und Gruppen sortiert werden.</string>
<string name="education_donation_title">Mitmachen</string>
<string name="education_donation_summary">Mithelfen, um Stabilität und Sicherheit zu verbessern und weitere Funktionen zu ermöglichen.</string>
<string name="education_donation_summary">Mithelfen, um Stabilität und Sicherheit zu verbessern sowie weitere Funktionen zu ermöglichen.</string>
<string name="html_text_ad_free">Anders als viele andere Passwortmanager ist dieser &lt;strong&gt;werbefrei&lt;/strong&gt;, &lt;strong&gt;quelloffen&lt;/strong&gt; und unter einer &lt;strong&gt;Copyleft-Lizenz&lt;/strong&gt;. Es werden keine persönlichen Daten gesammelt, in welcher Form auch immer, unabhängig von der verwendeten Version (kostenlos oder Pro).</string>
<string name="html_text_buy_pro">Mit dem Kauf der Pro-Version erhalten Sie Zugriff auf diesen &lt;strong&gt;visuellen Stil&lt;/strong&gt; und unterstützen insbesondere &lt;strong&gt;die Umsetzung gemeinschaftlicher Projekte.&lt;/strong&gt;</string>
<string name="html_text_feature_generosity">Dieser &lt;strong&gt;visuelle Stil&lt;/strong&gt; wurde wegen Ihrer Großzügigkeit freigeschaltet.</string>
@@ -343,7 +343,7 @@
<string name="list_groups_show_number_entries_summary">Anzahl der Einträge in einer Gruppe anzeigen</string>
<string name="content_description_add_node">Knoten hinzufügen</string>
<string name="lock_database_back_root_title">„Zurück“ drücken, um zu sperren</string>
<string name="clear_clipboard_notification_summary">Die Datenbank sperren, wenn die Dauer der Zwischenablage abläuft oder die Benachrichtigung geschlossen wird, nachdem Sie sie zu benutzen begonnen haben</string>
<string name="clear_clipboard_notification_summary">Die Datenbank sperren, wenn die Dauer der Zwischenablage abläuft oder die Benachrichtigung geschlossen wird, nachdem Sie sie benutzt haben</string>
<string name="content_description_node_children">Untergeordneter Knotenpunkt</string>
<string name="content_description_keyfile_checkbox">Schlüsseldatei-Kontrollkästchen</string>
<string name="error_move_entry_here">Hierher kann kein Eintrag verschoben werden.</string>
@@ -450,7 +450,7 @@
<string name="autofill_auto_search_summary">Suchergebnisse automatisch nach Web-Domain oder Anwendungs-ID vorschlagen</string>
<string name="autofill_auto_search_title">Automatische Suche</string>
<string name="autofill_manual_selection_title">Manuelle Auswahl</string>
<string name="autofill_manual_selection_summary">Manuelle Auswahl des Datenbank-Eintrags ermöglichen</string>
<string name="autofill_manual_selection_summary">Manuelle Auswahl des Datenbankeintrags ermöglichen</string>
<string name="lock_database_show_button_summary">Zeigt die Sperrtaste in der Benutzeroberfläche an</string>
<string name="lock_database_show_button_title">Sperrtaste anzeigen</string>
<string name="autofill_preference_title">Einstellungen für automatisches Ausfüllen</string>
@@ -488,13 +488,13 @@
\nMit diesem Upload könnte Ihre Datenbank sehr groß werden und an Geschwindigkeit verlieren.</string>
<string name="upload_attachment">%1$s hochladen</string>
<string name="education_add_attachment_title">Anhang hinzufügen</string>
<string name="database_data_remove_unlinked_attachments_summary">Entfernt Anhänge die in der Datenbank enthalten, aber keinem Eintrag zugeordnet sind</string>
<string name="database_data_remove_unlinked_attachments_summary">Entfernt Anhänge, die in der Datenbank enthalten, aber keinem Eintrag zugeordnet sind</string>
<string name="warning_sure_add_file">Soll die Datei trotzdem hinzugefügt werden\?</string>
<string name="show_uuid_summary">Zeigt die mit einem Eintrag oder einer Gruppe verknüpfte UUID an</string>
<string name="show_uuid_title">UUID anzeigen</string>
<string name="autofill_read_only_save">Das Speichern von Daten ist für eine als schreibgeschützt geöffnete Datenbank nicht zulässig.</string>
<string name="autofill_close_database_title">Datenbank schließen</string>
<string name="keyboard_previous_lock_summary">Automatisches Zurückschalten zur vorherigen Tastatur nach dem Sperren der Datenbank</string>
<string name="keyboard_previous_lock_summary">Nach dem Sperren der Datenbank automatisch zur vorherigen Tastatur zurückschalten</string>
<string name="keyboard_previous_lock_title">Datenbank sperren</string>
<string name="notification">Benachrichtigung</string>
<string name="biometric_security_update_required">Biometrisches Sicherheitsupdate erforderlich.</string>
@@ -503,12 +503,12 @@
<string name="save_mode">Speichermodus</string>
<string name="search_mode">Suchmodus</string>
<string name="error_registration_read_only">Das Speichern eines neuen Elements in einer schreibgeschützten Datenbank ist nicht zulässig</string>
<string name="autofill_save_search_info_summary">Suchdaten bei manueller Auswahl einer Eingabe wenn möglich speichern</string>
<string name="autofill_save_search_info_summary">Suchdaten bei manueller Auswahl einer Eingabe, wenn möglich, speichern</string>
<string name="autofill_ask_to_save_data_summary">Nachfragen, ob die Daten gespeichert werden sollen, wenn ein Formular ausgefüllt ist</string>
<string name="autofill_ask_to_save_data_title">Speichern von Daten abfragen</string>
<string name="autofill_save_search_info_title">Suchinformationen speichern</string>
<string name="autofill_close_database_summary">Datenbank nach Auswahl eines Eintrags zum automatischen Ausfüllen schließen</string>
<string name="keyboard_save_search_info_summary">Nachdem eine URL mit KeePassDX geteilt und ein Eintrag gewählt wurde, wird versucht sich diesen Eintrag für weitere Nutzungen zu merken</string>
<string name="keyboard_save_search_info_summary">Nachdem eine URL mit KeePassDX geteilt und ein Eintrag gewählt wurde, wird versucht, sich diesen Eintrag für die weitere Nutzung zu merken</string>
<string name="keyboard_save_search_info_title">Gemeinsam genutzte Informationen speichern</string>
<string name="warning_empty_recycle_bin">Sollen alle ausgewählten Knoten wirklich aus dem Papierkorb gelöscht werden\?</string>
<string name="error_field_name_already_exists">Der Feldname existiert bereits.</string>
@@ -519,8 +519,8 @@
<string name="advanced_unlock_prompt_store_credential_title">Moderne Entsperrerkennung</string>
<string name="education_advanced_unlock_summary">Verknüpfen Sie Ihr Passwort mit Ihren gescannten Biometriedaten oder Daten zur Geräteanmeldung, um schnell Ihre Datenbank zu entsperren.</string>
<string name="education_advanced_unlock_title">Moderne Entsperrung der Datenbank</string>
<string name="advanced_unlock_timeout">Verfallzeit der modernen Entsperrung</string>
<string name="temp_advanced_unlock_timeout_summary">Laufzeit der modernen Entsperrung bevor ihr Inhalt gelöscht wird</string>
<string name="advanced_unlock_timeout">Verfallszeit der modernen Entsperrung</string>
<string name="temp_advanced_unlock_timeout_summary">Laufzeit der modernen Entsperrung, bevor ihr Inhalt gelöscht wird</string>
<string name="temp_advanced_unlock_timeout_title">Verfall der modernen Entsperrung</string>
<string name="temp_advanced_unlock_enable_summary">Keine zur modernen Entsperrung verwendeten, verschlüsselten Inhalte speichern</string>
<string name="temp_advanced_unlock_enable_title">Befristete moderne Entsperrung</string>
@@ -530,7 +530,7 @@
<string name="advanced_unlock_prompt_extract_credential_title">Datenbank mit moderner Entsperrerkennung öffnen</string>
<string name="enter">Eingabetaste</string>
<string name="backspace">Rücktaste</string>
<string name="select_entry">Wähle Eintrag</string>
<string name="select_entry">Eintrag auswählen</string>
<string name="back_to_previous_keyboard">Zurück zur vorherigen Tastatur</string>
<string name="custom_fields">Benutzerdefinierte Felder</string>
<string name="advanced_unlock_delete_all_key_warning">Alle Verschlüsselungsschlüssel, die mit der modernen Entsperrerkennung zusammenhängen, löschen\?</string>
@@ -576,7 +576,7 @@
<string name="unit_byte">B</string>
<string name="download_canceled">Abgebrochen!</string>
<string name="autofill_inline_suggestions_keyboard">Vorschläge für automatisches Ausfüllen hinzugefügt.</string>
<string name="autofill_inline_suggestions_summary">Wenn möglich unmittelbar Vorschläge zum automatischen Ausfüllen auf einer kompatiblen Tastatur anzeigen</string>
<string name="autofill_inline_suggestions_summary">Wenn möglich, Vorschläge zum automatischen Ausfüllen direkt auf einer kompatiblen Tastatur anzeigen</string>
<string name="properties">Eigenschaften</string>
<string name="description_app_properties">KeePassDX-Eigenschaften zur Verwaltung der App-Einstellungen</string>
<string name="secure_note">Sicherer Hinweis</string>
@@ -613,4 +613,7 @@
<string name="menu_external_icon">Externes Symbol</string>
<string name="show_otp_token_summary">Zeigt OTP-Tokens in der Liste der Einträge an</string>
<string name="show_otp_token_title">OTP-Token anzeigen</string>
<string name="hint_icon_name">Symbolname</string>
<string name="permission">Erlaubnis</string>
<string name="warning_exact_alarm">Sie haben der App nicht erlaubt, einen genauen Alarm zu verwenden. Deshalb werden die Funktionen, die einen Timer erfordern, nicht zu einer genauen Zeit ausgeführt.</string>
</resources>

View File

@@ -602,4 +602,7 @@
<string name="autofill_manual_selection_summary">Εμφάνισης επιλογής για να επιτρέψει στον χρήστη να επιλέξει την καταχώρηση βάσης δεδομένων</string>
<string name="autofill_manual_selection_title">Χειροκίνητη επιλογή</string>
<string name="autofill_select_entry">Επιλογή καταχώρησης…</string>
<string name="hint_icon_name">Όνομα εικονιδίου</string>
<string name="warning_exact_alarm">Δεν έχετε επιτρέψει στην εφαρμογή να χρησιμοποιεί ακριβή συναγερμό. Ως αποτέλεσμα, οι λειτουργίες που απαιτούν χρονόμετρο δεν θα γίνονται με ακριβή χρόνο.</string>
<string name="permission">Άδεια</string>
</resources>

View File

@@ -604,4 +604,7 @@
<string name="autofill_manual_selection_summary">Mostrar opción para permitir al usuario seleccionar la entrada de la base de datos</string>
<string name="autofill_manual_selection_title">Selección manual</string>
<string name="autofill_select_entry">Seleccionar entrada…</string>
<string name="hint_icon_name">Nombre del icono</string>
<string name="warning_exact_alarm">No ha permitido que la app use una alarma exacta. Como resultado, las funciones que requieren un temporizador no se harán con una hora exacta.</string>
<string name="permission">Permiso</string>
</resources>

View File

@@ -569,9 +569,9 @@
<string name="success_import_app_properties">Propriétés de l\'application importées</string>
<string name="description_app_properties">Propriétés KeePassDX pour gérer les paramètres de l\'application</string>
<string name="export_app_properties_summary">Créer un fichier pour exporter les propriétés de l\'application</string>
<string name="export_app_properties_title">Export des propriétés de l\'app</string>
<string name="export_app_properties_title">Exporter les propriétés de l\'application</string>
<string name="import_app_properties_summary">Sélectionner un fichier pour importer les propriétés de l\'application</string>
<string name="import_app_properties_title">Importation des propriétés de l\'appli</string>
<string name="import_app_properties_title">Importer les propriétés de l\'application</string>
<string name="error_start_database_action">Une erreur s\'est produite lors de l\'exécution d\'une action sur la base de données.</string>
<string name="error_move_group_here">Vous ne pouvez pas déplacer un groupe ici.</string>
<string name="error_word_reserved">Ce mot est réservé et ne peut pas être utilisé.</string>
@@ -609,4 +609,5 @@
<string name="menu_external_icon">Icône externe</string>
<string name="autofill_manual_selection_summary">Afficher l\'option permettant à l\'utilisateur de sélectionner l\'entrée de la base de données</string>
<string name="autofill_manual_selection_title">Sélection manuelle</string>
<string name="hint_icon_name">Nom de l\'icône</string>
</resources>

View File

@@ -597,4 +597,5 @@
<string name="autofill_select_entry">Odaberi unos …</string>
<string name="menu_external_icon">Vanjska ikona</string>
<string name="seed">Tajna fraza</string>
<string name="hint_icon_name">Ime ikone</string>
</resources>

View File

@@ -455,4 +455,37 @@
<string name="public_key">Kunci publik</string>
<string name="private_key">Kunci pribadi</string>
<string name="membership">Keanggotaan</string>
<string name="hint_icon_name">Nama ikon</string>
<string name="card_verification_value">CVV</string>
<string name="personal_identification_number">PIN</string>
<string name="id_card">Kartu ID</string>
<string name="email">Surel</string>
<string name="email_address">Alamat surel</string>
<string name="ssid">SSID</string>
<string name="token">Token</string>
<string name="seed">Bibit</string>
<string name="bank_identifier_code">SWIFT/BIC</string>
<string name="secure_note">Catatan Aman</string>
<string name="menu_external_icon">Ikon eksternal</string>
<string name="holder">Penyangga</string>
<string name="autofill_select_entry">Pilih entri…</string>
<string name="biometric_auto_open_prompt_summary">Secara otomatis meminta buka kunci lanjutan jika basis data diatur menggunakan itu</string>
<string name="temp_advanced_unlock_enable_title">Buka kunci lanjutan sementara</string>
<string name="temp_advanced_unlock_enable_summary">Jangan simpan konten terenkripsi apa pun untuk menggunakan buka kunci lanjutan</string>
<string name="temp_advanced_unlock_timeout_summary">Durasi penggunaan buka kunci lanjutan sebelum menghapus konten tersebut</string>
<string name="advanced_unlock_timeout">Batas waktu membuka kunci lanjutan</string>
<string name="advanced_unlock_delete_all_key_warning">Hapus semua kunci enkripsi yang terkait pengenalan buka kunci lanjutan\?</string>
<string name="templates_group_enable_title">Penggunaan templat</string>
<string name="biometric_delete_all_key_summary">Hapus semua kunci enkripsi yang terkait pengenalan buka kunci lanjutan</string>
<string name="max_history_items_summary">Batasi jumlah item riwayat per entri</string>
<string name="template_group_name">Templat</string>
<string name="date_of_issue">Tanggal diterbitkan</string>
<string name="template">Templat</string>
<string name="place_of_issue">Tempat diterbitkan</string>
<string name="device_credential_unlock_enable_summary">Membiarkan Anda menggunakan kredensial perangkat Anda untuk membuka basis data</string>
<string name="unavailable_feature_text">Tidak bisa memulai fitur ini.</string>
<string name="templates_group_uuid_title">Grup templat</string>
<string name="max_history_size_summary">Batasi ukuran riwayat per entri</string>
<string name="max_history_size_title">Ukuran maksimum</string>
<string name="biometric_delete_all_key_title">Hapus kunci enkripsi</string>
</resources>

View File

@@ -605,4 +605,5 @@
<string name="autofill_manual_selection_summary">Mostra l\'opzione che permette all\'utente di scegliere la voce nel database</string>
<string name="autofill_manual_selection_title">Selezione manuale</string>
<string name="autofill_select_entry">Seleziona voce…</string>
<string name="hint_icon_name">Nome icona</string>
</resources>

View File

@@ -600,4 +600,5 @@
<string name="templates_group_uuid_title">テンプレート グループ</string>
<string name="menu_external_icon">外部アイコン</string>
<string name="template">テンプレート</string>
<string name="hint_icon_name">アイコン名</string>
</resources>

View File

@@ -499,4 +499,105 @@
<string name="content_description_otp_information">Éngangspassordsinfo</string>
<string name="template_group_name">Maler</string>
<string name="content_description_credentials_information">Identitetdetaljsinfo</string>
<string name="templates_group_enable_summary">Bruk dynamiske maler for å fylle inn feltene for en oppføring</string>
<string name="templates">Maler</string>
<string name="error_word_reserved">Dette ordet er reservert og kan ikke brukes.</string>
<string name="enter">Enter-tast</string>
<string name="hint_icon_name">Ikonnavn</string>
<string name="menu_reload_database">Last inn databasen igjen</string>
<string name="menu_keystore_remove_key">Slett avansert opplåsningsnøkkel</string>
<string name="remember_database_locations_summary">Holder øye med hvor databaser lagres</string>
<string name="remember_keyfile_locations_title">Husk hvor lagringsfiler er lagret</string>
<string name="remember_keyfile_locations_summary">Holder øye med hvor nøkkelfiler er lagret</string>
<string name="show_recent_files_summary">Vis hvor nylige databaser er lagret</string>
<string name="description_app_properties">KeePassDX-egenskaper for håndtering av programinnstillinger</string>
<string name="success_import_app_properties">Programegenskaper importert</string>
<string name="filter">Filter</string>
<string name="warning_database_info_changed">Informasjonen som er å finne i databsefilen din har blitt endret utenfor programmet.</string>
<string name="warning_database_info_changed_options">Overskriv eksisterende endringer ved å lagre databasen, eller last den inn igjen med de siste endringene.</string>
<string name="warning_file_too_big">En KeePass-database er ment å kun inneholde små bruksfiler (som f-eks PGP-nøkkelfiler).
\n
\nDatabasen din kan bli veldig stor og få redusert ytelse med denne opplastingen.</string>
<string name="warning_empty_keyfile_explanation">Innholdet i nøkkelfilen bør aldri endre seg, og skal helst inneholde kun tilfeldig generert data.</string>
<string name="warning_database_revoked">Tilgang til filen ble tilbakekalt av filbehandleren. Lukk databasen og åpne den igjen fra dens posisjon.</string>
<string name="error_import_app_properties">Feil under importering av programegenskaper</string>
<string name="success_export_app_properties">Programegenskaper eksportert</string>
<string name="error_export_app_properties">Feil under eksportering av programegenskaper</string>
<string name="place_of_issue">Utsteder</string>
<string name="cryptocurrency">Kryptovaluta-lommebok</string>
<string name="seed">Utgangspunkt</string>
<string name="debit_credit_card">Bankkort</string>
<string name="secure_note">Sikkehetsnotis</string>
<string name="standard">Forvalg</string>
<string name="bank_identifier_code">SWIFT/BIC</string>
<string name="error_registration_read_only">Å lagre et nytt element tillates ikke i skrivebeskyttet database.</string>
<string name="error_database_uri_null">Database-URI kan ikke innhentes.</string>
<string name="error_rebuild_list">Kunne ikke gjenoppbygge listen på riktig måte.</string>
<string name="error_upload_file">En feil inntraff under opplasting av fildataen.</string>
<string name="error_remove_file">En feil inntraff under fjerning av fildataen.</string>
<string name="properties">Egenskaper</string>
<string name="advanced_unlock_prompt_extract_credential_title">Åpne databasen med avansert opplåsningsgjenkjennelse</string>
<string name="advanced_unlock_prompt_extract_credential_message">Pakk ut database-identitetsdetalj med avansert opplåsningsdata</string>
<string name="advanced_unlock_invalid_key">Kan ikke lese avansert opplåsningsnøkkel. Slett den og gjenta gjenkjenning av opplåsningsprosedyre.</string>
<string name="advanced_unlock_prompt_not_initialized">Kunne ikke igangsette avansert opplåsningsspørring.</string>
<string name="permission">Tilgang</string>
<string name="biometric_security_update_required">Biometrisk sikkerhetsoppdatering kreves.</string>
<string name="warning_exact_alarm">Du har ikke tillat programmet å bruke en eksakt alarm. Som resultat vil funksjoner som krever et tidsur ikke gjøres til eksakt tid.</string>
<string name="open_advanced_unlock_prompt_unlock_database">Åpne avansert opplåsningsspørring for å låse opp databasen</string>
<string name="open_advanced_unlock_prompt_store_credential">Åpne avansert opplåsningsspørring for å lagre identitetsdetaljer</string>
<string name="advanced_unlock_prompt_store_credential_message">Advarsel: Du vil fremdeles måtte huske hovedpassordet ditt hvis du bruker avansert opplåsningsgjenkjennelse.</string>
<string name="advanced_unlock_not_recognized">Kunne ikke gjenkjenne avansert opplåsningsskrift</string>
<string name="credential_before_click_advanced_unlock_button">Skriv inn passordet, og klikk deretter på denne knappen.</string>
<string name="device_credential">Enhets-identitetsdetalj</string>
<string name="autofill_select_entry">Velg oppføring …</string>
<string name="temp_advanced_unlock_enable_title">Midlertidig avansert opplåsing</string>
<string name="temp_advanced_unlock_timeout_title">Utløp av avansert opplåsing</string>
<string name="device_credential_unlock_enable_summary">Lar deg bruke din enhets-identitetsdetalj for å åpne databasen</string>
<string name="temp_advanced_unlock_timeout_summary">Varighet av avansert opplåsningsbruk for sletting av dens innhold</string>
<string name="advanced_unlock_explanation_summary">Bruk avansert databaseopplåsing for enklere åpning av databaser</string>
<string name="device_credential_unlock_enable_title">Opplåsning av enhetsidentitetsdetalj</string>
<string name="advanced_unlock_timeout">Tidsavbrudd for avansert opplåsing</string>
<string name="templates_group_enable_title">Mal-bruk</string>
<string name="database_data_remove_unlinked_attachments_summary">Fjerner vedlegg som er å finne i databasen, men som ikke er lenket til en oppføring</string>
<string name="keyboard_save_search_info_summary">Etter deling av en nettadresse til KeePassDX, vil oppføringer som velges bli forsøkt husket for fremtidig bruk</string>
<string name="keyboard_previous_database_credentials_title">Skjem for database-identitetsdetaljer</string>
<string name="keyboard_previous_database_credentials_summary">Bytt tilbake til forrige tastatur atuomatisk på skjermen for database-identitetsdetaljer</string>
<string name="autofill_inline_suggestions_title">Forslag på linjen</string>
<string name="autofill_inline_suggestions_summary">Prøv å vise autofullføringsforslag direkte fra et kompatibelt tastatur</string>
<string name="autofill_manual_selection_title">Manuellt utvalg</string>
<string name="autofill_manual_selection_summary">Vis alternativ for å la brukeren velge databaseoppføring</string>
<string name="keyboard_previous_fill_in_title">Automatisk tastehandling</string>
<string name="autofill_application_id_blocklist_title">Programsvarteliste</string>
<string name="autofill_application_id_blocklist_summary">Svartelisten som forhindrer automatisk innfylling i programmer</string>
<string name="autofill_web_domain_blocklist_title">Svarteliste for vev-domene</string>
<string name="autofill_web_domain_blocklist_summary">Svarteliste som forhindrer automatisk innfylling av vev-domener</string>
<string name="autofill_ask_to_save_data_summary">Spør om å lagre data når et skjema bekreftes</string>
<string name="education_add_attachment_summary">Last opp et vedlegg til din oppføring for å lagre viktig ekstern data.</string>
<string name="education_setup_OTP_summary">Sett opp engangspassordhåndtering (HOTP/TOTP) for å generere et symbol forespurt for to-faktoridentitetsbekreftelse (2FA).</string>
<string name="show_otp_token_title">Vis OTP-symbol</string>
<string name="keyboard_auto_go_action_summary">«Start»-nøkkelhandling etter at en «Felt»-nøkkel trykkes</string>
<string name="keyboard_auto_go_action_title">Automatisk nøkkelhandling</string>
<string name="keyboard_search_share_summary">Filtrer oppføringene ved bruk av nettadressedomenet ved deling av en nettadresse til KeePassDX</string>
<string name="settings_database_force_changing_master_key_next_time_summary">Krev endring av hovednøkkelen neste gang (én gang)</string>
<string name="settings_database_force_changing_master_key_next_time_title">Påtving fornyelse neste gang</string>
<string name="database_data_remove_unlinked_attachments_title">Fjen ulenket data</string>
<string name="biometric_auto_open_prompt_summary">Forespør opplasting automatisk hvis databasen er satt opp til å bruke det</string>
<string name="menu_external_icon">Eksternt ikon</string>
<string name="subdomain_search_title">Underdomenesøk</string>
<string name="error_otp_type">Eksisterende OTP-type gjenkjennes ikke av dette skjemaet. Bekreftelse av trenger ikke nødvendigvis å generere symbolet på riktig måte.</string>
<string name="error_start_database_action">En feil inntraff under utføring av en databasehandling.</string>
<string name="temp_advanced_unlock_enable_summary">Ikke lagre noe kryptert innhold for å bruke avansert opplåsing</string>
<string name="subdomain_search_summary">Søk i vev-domener med underdomene-begrensninger</string>
<string name="max_history_items_summary">Begrens antall historikkelementer per oppføring</string>
<string name="show_otp_token_summary">Viser OTP-symboler i listen over oppføringer</string>
<string name="warning_replace_file">Opplasting av denne filen erstatter den som finnes.</string>
<string name="max_history_size_summary">Begrens historikkstørrelse per oppføring</string>
<string name="import_app_properties_summary">Velg en fil for å importere programegenskaper</string>
<string name="export_app_properties_summary">Opprett en fil for å eksportere programegenskaper</string>
<string name="warning_remove_unlinked_attachment">Fjerning av ulenket data kan redusere databasestørrelsen din, men kan også slette data bruk av KeePass-programtillegg.</string>
<string name="settings_database_force_changing_master_key_summary">Krev endring av hovednøkkelen (dager)</string>
<string name="biometric_auto_open_prompt_title">Åpne spørring automatisk</string>
<string name="settings_database_recommend_changing_master_key_summary">Anbefal endring av hovednøkkel (dager)</string>
<string name="keystore_not_accessible">Nøkkellageret er ikke igangsatt på riktig vis.</string>
<string name="keyboard_previous_fill_in_summary">Bytt tilbake til forrige tastatur automatisk etter kjøring av «Automatisk tastehandling»</string>
</resources>

View File

@@ -603,4 +603,5 @@
<string name="number">Nummer</string>
<string name="debit_credit_card">Betaalkaart</string>
<string name="template_group_name">Sjablonen</string>
<string name="hint_icon_name">Naam van het pictogram</string>
</resources>

View File

@@ -162,8 +162,8 @@
<string name="kdf_explanation">Aby wygenerować klucz dla algorytmu szyfrowania, klucz główny jest transformowany przy użyciu losowo solonej funkcji wyprowadzania klucza.</string>
<string name="memory_usage">Użycie pamięci</string>
<string name="memory_usage_explanation">Ilość pamięci do użycia przez funkcję wyprowadzania klucza.</string>
<string name="parallelism">Równoległy</string>
<string name="parallelism_explanation">Stopień równoległości (tj. Liczba wątków) wykorzystywany przez funkcję wyprowadzania klucza.</string>
<string name="parallelism">Równoległość</string>
<string name="parallelism_explanation">Stopień równoległości (tj. liczba wątków) wykorzystywany przez funkcję wyprowadzania klucza.</string>
<string name="sort_menu">Sortuj</string>
<string name="sort_ascending">Od najniższej ↓</string>
<string name="sort_username">Nazwa Użytkownika</string>
@@ -602,4 +602,7 @@
<string name="autofill_manual_selection_summary">Wyświetl opcję pozwalającą użytkownikowi wybrać wpis bazy danych</string>
<string name="autofill_manual_selection_title">Wybór ręczny</string>
<string name="autofill_select_entry">Wybierz wpis…</string>
<string name="hint_icon_name">Nazwa ikony</string>
<string name="warning_exact_alarm">Nie zezwolono aplikacji na użycie dokładnego alarmu. W rezultacie funkcje wymagające czasomierza nie będą wykonywane z dokładnym czasem.</string>
<string name="permission">Uprawnienie</string>
</resources>

View File

@@ -23,9 +23,9 @@
<string name="accept">Aceitar</string>
<string name="add_entry">Adicionar entrada</string>
<string name="add_group">Adicionar grupo</string>
<string name="encryption_algorithm">Algoritmo de cifragem</string>
<string name="encryption_algorithm">Algoritmo de criptografia</string>
<string name="app_timeout">Tempo de espera</string>
<string name="app_timeout_summary">Inatividade até que o aplicativo seja bloqueado</string>
<string name="app_timeout_summary">Inatividade até que o banco de dados seja bloqueado</string>
<string name="application">Aplicativo</string>
<string name="menu_app_settings">Configurações do aplicativo</string>
<string name="brackets">Parênteses</string>
@@ -77,7 +77,7 @@
<string name="password">Senha</string>
<string name="hint_pass">Senha</string>
<string name="invalid_credentials">Não foi possível ler credenciais.</string>
<string name="invalid_db_sig">Não pôde reconhecer formato da base de dados.</string>
<string name="invalid_db_sig">Não foi possível reconhecer o formato do banco de dados.</string>
<string name="length">Comprimento</string>
<string name="list_size_title">Tamanho da lista de grupos</string>
<string name="list_size_summary">Tamanho do texto na lista de grupos</string>
@@ -86,14 +86,14 @@
<string name="hide_password_title">Esconder senhas</string>
<string name="hide_password_summary">Mascarar senhas (***) por padrão</string>
<string name="about">Sobre</string>
<string name="menu_change_key_settings">Modificar a chave mestra</string>
<string name="menu_change_key_settings">Modificar a chave-mestra</string>
<string name="settings">Configurações</string>
<string name="menu_database_settings">Configurações do banco de dados</string>
<string name="menu_delete">Deletar</string>
<string name="menu_donate">Doar</string>
<string name="menu_edit">Editar</string>
<string name="menu_hide_password">Esconder senha</string>
<string name="menu_lock">Bloquear base de dados</string>
<string name="menu_lock">Bloquear banco de dados</string>
<string name="menu_open">Abrir</string>
<string name="menu_search">Buscar</string>
<string name="menu_showpass">Mostrar senha</string>
@@ -109,7 +109,7 @@
<string name="content_description_remove_from_list">Remover</string>
<string name="root">Raiz</string>
<string name="rounds">Rodadas de transformação</string>
<string name="rounds_explanation">Rodadas adicionais de cifragem adicionam mais proteção contra ataques de força bruta, mas podem tornar o processo de carregar e salvar mais lentos.</string>
<string name="rounds_explanation">Rodadas adicionais de criptografia adicionam mais proteção contra ataques de força bruta, mas podem tornar o processo de carregar e salvar mais lentos.</string>
<string name="saving_database">Salvando banco de dados…</string>
<string name="space">Espaço</string>
<string name="search_label">Busca</string>
@@ -122,13 +122,13 @@
<string name="version_label">Versão %1$s</string>
<string name="education_unlock_summary">Entre com a senha e/ou com o caminho para o arquivo-chave do banco de dados.
\n
\nGuarde uma cópia do seu arquivo do banco em um lugar mais seguro depois de cada alteração.</string>
\nFaça um backup do banco em um lugar mais seguro depois de cada alteração.</string>
<string-array name="list_size_options">
<item>Pequeno</item>
<item>Médio</item>
<item>Grande</item>
</string-array>
<string name="encryption">Cifragem</string>
<string name="encryption">Criptografia</string>
<string name="key_derivation_function">Função de derivação de chave</string>
<string name="extended_ASCII">ASCII estendido</string>
<string name="allow">Permitir</string>
@@ -137,10 +137,10 @@
<string name="clipboard_error_clear">Não foi possível limpar a área de transferência</string>
<string name="entry_not_found">Não pôde encontrar dados de entrada.</string>
<string name="error_string_key">Um nome do campo é necessário para cada string.</string>
<string name="error_autofill_enable_service">Não pôde ser habilitado o serviço de preenchimento automático.</string>
<string name="error_autofill_enable_service">Não foi possível habilitar o serviço de preenchimento automático.</string>
<string name="field_name">Nome do campo</string>
<string name="field_value">Valor do campo</string>
<string name="file_not_found_content">Não pôde encontrar o arquivo. Tente reabri-lo de seu explorador de arquivos.</string>
<string name="file_not_found_content">Não foi possível encontrar o arquivo. Tente reabri-lo de seu explorador de arquivos.</string>
<string name="invalid_algorithm">Algoritmo errado.</string>
<string name="keyfile_is_empty">O arquivo-chave está vazio.</string>
<string name="copy_field">Cópia de %1$s</string>
@@ -156,8 +156,8 @@
<string name="read_only_warning">KeePassDX precisa de permissões de escrita para poder mudar qualquer coisa no seu banco.</string>
<string name="show_recent_files_title">Exibir arquivos recentes</string>
<string name="show_recent_files_summary">Exibir locais de bancos de dados recentes</string>
<string name="encryption_explanation">Algoritmo de cifragem usado para todos os dados.</string>
<string name="kdf_explanation">Para gerar uma chave para o algoritmo de cifragem, a chave mestra é transformada usando uma função de derivação de chave salgada aleatoriamente.</string>
<string name="encryption_explanation">Algoritmo de criptografia usado para todos os dados.</string>
<string name="kdf_explanation">Para gerar uma chave para o algoritmo de criptografia, a chave-mestra é transformada usando uma função de derivação de chave salgada aleatoriamente.</string>
<string name="memory_usage">Uso de memória</string>
<string name="memory_usage_explanation">Quantidade de memória a ser usada pela função de derivação de chave.</string>
<string name="parallelism">Paralelismo</string>
@@ -174,8 +174,8 @@
<string name="search_results">Resultados da busca</string>
<string name="warning">Aviso</string>
<string name="warning_password_encoding">Evite caracteres fora do formato de codificação do arquivo do banco (todos os caracteres não reconhecidos são convertidos para a mesma letra).</string>
<string name="warning_empty_password">Continuar sem proteção de desbloqueio de senha\?</string>
<string name="warning_no_encryption_key">Continuar sem chave de cifragem\?</string>
<string name="warning_empty_password">Continuar sem proteção de desbloqueio por senha\?</string>
<string name="warning_no_encryption_key">Continuar sem chave de criptografia\?</string>
<string name="encrypted_value_stored">Senha cifrada armazenada</string>
<string name="no_credentials_stored">Ainda não há nenhuma senha armazenada nesse banco de dados.</string>
<string name="database_history">Histórico</string>
@@ -199,14 +199,14 @@
<string name="advanced_unlock">Desbloqueio avançado</string>
<string name="biometric_unlock_enable_title">Desbloqueio biométrico</string>
<string name="biometric_unlock_enable_summary">Permite que você escaneie sua biometria para a abertura do banco de dados</string>
<string name="biometric_delete_all_key_title">Apague chaves de cifragem</string>
<string name="biometric_delete_all_key_summary">Apagar todas as chaves de cifragem relacionadas ao reconhecimento de desbloqueio avançado</string>
<string name="biometric_delete_all_key_title">Apague chaves de criptografia</string>
<string name="biometric_delete_all_key_summary">Apagar todas as chaves de criptografia relacionadas ao reconhecimento de desbloqueio avançado</string>
<string name="unavailable_feature_text">Não foi possível iniciar esse recurso.</string>
<string name="unavailable_feature_version">O dispositivo está utilizando Android %1$s, mas precisa %2$s ou posterior.</string>
<string name="unavailable_feature_hardware">Não foi possível encontrar o hardware correspondente.</string>
<string name="file_name">Nome do arquivo</string>
<string name="path">Caminho</string>
<string name="assign_master_key">Defina uma chave mestra</string>
<string name="assign_master_key">Defina uma chave-mestra</string>
<string name="create_keepass_file">Criar novo banco</string>
<string name="recycle_bin_title">Usar lixeira</string>
<string name="recycle_bin_summary">Mover grupos e entradas para o grupo \"Lixeira\" antes de apagar</string>
@@ -224,7 +224,7 @@
<string name="keyboard">Teclado</string>
<string name="magic_keyboard_title">Magikeyboard</string>
<string name="magic_keyboard_explanation_summary">Ative um teclado customizado, populando suas senhas e todos os campos de identidade</string>
<string name="allow_no_password_title">Não permitir chave mestra</string>
<string name="allow_no_password_title">Não permitir chave-mestra</string>
<string name="allow_no_password_summary">Permitir tocar no botão \"Abrir\" se nenhuma credencial for selecionada</string>
<string name="enable_read_only_title">Somente leitura</string>
<string name="enable_read_only_summary">Por padrão, abrir seu banco de dados no modo somente leitura</string>
@@ -238,9 +238,9 @@
<string name="education_select_database_title">Abra um banco de dados existente</string>
<string name="education_select_database_summary">Abra seu arquivo de banco de dados existente a partir do navegador de arquivos.</string>
<string name="education_new_node_title">Adicione itens ao seu banco</string>
<string name="education_new_node_summary">Entradas ajudam a gerenciar suas identidades digitais.
\n
\nGrupos (~pastas) organizam suas entradas e sua base de dados.</string>
<string name="education_new_node_summary">Entradas ajudam a gerenciar suas identidades digitais.
\n
\nGrupos (~pastas) organizam suas entradas e seu banco de dados.</string>
<string name="education_search_title">Pesquise suas entradas</string>
<string name="education_search_summary">Entre com título, nome de usuário ou outros campos para recuperar facilmente suas senhas.</string>
<string name="education_entry_edit_title">Modifique a entrada</string>
@@ -248,7 +248,7 @@
<string name="education_generate_password_title">Criar uma senha forte</string>
<string name="education_generate_password_summary">Gere uma senha forte para associar a sua entrada, defina-a facilmente de acordo com os critérios do formulário e lembre-se de torná-la segura.</string>
<string name="education_entry_new_field_title">Adicione campos customizados</string>
<string name="education_entry_new_field_summary">Registrar um campo adicional, adicionar um valor e, opcionalmente, protegê-lo.</string>
<string name="education_entry_new_field_summary">Registre um campo adicional, adicione um valor e, opcionalmente, proteja-o.</string>
<string name="education_unlock_title">Desbloqueie seu banco de dados</string>
<string name="education_read_only_title">Proteja seu banco de modificações</string>
<string name="education_read_only_summary">Altere o modo de abertura para a sessão.
@@ -301,7 +301,7 @@
<string name="keyboard_notification_entry_content_title">%1$s disponível no Magikeyboard</string>
<string name="keyboard_notification_entry_content_text">%1$s</string>
<string name="keyboard_notification_entry_clear_close_title">Limpar ao fechar</string>
<string name="keyboard_notification_entry_clear_close_summary">Fecha a base de dados ao dispensar a notificação</string>
<string name="keyboard_notification_entry_clear_close_summary">Fechar o banco de dados ao dispensar a notificação</string>
<string name="keyboard_appearance_category">Aparência</string>
<string name="keyboard_theme_title">Tema do teclado</string>
<string name="keyboard_keys_category">Teclas</string>
@@ -347,7 +347,7 @@
<string name="biometric_auto_open_prompt_summary">Solicitar desbloqueio avançado automaticamente se o banco de dados estiver configurado para usá-lo</string>
<string name="enable">Habilitado</string>
<string name="disable">Desabilitado</string>
<string name="master_key">Chave mestra</string>
<string name="master_key">Chave-mestra</string>
<string name="security">Segurança</string>
<string name="entry_history">Histórico</string>
<string name="entry_setup_otp">Configure uma senha de uso único</string>
@@ -368,7 +368,7 @@
<string name="invalid_db_same_uuid">%1$s com o mesmo UUID %2$s já existe.</string>
<string name="creating_database">Criando banco de dados…</string>
<string name="menu_security_settings">Configurações de segurança</string>
<string name="menu_master_key_settings">Configurações da chave mestra</string>
<string name="menu_master_key_settings">Configurações da chave-mestra</string>
<string name="contains_duplicate_uuid">O banco de dados contém UUIDs duplicados.</string>
<string name="contains_duplicate_uuid_procedure">Consertar o problema gerando nova UUIDs para duplicatas para continuar\?</string>
<string name="database_opened">Banco de dados aberto</string>
@@ -381,11 +381,11 @@
<string name="max_history_size_title">Tamanho máximo</string>
<string name="max_history_size_summary">Limitar o tamanho do histórico por entrada</string>
<string name="settings_database_recommend_changing_master_key_title">Renovação recomendada</string>
<string name="settings_database_recommend_changing_master_key_summary">Recomende a mudança da chave mestra (em dias)</string>
<string name="settings_database_recommend_changing_master_key_summary">Recomende a mudança da chave-mestra (em dias)</string>
<string name="settings_database_force_changing_master_key_title">Forçar renovação</string>
<string name="settings_database_force_changing_master_key_summary">Forçar mudança da chave mestra (em dias)</string>
<string name="settings_database_force_changing_master_key_summary">Forçar mudança da chave-mestra (em dias)</string>
<string name="settings_database_force_changing_master_key_next_time_title">Forçar renovação na próxima vez</string>
<string name="settings_database_force_changing_master_key_next_time_summary">Forçar mudança da chave mestra na próxima vez (uma vez)</string>
<string name="settings_database_force_changing_master_key_next_time_summary">Forçar mudança da chave-mestra na próxima vez (uma vez)</string>
<string name="database_default_username_title">Usuário padrão</string>
<string name="database_custom_color_title">Cor personalizada do banco de dados</string>
<string name="compression">Compressão</string>
@@ -399,8 +399,8 @@
<string name="warning_permanently_delete_nodes">Apagar permanentemente os nós selecionados\?</string>
<string name="keystore_not_accessible">O armazenamento chaves não foi propriamente inicializado.</string>
<string name="recycle_bin_group_title">Grupo de lixeira</string>
<string name="enable_auto_save_database_title">Salvar automaticamente a base de dados</string>
<string name="enable_auto_save_database_summary">Salvar automaticamente a base de dados depois de uma ação importante (somente no modo \"Modificável\")</string>
<string name="enable_auto_save_database_title">Salvar automaticamente o banco de dados</string>
<string name="enable_auto_save_database_summary">Salvar automaticamente o banco de dados depois de uma ação importante (somente no modo \"Modificável\")</string>
<string name="discard">Descartar</string>
<string name="contact">Contato</string>
<string name="auto_focus_search_title">Busca rápida</string>
@@ -423,10 +423,10 @@
<string name="lock_database_show_button_title">Mostrar botão de bloqueio</string>
<string name="autofill_preference_title">Configurações de preenchimento automático</string>
<string name="warning_database_link_revoked">Acesso ao arquivo revogado pelo gerenciador de arquivos</string>
<string name="warning_database_read_only">Conceder acesso de gravação de arquivo para salvar alterações na base de dados</string>
<string name="hide_broken_locations_summary">Esconder links quebrados na lista de bases de dados recentes</string>
<string name="warning_database_read_only">Conceder acesso de gravação de arquivo para salvar alterações no banco de dados</string>
<string name="hide_broken_locations_summary">Esconder links quebrados na lista de bancos de dados recentes</string>
<string name="hide_broken_locations_title">Ocultar links quebrados do banco de dados</string>
<string name="remember_keyfile_locations_summary">Manter o local onde os arquivos-chave são armazenados</string>
<string name="remember_keyfile_locations_summary">Lembrar o local onde os arquivos-chave são guardados</string>
<string name="remember_keyfile_locations_title">Lembrar a localização dos arquivos-chave</string>
<string name="remember_database_locations_summary">Lembra o local onde os bancos de dados são guardados</string>
<string name="remember_database_locations_title">Lembrar a localização dos bancos de dados</string>
@@ -589,7 +589,7 @@
<string name="templates_group_uuid_title">Grupo de modelos</string>
<string name="templates_group_enable_summary">Use modelos dinâmicos para preencher os campos de uma entrada</string>
<string name="templates_group_enable_title">Uso de modelos</string>
<string name="advanced_unlock_delete_all_key_warning">Apagar todas as chaves de cifragem relacionadas ao reconhecimento de desbloqueio avançado\?</string>
<string name="advanced_unlock_delete_all_key_warning">Apagar todas as chaves de criptografia relacionadas ao reconhecimento de desbloqueio avançado\?</string>
<string name="advanced_unlock_timeout">Tempo limite de desbloqueio avançado</string>
<string name="temp_advanced_unlock_timeout_summary">Duração do uso de desbloqueio avançado antes de excluir seu conteúdo</string>
<string name="temp_advanced_unlock_timeout_title">Expiração de desbloqueio avançado</string>
@@ -600,4 +600,7 @@
<string name="advanced_unlock_tap_delete">Toque para excluir as chaves de desbloqueio avançado</string>
<string name="content">Conteúdo</string>
<string name="autofill_select_entry">Selecione a entrada…</string>
<string name="hint_icon_name">Nome do ícone</string>
<string name="permission">Permissão</string>
<string name="warning_exact_alarm">Você não permitiu que o aplicativo usasse um alarme exato. Como resultado, os recursos que requerem um cronômetro não serão executados com um tempo exato.</string>
</resources>

View File

@@ -578,4 +578,7 @@
<string name="debit_credit_card">Cartão de crédito / débito</string>
<string name="template_group_name">Modelos</string>
<string name="content_description_otp_information">Informação de palavra-passe de uso único</string>
<string name="hint_icon_name">Nome do ícone</string>
<string name="permission">Permissão</string>
<string name="warning_exact_alarm">Não permitiu que a app usasse um alarme exato. Como resultado, as funcionalidades que requerem um temporizador não serão feitas com um tempo exato.</string>
</resources>

View File

@@ -494,7 +494,7 @@
<string name="save_mode">Режим записи</string>
<string name="search_mode">Режим поиска</string>
<string name="error_registration_read_only">Сохранение новых записей невозможно, т.к. база открыта только для чтения</string>
<string name="error_field_name_already_exists">Поле с таким именем уже существует.</string>
<string name="error_field_name_already_exists">Поле с таким названием уже существует.</string>
<string name="device_credential_unlock_enable_summary">Позволяет использовать учётные данные вашего устройства для открытия базы</string>
<string name="device_credential_unlock_enable_title">Разблокировка учётными данными устройства</string>
<string name="device_credential">Учётные данные устройства</string>
@@ -602,4 +602,7 @@
<string name="autofill_select_entry">Выбор записи…</string>
<string name="autofill_manual_selection_summary">Показывать функцию, позволяющую пользователю вручную выбирать запить из базы</string>
<string name="autofill_manual_selection_title">Ручной выбор</string>
<string name="hint_icon_name">Название значка</string>
<string name="permission">Разрешение</string>
<string name="warning_exact_alarm">Вы не разрешили приложению использовать точный сигнал будильника. В результате требующие этого функции не будут выполняться с точным временем.</string>
</resources>

View File

@@ -300,7 +300,7 @@
<string name="icon_pack_choose_title">Simge paketi</string>
<string name="icon_pack_choose_summary">Uygulamada kullanılan simge paketi</string>
<string name="selection_mode">Seçim modu</string>
<string name="do_not_kill_app">Uygulamayı öldürmeyin…</string>
<string name="do_not_kill_app">Uygulamayı sonlandırmayın…</string>
<string name="lock_database_back_root_summary">Kullanıcı kök ekranda geri düğmesine tıkladığında veri tabanını kilitle</string>
<string name="clear_clipboard_notification_title">Kapanışta temizle</string>
<string name="clear_clipboard_notification_summary">Panonun süresi dolduğunda veya kullanmaya başladıktan sonra bildirim kapatıldığında veri tabanını kilitle</string>
@@ -597,4 +597,7 @@
<string name="autofill_manual_selection_summary">Kullanıcının veri tabanı girdisini seçmesine izin veren seçeneği göster</string>
<string name="autofill_manual_selection_title">Elle seçim</string>
<string name="autofill_select_entry">Girdi seç…</string>
<string name="hint_icon_name">Simge adı</string>
<string name="warning_exact_alarm">Uygulamanın tam bir alarm kullanmasına izin vermediniz. Sonuç olarak zamanlayıcı gerektiren özellikler kesin bir süre ile yapılmayacaktır.</string>
<string name="permission">İzinler</string>
</resources>

View File

@@ -134,7 +134,7 @@
<string name="content_description_open_file">Відкрити файл</string>
<string name="extended_ASCII">Розширений ASCII</string>
<string name="edit_entry">Змінити запис</string>
<string name="clear_clipboard_notification_summary">Блокувати базу даних після закінчення терміну дії буфера обміну, або закрито сповіщення після початку користування ним</string>
<string name="clear_clipboard_notification_summary">Блокувати базу даних після закриття сповіщення після використання або якщо вичерпано час очікування</string>
<string name="content_description_add_group">Додати групу</string>
<string name="content_description_update_from_list">Оновити</string>
<string name="keyboard">Клавіатура</string>
@@ -159,7 +159,7 @@
<string name="clipboard_error">Деякі пристрої не дозволяють застосункам користуватися буфером обміну.</string>
<string name="clipboard_error_title">Помилка буфера обміну</string>
<string name="allow">Дозволити</string>
<string name="key_derivation_function">Функція створення ключа</string>
<string name="key_derivation_function">Функція генерування ключа</string>
<string name="encryption">Шифрування</string>
<string name="security">Безпека</string>
<string name="master_key">Головний ключ</string>
@@ -375,7 +375,7 @@
<string name="file_name">Назва файлу</string>
<string name="unavailable_feature_version">Пристрій працює під керуванням Android %1$s, але необхідний %2$s чи пізніші.</string>
<string name="unavailable_feature_text">Не вдалось запустити цю функцію.</string>
<string name="biometric_delete_all_key_summary">Видалити всі ключі шифрування, пов\'язані з розпізнаванням розширеного розблокування</string>
<string name="biometric_delete_all_key_summary">Видалити всі ключі шифрування, повязані з розпізнаванням розширеного розблокування</string>
<string name="biometric_delete_all_key_title">Видалити ключі шифрування</string>
<string name="biometric_auto_open_prompt_summary">Автоматично запитувати розширене розблокування, якщо базу даних налаштовано для роботи з ним</string>
<string name="biometric_auto_open_prompt_title">Автозапит ключа</string>
@@ -471,8 +471,8 @@
<string name="warning_empty_keyfile">Не рекомендовано додавати порожній файл ключа.</string>
<string name="warning_empty_keyfile_explanation">Вміст файлу ключів ніколи не слід змінювати, а в кращому випадку повинен містити випадково згенеровані дані.</string>
<string name="data">Дані</string>
<string name="database_data_remove_unlinked_attachments_title">Вилучити незв\'язані дані</string>
<string name="database_data_remove_unlinked_attachments_summary">Вилучає вкладення, що містяться в базі даних, але не пов’язані із записом</string>
<string name="database_data_remove_unlinked_attachments_title">Вилучити непов’язані дані</string>
<string name="database_data_remove_unlinked_attachments_summary">Вилучає вкладення, що містяться в базі даних, але не пов’язані з записом</string>
<string name="show_uuid_summary">Показ пов\'язаного з записом чи групою UUID</string>
<string name="show_uuid_title">Показувати UUID</string>
<string name="autofill_read_only_save">Збереження даних заборонено для бази даних, відкритої лише для читання.</string>
@@ -495,7 +495,7 @@
<string name="search_mode">Режим пошуку</string>
<string name="error_registration_read_only">Збереження нового елемента заборонено в базі даних лише для читання</string>
<string name="error_field_name_already_exists">Назва поля вже існує.</string>
<string name="advanced_unlock_delete_all_key_warning">Видалити всі ключі шифрування, пов\'язані з розширеним розпізнаванням розблокування\?</string>
<string name="advanced_unlock_delete_all_key_warning">Видалити всі ключі шифрування, повязані з розширеним розпізнаванням розблокування\?</string>
<string name="device_credential_unlock_enable_summary">Дає змогу використовувати облікові дані пристрою для відкриття бази даних</string>
<string name="device_credential_unlock_enable_title">Розблокування облікових даних пристрою</string>
<string name="device_credential">Облікові дані пристрою</string>
@@ -521,7 +521,7 @@
<string name="education_advanced_unlock_summary">Пов’яжіть свій пароль зі сканованими біометричними даними або даними пристрою, щоб швидко розблокувати базу даних.</string>
<string name="education_advanced_unlock_title">Розширене розблокування бази даних</string>
<string name="temp_advanced_unlock_timeout_summary">Тривалість використання розширеного розблокування перед видаленням його вмісту</string>
<string name="temp_advanced_unlock_enable_summary">Не зберігайте зашифрований вміст для використання розширеного розблокування</string>
<string name="temp_advanced_unlock_enable_summary">Не зберігати зашифрований вміст для використання розширеного розблокування</string>
<string name="temp_advanced_unlock_enable_title">Тимчасове розширене розблокування</string>
<string name="advanced_unlock_tap_delete">Торкнутися, щоб видалити клавіші розширеного розблокування</string>
<string name="content">Вміст</string>
@@ -542,7 +542,7 @@
<string name="download_canceled">Скасовано!</string>
<string name="icon_section_custom">Власні</string>
<string name="icon_section_standard">Стандартні</string>
<string name="style_brightness_summary">Вибрати світлі або темні теми</string>
<string name="style_brightness_summary">Вибрати світлу або темну теми</string>
<string name="style_brightness_title">Яскравість теми</string>
<string name="error_upload_file">Під час передавання даних файлу сталася помилка.</string>
<string name="error_file_to_big">Файл, який ви намагаєтеся передати, завеликий.</string>
@@ -602,4 +602,7 @@
<string name="autofill_manual_selection_summary">Параметр показу, який дає користувачеві змогу вибрати запис бази даних</string>
<string name="autofill_manual_selection_title">Вибір вручну</string>
<string name="autofill_select_entry">Вибрати запис…</string>
<string name="hint_icon_name">Назва піктограми</string>
<string name="permission">Дозвіл</string>
<string name="warning_exact_alarm">Ви не дозволили застосунку використовувати точний час. У результаті функції, для яких потрібен таймер, не виконуватимуться точно за часом.</string>
</resources>

Some files were not shown because too many files have changed in this diff Show More