mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Compare commits
32 Commits
3.0.0_beta
...
3.0.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7750843b04 | ||
|
|
d7da1ce333 | ||
|
|
dd9ee8c3f8 | ||
|
|
c0ac01a34a | ||
|
|
1b88f2ddf0 | ||
|
|
b4f2a1eb89 | ||
|
|
e9fc9cbc2a | ||
|
|
b809180a1b | ||
|
|
ecc75df3a1 | ||
|
|
d1b6863143 | ||
|
|
faf27143aa | ||
|
|
aee58a4475 | ||
|
|
c5e07f643f | ||
|
|
818f5820d5 | ||
|
|
77c6e28876 | ||
|
|
fb7f66012d | ||
|
|
0f7f7bbe6c | ||
|
|
8dedb8deb4 | ||
|
|
d284db4d3c | ||
|
|
82450c0ae8 | ||
|
|
8dd6c33901 | ||
|
|
f920d40db5 | ||
|
|
19be6c1acc | ||
|
|
7d9d8ad0e4 | ||
|
|
85f8237d5f | ||
|
|
c542894734 | ||
|
|
d348987077 | ||
|
|
3718610595 | ||
|
|
9c36ec0623 | ||
|
|
c6917b5d74 | ||
|
|
4eaa179789 | ||
|
|
9008cd4549 |
14
CHANGELOG
14
CHANGELOG
@@ -1,3 +1,15 @@
|
||||
KeePassDX(3.0.2)
|
||||
* Samsung DeX mode #1114 #245 (Thx @chenxiaolong)
|
||||
|
||||
KeePassDX(3.0.1)
|
||||
* Fix text size and smallest margin #1085
|
||||
* Fix number of lines during an edition #1073
|
||||
* Fix Magikeyboard URL auto action #1100
|
||||
* Fix exception after group name change and save #1112
|
||||
* Fix timeout reset #1107
|
||||
* Fix search actions #1091 #1092
|
||||
* Small changes #1106 #1085
|
||||
|
||||
KeePassDX(3.0.0)
|
||||
* Add / Manage dynamic templates #191
|
||||
* Manually select RecycleBin group and Templates group #191
|
||||
@@ -5,7 +17,7 @@ KeePassDX(3.0.0)
|
||||
* Fix timeout in dialogs #716
|
||||
* Check URI permissions #626
|
||||
* Better autofill implementation #943 #946 #984 #1070 (Thx @uduerholz)
|
||||
* Improvements #680 #1035 #1043 #942 #1021 #1027
|
||||
* Improvements #680 #1035 #1043 #942 #1021 #1027 #1046 #1082 #1083 (Thx @chenxiaolong)
|
||||
|
||||
KeePassDX(2.10.5)
|
||||
* Increase the saving speed of database #1028
|
||||
|
||||
@@ -11,8 +11,8 @@ android {
|
||||
applicationId "com.kunzisoft.keepass"
|
||||
minSdkVersion 15
|
||||
targetSdkVersion 30
|
||||
versionCode = 86
|
||||
versionName = "3.0.0_beta03"
|
||||
versionCode = 89
|
||||
versionName = "3.0.2"
|
||||
multiDexEnabled true
|
||||
|
||||
testApplicationId = "com.kunzisoft.keepass.tests"
|
||||
|
||||
@@ -221,6 +221,14 @@
|
||||
android:name="com.kunzisoft.keepass.services.KeyboardEntryNotificationService"
|
||||
android:enabled="true"
|
||||
android:exported="false" />
|
||||
<receiver
|
||||
android:name="com.kunzisoft.keepass.receivers.DexModeReceiver"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.app.action.ENTER_KNOX_DESKTOP_MODE" />
|
||||
<action android:name="android.app.action.EXIT_KNOX_DESKTOP_MODE" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<meta-data android:name="com.sec.android.support.multiwindow" android:value="true" />
|
||||
</application>
|
||||
|
||||
@@ -88,6 +88,12 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(),
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
// Enabling/disabling MagikeyboardService is normally done by DexModeReceiver, but this
|
||||
// additional check will allow the keyboard to be reenabled more easily if the app crashes
|
||||
// or is force quit within DeX mode and then the user leaves DeX mode. Without this, the
|
||||
// user would need to enter and exit DeX mode once to reenable the service.
|
||||
MagikeyboardUtil.setEnabled(this, !DexUtil.isDexMode(resources.configuration))
|
||||
|
||||
mFileDatabaseHistoryAction = FileDatabaseHistoryAction.getInstance(applicationContext)
|
||||
|
||||
setContentView(R.layout.activity_file_selection)
|
||||
|
||||
@@ -653,6 +653,8 @@ class GroupActivity : DatabaseLockActivity(),
|
||||
Log.e(TAG, "Node can't be cast in Entry")
|
||||
}
|
||||
}
|
||||
|
||||
reloadGroupIfSearch()
|
||||
}
|
||||
|
||||
private fun entrySelectedForSave(database: Database, entry: Entry, searchInfo: SearchInfo) {
|
||||
@@ -738,6 +740,12 @@ class GroupActivity : DatabaseLockActivity(),
|
||||
actionNodeMode?.finish()
|
||||
}
|
||||
|
||||
private fun reloadGroupIfSearch() {
|
||||
if (Intent.ACTION_SEARCH == intent.action) {
|
||||
reloadCurrentGroup()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNodeSelected(
|
||||
database: Database,
|
||||
nodes: List<Node>
|
||||
@@ -793,6 +801,7 @@ class GroupActivity : DatabaseLockActivity(),
|
||||
(node as Entry).nodeId
|
||||
)
|
||||
}
|
||||
reloadGroupIfSearch()
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -847,6 +856,7 @@ class GroupActivity : DatabaseLockActivity(),
|
||||
): Boolean {
|
||||
deleteNodes(nodes)
|
||||
finishNodeAction()
|
||||
reloadGroupIfSearch()
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -934,9 +944,7 @@ class GroupActivity : DatabaseLockActivity(),
|
||||
) {
|
||||
|
||||
// If no node, show education to add new one
|
||||
val addNodeButtonEducationPerformed = mGroupFragment != null
|
||||
&& mGroupFragment!!.isEmpty
|
||||
&& actionNodeMode == null
|
||||
val addNodeButtonEducationPerformed = actionNodeMode == null
|
||||
&& addNodeButtonView?.addButtonView != null
|
||||
&& addNodeButtonView!!.isEnable
|
||||
&& groupActivityEducation.checkAndPerformedAddNodeButtonEducation(
|
||||
@@ -1095,7 +1103,7 @@ class GroupActivity : DatabaseLockActivity(),
|
||||
try {
|
||||
mGroupViewModel.loadGroup(mDatabase, mCurrentGroupState)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to rebuild the list after deletion", e)
|
||||
Log.e(TAG, "Unable to rebuild the group", e)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -74,9 +74,6 @@ class GroupFragment : DatabaseFragment(), SortDialogFragment.SortSelectionListen
|
||||
private var mRecycleBinEnable: Boolean = false
|
||||
private var mRecycleBin: Group? = null
|
||||
|
||||
val isEmpty: Boolean
|
||||
get() = mAdapter == null || mAdapter?.itemCount?:0 <= 0
|
||||
|
||||
private var mRecycleViewScrollListener = object : RecyclerView.OnScrollListener() {
|
||||
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
|
||||
super.onScrollStateChanged(recyclerView, newState)
|
||||
|
||||
@@ -28,6 +28,7 @@ import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.ProgressBar
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
@@ -41,9 +42,11 @@ import com.kunzisoft.keepass.database.element.SortNodeEnum
|
||||
import com.kunzisoft.keepass.database.element.node.Node
|
||||
import com.kunzisoft.keepass.database.element.node.NodeVersionedInterface
|
||||
import com.kunzisoft.keepass.database.element.node.Type
|
||||
import com.kunzisoft.keepass.database.element.template.TemplateField
|
||||
import com.kunzisoft.keepass.otp.OtpElement
|
||||
import com.kunzisoft.keepass.otp.OtpType
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.timeout.ClipboardHelper
|
||||
import com.kunzisoft.keepass.view.setTextSize
|
||||
import com.kunzisoft.keepass.view.strikeOut
|
||||
import java.util.*
|
||||
@@ -64,8 +67,10 @@ class NodeAdapter (private val context: Context,
|
||||
private var mCalculateViewTypeTextSize = Array(2) { true } // number of view type
|
||||
private var mTextSizeUnit: Int = TypedValue.COMPLEX_UNIT_PX
|
||||
private var mPrefSizeMultiplier: Float = 0F
|
||||
private var mSubtextDefaultDimension: Float = 0F
|
||||
private var mInfoTextDefaultDimension: Float = 0F
|
||||
private var mTextDefaultDimension: Float = 0F
|
||||
private var mSubTextDefaultDimension: Float = 0F
|
||||
private var mMetaTextDefaultDimension: Float = 0F
|
||||
private var mOtpTokenTextDefaultDimension: Float = 0F
|
||||
private var mNumberChildrenTextDefaultDimension: Float = 0F
|
||||
private var mIconDefaultDimension: Float = 0F
|
||||
|
||||
@@ -77,6 +82,7 @@ class NodeAdapter (private val context: Context,
|
||||
|
||||
private var mActionNodesList = LinkedList<Node>()
|
||||
private var mNodeClickCallback: NodeClickCallback? = null
|
||||
private var mClipboardHelper = ClipboardHelper(context)
|
||||
|
||||
@ColorInt
|
||||
private val mContentSelectionColor: Int
|
||||
@@ -299,8 +305,10 @@ class NodeAdapter (private val context: Context,
|
||||
mInflater.inflate(R.layout.item_list_nodes_entry, parent, false)
|
||||
}
|
||||
val nodeViewHolder = NodeViewHolder(view)
|
||||
mInfoTextDefaultDimension = nodeViewHolder.text.textSize
|
||||
mSubtextDefaultDimension = nodeViewHolder.subText.textSize
|
||||
mTextDefaultDimension = nodeViewHolder.text.textSize
|
||||
mSubTextDefaultDimension = nodeViewHolder.subText?.textSize ?: mSubTextDefaultDimension
|
||||
mMetaTextDefaultDimension = nodeViewHolder.meta.textSize
|
||||
mOtpTokenTextDefaultDimension = nodeViewHolder.otpToken?.textSize ?: mOtpTokenTextDefaultDimension
|
||||
nodeViewHolder.numberChildren?.let {
|
||||
mNumberChildrenTextDefaultDimension = it.textSize
|
||||
}
|
||||
@@ -311,7 +319,9 @@ class NodeAdapter (private val context: Context,
|
||||
val subNode = mNodeSortedList.get(position)
|
||||
|
||||
// Node selection
|
||||
holder.container.isSelected = mActionNodesList.contains(subNode)
|
||||
holder.container.apply {
|
||||
isSelected = mActionNodesList.contains(subNode)
|
||||
}
|
||||
|
||||
// Assign image
|
||||
val iconColor = if (holder.container.isSelected)
|
||||
@@ -333,19 +343,18 @@ class NodeAdapter (private val context: Context,
|
||||
// Assign text
|
||||
holder.text.apply {
|
||||
text = subNode.title
|
||||
setTextSize(mTextSizeUnit, mInfoTextDefaultDimension, mPrefSizeMultiplier)
|
||||
setTextSize(mTextSizeUnit, mTextDefaultDimension, mPrefSizeMultiplier)
|
||||
strikeOut(subNode.isCurrentlyExpires)
|
||||
}
|
||||
// Add subText with username
|
||||
holder.subText.apply {
|
||||
text = ""
|
||||
strikeOut(subNode.isCurrentlyExpires)
|
||||
visibility = View.GONE
|
||||
}
|
||||
// Add meta text to show UUID
|
||||
holder.meta.apply {
|
||||
if (mShowUUID) {
|
||||
text = subNode.nodeId.toString()
|
||||
visibility = if (mShowUUID) View.VISIBLE else View.GONE
|
||||
setTextSize(mTextSizeUnit, mMetaTextDefaultDimension, mPrefSizeMultiplier)
|
||||
visibility = View.VISIBLE
|
||||
} else {
|
||||
visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
// Specific elements for entry
|
||||
@@ -354,12 +363,16 @@ class NodeAdapter (private val context: Context,
|
||||
database.startManageEntry(entry)
|
||||
|
||||
holder.text.text = entry.getVisualTitle()
|
||||
holder.subText.apply {
|
||||
// Add subText with username
|
||||
holder.subText?.apply {
|
||||
val username = entry.username
|
||||
if (mShowUserNames && username.isNotEmpty()) {
|
||||
visibility = View.VISIBLE
|
||||
text = username
|
||||
setTextSize(mTextSizeUnit, mSubtextDefaultDimension, mPrefSizeMultiplier)
|
||||
setTextSize(mTextSizeUnit, mSubTextDefaultDimension, mPrefSizeMultiplier)
|
||||
strikeOut(subNode.isCurrentlyExpires)
|
||||
} else {
|
||||
visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
@@ -427,7 +440,21 @@ class NodeAdapter (private val context: Context,
|
||||
}
|
||||
}
|
||||
}
|
||||
holder?.otpToken?.text = otpElement?.token
|
||||
holder?.otpToken?.apply {
|
||||
text = otpElement?.token
|
||||
setTextSize(mTextSizeUnit, mOtpTokenTextDefaultDimension, mPrefSizeMultiplier)
|
||||
}
|
||||
holder?.otpContainer?.setOnClickListener {
|
||||
otpElement?.token?.let { token ->
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.copy_field,
|
||||
TemplateField.getLocalizedName(context, TemplateField.LABEL_TOKEN)),
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
mClipboardHelper.copyToClipboard(token)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class OtpRunnable(val view: View?): Runnable {
|
||||
@@ -468,7 +495,7 @@ class NodeAdapter (private val context: Context,
|
||||
var imageIdentifier: ImageView? = itemView.findViewById(R.id.node_image_identifier)
|
||||
var icon: ImageView = itemView.findViewById(R.id.node_icon)
|
||||
var text: TextView = itemView.findViewById(R.id.node_text)
|
||||
var subText: TextView = itemView.findViewById(R.id.node_subtext)
|
||||
var subText: TextView? = itemView.findViewById(R.id.node_subtext)
|
||||
var meta: TextView = itemView.findViewById(R.id.node_meta)
|
||||
var otpContainer: ViewGroup? = itemView.findViewById(R.id.node_otp_container)
|
||||
var otpProgress: ProgressBar? = itemView.findViewById(R.id.node_otp_progress)
|
||||
|
||||
@@ -53,9 +53,11 @@ class StructureParser(private val structure: AssistStructure) {
|
||||
applicationId = windowNode.title.toString().split("/")[0]
|
||||
Log.d(TAG, "Autofill applicationId: $applicationId")
|
||||
|
||||
if (applicationId?.contains("PopupWindow:") == false) {
|
||||
if (parseViewNode(windowNode.rootViewNode))
|
||||
break@mainLoop
|
||||
}
|
||||
}
|
||||
// If not explicit username field found, add the field just before password field.
|
||||
if (usernameId == null && passwordId != null && usernameIdCandidate != null) {
|
||||
usernameId = usernameIdCandidate
|
||||
|
||||
@@ -177,16 +177,20 @@ class Group : Node, GroupVersionedInterface<Group, Entry> {
|
||||
fun addChildrenFrom(group: Group) {
|
||||
group.groupKDB?.getChildEntries()?.forEach { entryToAdd ->
|
||||
groupKDB?.addChildEntry(entryToAdd)
|
||||
entryToAdd.parent = groupKDB
|
||||
}
|
||||
group.groupKDB?.getChildGroups()?.forEach { groupToAdd ->
|
||||
groupKDB?.addChildGroup(groupToAdd)
|
||||
groupToAdd.parent = groupKDB
|
||||
}
|
||||
|
||||
group.groupKDBX?.getChildEntries()?.forEach { entryToAdd ->
|
||||
groupKDBX?.addChildEntry(entryToAdd)
|
||||
entryToAdd.parent = groupKDBX
|
||||
}
|
||||
group.groupKDBX?.getChildGroups()?.forEach { groupToAdd ->
|
||||
groupKDBX?.addChildGroup(groupToAdd)
|
||||
groupToAdd.parent = groupKDBX
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -178,6 +178,11 @@ class MagikeyboardService : InputMethodService(), KeyboardView.OnKeyboardActionL
|
||||
assignKeyboardView()
|
||||
}
|
||||
|
||||
override fun onEvaluateFullscreenMode(): Boolean {
|
||||
return resources.getBoolean(R.bool.magikeyboard_allow_fullscreen_mode)
|
||||
&& super.onEvaluateFullscreenMode()
|
||||
}
|
||||
|
||||
private fun playVibration(keyCode: Int) {
|
||||
when (keyCode) {
|
||||
Keyboard.KEYCODE_DELETE -> {}
|
||||
@@ -267,7 +272,7 @@ class MagikeyboardService : InputMethodService(), KeyboardView.OnKeyboardActionL
|
||||
if (entryInfoKey != null) {
|
||||
currentInputConnection.commitText(entryInfoKey!!.url, 1)
|
||||
}
|
||||
actionTabAutomatically()
|
||||
actionGoAutomatically()
|
||||
}
|
||||
KEY_FIELDS -> {
|
||||
if (entryInfoKey != null) {
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.kunzisoft.keepass.receivers
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.util.Log
|
||||
import com.kunzisoft.keepass.magikeyboard.MagikeyboardService
|
||||
import com.kunzisoft.keepass.utils.DexUtil
|
||||
import com.kunzisoft.keepass.utils.MagikeyboardUtil
|
||||
|
||||
class DexModeReceiver : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
val enabled = when (intent?.action) {
|
||||
"android.app.action.ENTER_KNOX_DESKTOP_MODE" -> {
|
||||
Log.i(TAG, "Entered DeX mode")
|
||||
false
|
||||
}
|
||||
"android.app.action.EXIT_KNOX_DESKTOP_MODE" -> {
|
||||
Log.i(TAG, "Left DeX mode")
|
||||
true
|
||||
}
|
||||
else -> return
|
||||
}
|
||||
|
||||
MagikeyboardUtil.setEnabled(context!!, enabled)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = DexModeReceiver::class.java.name
|
||||
}
|
||||
}
|
||||
@@ -64,11 +64,13 @@ class DurationDialogFragmentCompat : InputPreferenceDialogFragmentCompat() {
|
||||
|
||||
private fun durationToDaysHoursMinutesSeconds(duration: Long) {
|
||||
if (duration < 0) {
|
||||
mEnabled = false
|
||||
mDays = 0
|
||||
mHours = 0
|
||||
mMinutes = 0
|
||||
mSeconds = 0
|
||||
} else {
|
||||
mEnabled = true
|
||||
mDays = (duration / (24L * 60L * 60L * 1000L)).toInt()
|
||||
val daysMilliseconds = mDays * 24L * 60L * 60L * 1000L
|
||||
mHours = ((duration - daysMilliseconds) / (60L * 60L * 1000L)).toInt()
|
||||
@@ -125,10 +127,9 @@ class DurationDialogFragmentCompat : InputPreferenceDialogFragmentCompat() {
|
||||
}
|
||||
}
|
||||
|
||||
mEnabled = isSwitchActivated()
|
||||
setSwitchAction({ isChecked ->
|
||||
mEnabled = isChecked
|
||||
}, mDays + mHours + mMinutes + mSeconds > 0)
|
||||
}, mEnabled)
|
||||
|
||||
assignValuesInViews()
|
||||
}
|
||||
|
||||
27
app/src/main/java/com/kunzisoft/keepass/utils/DexUtil.kt
Normal file
27
app/src/main/java/com/kunzisoft/keepass/utils/DexUtil.kt
Normal file
@@ -0,0 +1,27 @@
|
||||
package com.kunzisoft.keepass.utils
|
||||
|
||||
import android.content.res.Configuration
|
||||
import android.util.Log
|
||||
|
||||
object DexUtil {
|
||||
private val TAG = DexUtil::class.java.name
|
||||
|
||||
// Determine if the current environment is in DeX mode. Always returns false on non-Samsung
|
||||
// devices.
|
||||
fun isDexMode(config: Configuration): Boolean {
|
||||
// This is the documented way to check this: https://developer.samsung.com/samsung-dex/modify-optimizing.html
|
||||
return try {
|
||||
val configClass = config.javaClass
|
||||
val enabledConstant = configClass.getField("SEM_DESKTOP_MODE_ENABLED").getInt(configClass)
|
||||
val enabledField = configClass.getField("semDesktopModeEnabled").getInt(config)
|
||||
val isEnabled = enabledConstant == enabledField
|
||||
|
||||
Log.d(TAG, "DeX currently enabled: $isEnabled")
|
||||
|
||||
isEnabled
|
||||
} catch (e: Exception) {
|
||||
Log.d(TAG, "Failed to check for DeX mode; likely not Samsung device: $e")
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.kunzisoft.keepass.utils
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.util.Log
|
||||
import com.kunzisoft.keepass.magikeyboard.MagikeyboardService
|
||||
|
||||
object MagikeyboardUtil {
|
||||
private val TAG = MagikeyboardUtil::class.java.name
|
||||
|
||||
// Set whether MagikeyboardService is enabled. This change is persistent and survives app
|
||||
// crashes and device restarts. The state is changed immediately and does not require an app
|
||||
// restart.
|
||||
fun setEnabled(context: Context, enabled: Boolean) {
|
||||
val componentState = if (enabled) {
|
||||
PackageManager.COMPONENT_ENABLED_STATE_ENABLED
|
||||
} else {
|
||||
PackageManager.COMPONENT_ENABLED_STATE_DISABLED
|
||||
}
|
||||
|
||||
Log.d(TAG, "Setting service state: $enabled")
|
||||
|
||||
val component = ComponentName(context, MagikeyboardService::class.java)
|
||||
context.packageManager.setComponentEnabledSetting(component, componentState, PackageManager.DONT_KILL_APP)
|
||||
}
|
||||
}
|
||||
@@ -46,8 +46,6 @@ class DateTimeFieldView @JvmOverloads constructor(context: Context,
|
||||
|
||||
private var mDefault: DateInstant = DateInstant.NEVER_EXPIRES
|
||||
|
||||
var setOnDateClickListener: ((DateInstant) -> Unit)? = null
|
||||
|
||||
init {
|
||||
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater?
|
||||
inflater?.inflate(R.layout.view_date_time, this)
|
||||
|
||||
@@ -275,6 +275,7 @@ abstract class TemplateAbstractView<
|
||||
templateAttribute: TemplateAttribute,
|
||||
entryInfoValue: String,
|
||||
showEmptyFields: Boolean) {
|
||||
try {
|
||||
var fieldView: TEntryFieldView? = findViewWithTag(fieldTag)
|
||||
if (!showEmptyFields && entryInfoValue.isEmpty()) {
|
||||
fieldView?.isFieldVisible = false
|
||||
@@ -291,6 +292,9 @@ abstract class TemplateAbstractView<
|
||||
}
|
||||
fieldView?.value = entryInfoValue
|
||||
fieldView?.applyFontVisibility(mFontInVisibility)
|
||||
} catch(e: Exception) {
|
||||
Log.e(TAG, "Unable to populate entry field view", e)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@@ -299,7 +303,7 @@ abstract class TemplateAbstractView<
|
||||
expires: Boolean,
|
||||
expiryTime: DateInstant,
|
||||
showEmptyFields: Boolean) {
|
||||
|
||||
try {
|
||||
var fieldView: TDateTimeView? = findViewWithTag(fieldTag)
|
||||
if (!showEmptyFields && !expires) {
|
||||
fieldView?.isFieldVisible = false
|
||||
@@ -315,6 +319,9 @@ abstract class TemplateAbstractView<
|
||||
}
|
||||
fieldView?.activation = expires
|
||||
fieldView?.dateTime = expiryTime
|
||||
} catch(e: Exception) {
|
||||
Log.e(TAG, "Unable to populate date time view", e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -53,7 +53,6 @@ class TemplateView @JvmOverloads constructor(context: Context,
|
||||
label = templateAttribute.alias
|
||||
?: TemplateField.getLocalizedName(context, field.name)
|
||||
setMaxChars(templateAttribute.options.getNumberChars())
|
||||
setMaxLines(templateAttribute.options.getNumberLines())
|
||||
// TODO Linkify
|
||||
value = field.protectedValue.stringValue
|
||||
// Here the value is often empty
|
||||
|
||||
@@ -187,6 +187,6 @@ class TextEditFieldView @JvmOverloads constructor(context: Context,
|
||||
|
||||
companion object {
|
||||
const val MAX_CHARS_LIMIT = Integer.MAX_VALUE
|
||||
const val MAX_LINES_LIMIT = 40
|
||||
const val MAX_LINES_LIMIT = Integer.MAX_VALUE
|
||||
}
|
||||
}
|
||||
@@ -214,18 +214,6 @@ class TextFieldView @JvmOverloads constructor(context: Context,
|
||||
}
|
||||
}
|
||||
|
||||
fun setMaxLines(numberLines: Int) {
|
||||
when {
|
||||
numberLines <= 0 -> {
|
||||
valueView.maxLines = MAX_LINES_LIMIT
|
||||
}
|
||||
else -> {
|
||||
val lines = if (numberLines > MAX_LINES_LIMIT) MAX_LINES_LIMIT else numberLines
|
||||
valueView.maxLines = lines
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setProtection(protection: Boolean, hiddenProtectedValue: Boolean = false) {
|
||||
showButton.isVisible = protection
|
||||
showButton.isSelected = hiddenProtectedValue
|
||||
@@ -343,6 +331,5 @@ class TextFieldView @JvmOverloads constructor(context: Context,
|
||||
|
||||
companion object {
|
||||
const val MAX_CHARS_LIMIT = Integer.MAX_VALUE
|
||||
const val MAX_LINES_LIMIT = 40
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
style="@style/KeepassDXStyle.TextAppearance.Title"
|
||||
android:textStyle="bold"
|
||||
android:textColor="?android:attr/textColor"/>
|
||||
|
||||
<TextView
|
||||
@@ -62,7 +63,7 @@
|
||||
app:indicatorColor="?attr/colorAccent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:indeterminate="true"
|
||||
android:max="100"/>
|
||||
|
||||
|
||||
@@ -30,8 +30,7 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:minHeight="56dp"
|
||||
android:maxHeight="72dp"
|
||||
android:minHeight="48dp"
|
||||
app:layout_constraintWidth_percent="@dimen/content_percent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
@@ -104,10 +103,12 @@
|
||||
tools:text="7543A7EAB2EA7CFD1394F1615EBEB08C" />
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
<LinearLayout
|
||||
android:id="@+id/node_options"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:gravity="end"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginLeft="12dp"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
@@ -118,9 +119,11 @@
|
||||
android:id="@+id/node_otp_container"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_marginRight="12dp"
|
||||
android:padding="4dp"
|
||||
android:orientation="horizontal"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@+id/node_attachment_icon"
|
||||
app:layout_constraintEnd_toEndOf="parent">
|
||||
@@ -162,7 +165,7 @@
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/node_otp_container" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -30,8 +30,7 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:minHeight="56dp"
|
||||
android:maxHeight="72dp"
|
||||
android:minHeight="48dp"
|
||||
app:layout_constraintWidth_percent="@dimen/content_percent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
@@ -89,18 +88,6 @@
|
||||
android:maxLines="2"
|
||||
tools:text="Node Title" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/node_subtext"
|
||||
style="@style/KeepassDXStyle.TextAppearance.Group.SubTitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="-4dp"
|
||||
android:gravity="center_vertical"
|
||||
android:lines="1"
|
||||
android:singleLine="true"
|
||||
android:visibility="gone"
|
||||
tools:text="Node SubTitle" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/node_meta"
|
||||
style="@style/KeepassDXStyle.TextAppearance.Group.Meta"
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
android:focusable="false"
|
||||
android:cursorVisible="false"
|
||||
android:focusableInTouchMode="false"
|
||||
style="@style/KeepassDXStyle.TextAppearance.Large"
|
||||
style="@style/KeepassDXStyle.TextAppearance.TextEntryItem"
|
||||
tools:text="2020-03-04 05:00" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
|
||||
22
app/src/main/res/values-land/config.xml
Normal file
22
app/src/main/res/values-land/config.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<?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/>.
|
||||
-->
|
||||
<resources>
|
||||
<bool name="magikeyboard_allow_fullscreen_mode">true</bool>
|
||||
</resources>
|
||||
22
app/src/main/res/values-sw430dp/config.xml
Normal file
22
app/src/main/res/values-sw430dp/config.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<?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/>.
|
||||
-->
|
||||
<resources>
|
||||
<bool name="magikeyboard_allow_fullscreen_mode">false</bool>
|
||||
</resources>
|
||||
22
app/src/main/res/values/config.xml
Normal file
22
app/src/main/res/values/config.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<?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/>.
|
||||
-->
|
||||
<resources>
|
||||
<bool name="magikeyboard_allow_fullscreen_mode">false</bool>
|
||||
</resources>
|
||||
@@ -367,7 +367,7 @@
|
||||
<string name="clipboard_warning">If automatic deletion of clipboard fails, delete its history manually.</string>
|
||||
<string name="lock">Lock</string>
|
||||
<string name="lock_database_screen_off_title">Screen lock</string>
|
||||
<string name="lock_database_screen_off_summary">Lock the database when the screen is off</string>
|
||||
<string name="lock_database_screen_off_summary">Lock the database after a few seconds once the screen is off</string>
|
||||
<string name="lock_database_back_root_title">Press \'Back\' to lock</string>
|
||||
<string name="lock_database_back_root_summary">Lock the database when the user clicks the back button on the root screen</string>
|
||||
<string name="lock_database_show_button_title">Show lock button</string>
|
||||
|
||||
@@ -4,4 +4,4 @@
|
||||
* Fix timeout in dialogs #716
|
||||
* Check URI permissions #626
|
||||
* Better autofill implementation #943 #946 #984 #1070 (Thx @uduerholz)
|
||||
* Improvements #680 #1035 #1043 #942 #1021 #1027
|
||||
* Improvements #680 #1035 #1043 #942 #1021 #1027 #1046 #1082 #1083 (Thx @chenxiaolong)
|
||||
7
fastlane/metadata/android/en-US/changelogs/88.txt
Normal file
7
fastlane/metadata/android/en-US/changelogs/88.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
* Fix text size and smallest margin #1085
|
||||
* Fix number of lines during an edition #1073
|
||||
* Fix Magikeyboard URL auto action #1100
|
||||
* Fix exception after group name change and save #1112
|
||||
* Fix timeout reset #1107
|
||||
* Fix search actions #1091 #1092
|
||||
* Small changes #1106 #1085
|
||||
1
fastlane/metadata/android/en-US/changelogs/89.txt
Normal file
1
fastlane/metadata/android/en-US/changelogs/89.txt
Normal file
@@ -0,0 +1 @@
|
||||
* Samsung DeX mode #1114 #245 (Thx @chenxiaolong)
|
||||
@@ -4,4 +4,4 @@
|
||||
* Correction du délai d'expiration dans les dialogues #716
|
||||
* Vérification des permissions URI #626
|
||||
* Meilleure implémentation du remplissage auto #943 #946 #984 #1070 (Thx @uduerholz)
|
||||
* Améliorations #680 #1035 #1043 #942 #1021 #1027
|
||||
* Améliorations #680 #1035 #1043 #942 #1021 #1027 #1046 #1082 #1083 (Thx @chenxiaolong)
|
||||
7
fastlane/metadata/android/fr-FR/changelogs/88.txt
Normal file
7
fastlane/metadata/android/fr-FR/changelogs/88.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
* Correction de la taille de texte et plus petites marges #1085
|
||||
* Correction du nombre de lignes d'édition #1073
|
||||
* Correction de l'action auto de l'URL Magiclavier #1100
|
||||
* Correction de l'exception après un changement de nom de groupe et sauvegarde #1112
|
||||
* Correction de la réinitialisation du délai d'expiration #1107
|
||||
* Correction des actions de recherche #1091 #1092
|
||||
* Petits changements #1106 #1085
|
||||
1
fastlane/metadata/android/fr-FR/changelogs/89.txt
Normal file
1
fastlane/metadata/android/fr-FR/changelogs/89.txt
Normal file
@@ -0,0 +1 @@
|
||||
* Mode Samsung DeX #1114 #245 (Thx @chenxiaolong)
|
||||
Reference in New Issue
Block a user