mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e3b69789bf | ||
|
|
54f2ed9fab | ||
|
|
2fea019b95 | ||
|
|
9ac7ef2d22 | ||
|
|
6d452fa49c | ||
|
|
d99edb6b4d | ||
|
|
cb679f0d59 | ||
|
|
5dd9f75095 |
@@ -1,3 +1,9 @@
|
||||
KeePassDX(4.2.3)
|
||||
* Fix multiple Passkey selection #2253
|
||||
* Fix database dialog subtitle #2254
|
||||
* Fix save search info if URL present #2255
|
||||
* Small fixes
|
||||
|
||||
KeePassDX(4.2.2)
|
||||
* Fix database merge algorithm #2223
|
||||
* Fix save search info #2243
|
||||
|
||||
@@ -11,8 +11,8 @@ android {
|
||||
applicationId "com.kunzisoft.keepass"
|
||||
minSdkVersion 19
|
||||
targetSdkVersion 35
|
||||
versionCode = 147
|
||||
versionName = "4.2.2"
|
||||
versionCode = 148
|
||||
versionName = "4.2.3"
|
||||
multiDexEnabled true
|
||||
|
||||
testApplicationId = "com.kunzisoft.keepass.tests"
|
||||
|
||||
@@ -841,7 +841,7 @@ class GroupActivity : DatabaseLockActivity(),
|
||||
// Open child group
|
||||
loadMainGroup(GroupState(group.nodeId, 0))
|
||||
} catch (e: ClassCastException) {
|
||||
Log.e(TAG, "Node can't be cast in Group")
|
||||
Log.e(TAG, "Node can't be cast in Group", e)
|
||||
}
|
||||
|
||||
Type.ENTRY -> try {
|
||||
@@ -867,6 +867,7 @@ class GroupActivity : DatabaseLockActivity(),
|
||||
if (!database.isReadOnly
|
||||
&& searchInfo != null
|
||||
&& PreferencesUtil.isKeyboardSaveSearchInfoEnable(this@GroupActivity)
|
||||
&& entryVersioned.containsSearchInfo(database, searchInfo).not()
|
||||
) {
|
||||
updateEntryWithRegisterInfo(
|
||||
database,
|
||||
@@ -884,6 +885,7 @@ class GroupActivity : DatabaseLockActivity(),
|
||||
if (!database.isReadOnly
|
||||
&& searchInfo != null
|
||||
&& PreferencesUtil.isAutofillSaveSearchInfoEnable(this@GroupActivity)
|
||||
&& entryVersioned.containsSearchInfo(database, searchInfo).not()
|
||||
) {
|
||||
updateEntryWithRegisterInfo(
|
||||
database,
|
||||
@@ -912,7 +914,7 @@ class GroupActivity : DatabaseLockActivity(),
|
||||
finish()
|
||||
})
|
||||
} catch (e: ClassCastException) {
|
||||
Log.e(TAG, "Node can't be cast in Entry")
|
||||
Log.e(TAG, "Node can't be cast in Entry", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -981,6 +983,17 @@ class GroupActivity : DatabaseLockActivity(),
|
||||
updateEntry(entry, newEntry)
|
||||
}
|
||||
|
||||
private fun Entry.containsSearchInfo(
|
||||
database: ContextualDatabase,
|
||||
searchInfo: SearchInfo
|
||||
): Boolean {
|
||||
return getEntryInfo(
|
||||
database,
|
||||
raw = true,
|
||||
removeTemplateConfiguration = false
|
||||
).containsSearchInfo(searchInfo)
|
||||
}
|
||||
|
||||
private fun finishNodeAction() {
|
||||
actionNodeMode?.finish()
|
||||
}
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
package com.kunzisoft.keepass.activities.legacy
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContract
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.app.ActivityOptionsCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
@@ -16,6 +20,7 @@ import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.dialogs.DatabaseChangedDialogFragment
|
||||
import com.kunzisoft.keepass.activities.dialogs.DatabaseChangedDialogFragment.Companion.DATABASE_CHANGED_DIALOG_TAG
|
||||
import com.kunzisoft.keepass.activities.stylish.StylishActivity
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.setActivityResult
|
||||
import com.kunzisoft.keepass.database.ContextualDatabase
|
||||
import com.kunzisoft.keepass.database.DatabaseTaskProvider.Companion.startDatabaseService
|
||||
import com.kunzisoft.keepass.model.SnapFileDatabaseInfo
|
||||
@@ -54,49 +59,104 @@ abstract class DatabaseActivity : StylishActivity(), DatabaseRetrieval {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Useful to only waiting for the activity result and prevent any parallel action
|
||||
*/
|
||||
var credentialResultLaunched = false
|
||||
|
||||
/**
|
||||
* Utility activity result launcher,
|
||||
* Used recursively, close each activity with return data
|
||||
*/
|
||||
protected var mCredentialActivityResultLauncher: CredentialActivityResultLauncher =
|
||||
CredentialActivityResultLauncher(
|
||||
registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) {
|
||||
setActivityResult(
|
||||
lockDatabase = false,
|
||||
resultCode = it.resultCode,
|
||||
data = it.data
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* Custom ActivityResultLauncher to manage the database action
|
||||
*/
|
||||
protected inner class CredentialActivityResultLauncher(
|
||||
val builder: ActivityResultLauncher<Intent>
|
||||
) : ActivityResultLauncher<Intent>() {
|
||||
|
||||
override fun launch(
|
||||
input: Intent?,
|
||||
options: ActivityOptionsCompat?
|
||||
) {
|
||||
credentialResultLaunched = true
|
||||
builder.launch(input, options)
|
||||
}
|
||||
|
||||
override fun unregister() {
|
||||
builder.unregister()
|
||||
}
|
||||
|
||||
override fun getContract(): ActivityResultContract<Intent?, *> {
|
||||
return builder.getContract()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
if (savedInstanceState != null
|
||||
&& savedInstanceState.containsKey(CREDENTIAL_RESULT_LAUNCHER_KEY)
|
||||
) {
|
||||
credentialResultLaunched = savedInstanceState.getBoolean(CREDENTIAL_RESULT_LAUNCHER_KEY)
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
mDatabaseViewModel.actionState.collect { uiState ->
|
||||
when (uiState) {
|
||||
is DatabaseViewModel.ActionState.Loading -> {}
|
||||
is DatabaseViewModel.ActionState.OnDatabaseReloaded -> {
|
||||
if (finishActivityIfReloadRequested()) {
|
||||
finish()
|
||||
if (credentialResultLaunched.not()) {
|
||||
when (uiState) {
|
||||
is DatabaseViewModel.ActionState.Wait -> {}
|
||||
is DatabaseViewModel.ActionState.OnDatabaseReloaded -> {
|
||||
if (finishActivityIfReloadRequested()) {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
is DatabaseViewModel.ActionState.OnDatabaseInfoChanged -> {
|
||||
if (manageDatabaseInfo()) {
|
||||
showDatabaseChangedDialog(
|
||||
uiState.previousDatabaseInfo,
|
||||
uiState.newDatabaseInfo,
|
||||
uiState.readOnlyDatabase
|
||||
is DatabaseViewModel.ActionState.OnDatabaseInfoChanged -> {
|
||||
if (manageDatabaseInfo()) {
|
||||
showDatabaseChangedDialog(
|
||||
uiState.previousDatabaseInfo,
|
||||
uiState.newDatabaseInfo,
|
||||
uiState.readOnlyDatabase
|
||||
)
|
||||
}
|
||||
}
|
||||
is DatabaseViewModel.ActionState.OnDatabaseActionRequested -> {
|
||||
startDatabasePermissionService(
|
||||
uiState.bundle,
|
||||
uiState.actionTask
|
||||
)
|
||||
}
|
||||
}
|
||||
is DatabaseViewModel.ActionState.OnDatabaseActionRequested -> {
|
||||
startDatabasePermissionService(
|
||||
uiState.bundle,
|
||||
uiState.actionTask
|
||||
)
|
||||
}
|
||||
is DatabaseViewModel.ActionState.OnDatabaseActionStarted -> {
|
||||
progressTaskViewModel.start(uiState.progressMessage)
|
||||
}
|
||||
is DatabaseViewModel.ActionState.OnDatabaseActionUpdated -> {
|
||||
progressTaskViewModel.update(uiState.progressMessage)
|
||||
}
|
||||
is DatabaseViewModel.ActionState.OnDatabaseActionStopped -> {
|
||||
progressTaskViewModel.stop()
|
||||
}
|
||||
is DatabaseViewModel.ActionState.OnDatabaseActionFinished -> {
|
||||
onDatabaseActionFinished(
|
||||
uiState.database,
|
||||
uiState.actionTask,
|
||||
uiState.result
|
||||
)
|
||||
progressTaskViewModel.stop()
|
||||
is DatabaseViewModel.ActionState.OnDatabaseActionStarted -> {
|
||||
progressTaskViewModel.show(uiState.progressMessage)
|
||||
}
|
||||
is DatabaseViewModel.ActionState.OnDatabaseActionUpdated -> {
|
||||
progressTaskViewModel.show(uiState.progressMessage)
|
||||
}
|
||||
is DatabaseViewModel.ActionState.OnDatabaseActionStopped -> {
|
||||
progressTaskViewModel.hide()
|
||||
}
|
||||
is DatabaseViewModel.ActionState.OnDatabaseActionFinished -> {
|
||||
onDatabaseActionFinished(
|
||||
uiState.database,
|
||||
uiState.actionTask,
|
||||
uiState.result
|
||||
)
|
||||
progressTaskViewModel.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -106,9 +166,9 @@ abstract class DatabaseActivity : StylishActivity(), DatabaseRetrieval {
|
||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
progressTaskViewModel.progressTaskState.collect { state ->
|
||||
when (state) {
|
||||
ProgressTaskViewModel.ProgressTaskState.Start ->
|
||||
showDialog()
|
||||
ProgressTaskViewModel.ProgressTaskState.Stop ->
|
||||
is ProgressTaskViewModel.ProgressTaskState.Show ->
|
||||
startDialog()
|
||||
is ProgressTaskViewModel.ProgressTaskState.Hide ->
|
||||
stopDialog()
|
||||
}
|
||||
}
|
||||
@@ -117,16 +177,23 @@ abstract class DatabaseActivity : StylishActivity(), DatabaseRetrieval {
|
||||
lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
mDatabaseViewModel.databaseState.collect { database ->
|
||||
// Nullable function
|
||||
onUnknownDatabaseRetrieved(database)
|
||||
database?.let {
|
||||
onDatabaseRetrieved(database)
|
||||
if (credentialResultLaunched.not()) {
|
||||
// Nullable function
|
||||
onUnknownDatabaseRetrieved(database)
|
||||
database?.let {
|
||||
onDatabaseRetrieved(database)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
outState.putBoolean(CREDENTIAL_RESULT_LAUNCHER_KEY, credentialResultLaunched)
|
||||
super.onSaveInstanceState(outState)
|
||||
}
|
||||
|
||||
/**
|
||||
* Nullable function to retrieve a database
|
||||
*/
|
||||
@@ -207,7 +274,7 @@ abstract class DatabaseActivity : StylishActivity(), DatabaseRetrieval {
|
||||
}
|
||||
}
|
||||
|
||||
private fun showDialog() {
|
||||
private fun startDialog() {
|
||||
lifecycleScope.launch {
|
||||
if (showDatabaseDialog()) {
|
||||
if (progressTaskDialogFragment == null) {
|
||||
@@ -233,4 +300,8 @@ abstract class DatabaseActivity : StylishActivity(), DatabaseRetrieval {
|
||||
protected open fun showDatabaseDialog(): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val CREDENTIAL_RESULT_LAUNCHER_KEY = "com.kunzisoft.keepass.CREDENTIAL_RESULT_LAUNCHER_KEY"
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,9 @@
|
||||
package com.kunzisoft.keepass.activities.legacy
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.isIntentSenderMode
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.removeInfo
|
||||
@@ -15,7 +12,6 @@ import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.retrieveReg
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.retrieveSearchInfo
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.retrieveSpecialMode
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.retrieveTypeMode
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.setActivityResult
|
||||
import com.kunzisoft.keepass.credentialprovider.SpecialMode
|
||||
import com.kunzisoft.keepass.credentialprovider.TypeMode
|
||||
import com.kunzisoft.keepass.model.RegisterInfo
|
||||
@@ -34,21 +30,6 @@ abstract class DatabaseModeActivity : DatabaseActivity() {
|
||||
|
||||
private var mToolbarSpecial: ToolbarSpecial? = null
|
||||
|
||||
/**
|
||||
* Utility activity result launcher,
|
||||
* Used recursively, close each activity with return data
|
||||
*/
|
||||
protected open var mCredentialActivityResultLauncher: ActivityResultLauncher<Intent>? =
|
||||
registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) {
|
||||
setActivityResult(
|
||||
lockDatabase = false,
|
||||
resultCode = it.resultCode,
|
||||
data = it.data
|
||||
)
|
||||
}
|
||||
|
||||
open fun onDatabaseBackPressed() {
|
||||
if (mSpecialMode != SpecialMode.DEFAULT)
|
||||
onCancelSpecialMode()
|
||||
|
||||
@@ -78,6 +78,7 @@ class EntrySelectionLauncherActivity : DatabaseModeActivity() {
|
||||
context = this@EntrySelectionLauncherActivity,
|
||||
searchInfo = uiState.searchInfo
|
||||
)
|
||||
finish()
|
||||
}
|
||||
is EntrySelectionViewModel.UIState.LaunchGroupActivityForSearch -> {
|
||||
GroupActivity.launchForSearch(
|
||||
@@ -85,6 +86,7 @@ class EntrySelectionLauncherActivity : DatabaseModeActivity() {
|
||||
database = uiState.database,
|
||||
searchInfo = uiState.searchInfo
|
||||
)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -661,7 +661,9 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
}
|
||||
|
||||
private fun updateMessage(resId: Int) {
|
||||
mProgressMessage.messageId = resId
|
||||
mProgressMessage = mProgressMessage.copy(
|
||||
messageId = resId
|
||||
)
|
||||
notifyProgressMessage()
|
||||
}
|
||||
|
||||
|
||||
@@ -70,16 +70,35 @@ open class ProgressTaskDialogFragment : DialogFragment() {
|
||||
|
||||
lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
progressTaskViewModel.progressMessageState.collect { state ->
|
||||
updateView(titleView,
|
||||
state.titleId?.let { title -> getString(title) })
|
||||
updateView(messageView,
|
||||
state.messageId?.let { message -> getString(message) })
|
||||
updateView(warningView,
|
||||
state.warningId?.let { warning -> getString(warning) })
|
||||
cancelButton?.isVisible = state.cancelable != null
|
||||
cancelButton?.setOnClickListener {
|
||||
state.cancelable?.invoke()
|
||||
progressTaskViewModel.progressTaskState.collect { state ->
|
||||
when (state) {
|
||||
is ProgressTaskViewModel.ProgressTaskState.Show -> {
|
||||
val value = state.value
|
||||
updateView(
|
||||
titleView,
|
||||
value.titleId?.let { title ->
|
||||
getString(title)
|
||||
})
|
||||
updateView(
|
||||
messageView,
|
||||
value.messageId?.let { message ->
|
||||
getString(message)
|
||||
})
|
||||
updateView(
|
||||
warningView,
|
||||
value.warningId?.let { warning ->
|
||||
getString(warning)
|
||||
})
|
||||
cancelButton?.apply {
|
||||
isVisible = value.cancelable != null
|
||||
setOnClickListener {
|
||||
value.cancelable?.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// Nothing here, this fragment is stopped externally
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,30 +4,25 @@ import androidx.lifecycle.ViewModel
|
||||
import com.kunzisoft.keepass.database.ProgressMessage
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
|
||||
class ProgressTaskViewModel: ViewModel() {
|
||||
|
||||
private val mProgressMessageState = MutableStateFlow(ProgressMessage())
|
||||
val progressMessageState: StateFlow<ProgressMessage> = mProgressMessageState
|
||||
|
||||
private val mProgressTaskState = MutableStateFlow<ProgressTaskState>(ProgressTaskState.Stop)
|
||||
private val mProgressTaskState = MutableStateFlow<ProgressTaskState>(ProgressTaskState.Hide)
|
||||
val progressTaskState: StateFlow<ProgressTaskState> = mProgressTaskState
|
||||
|
||||
fun update(value: ProgressMessage) {
|
||||
mProgressMessageState.value = value
|
||||
fun show(value: ProgressMessage) {
|
||||
mProgressTaskState.update { currentState ->
|
||||
ProgressTaskState.Show(value)
|
||||
}
|
||||
}
|
||||
|
||||
fun start(value: ProgressMessage) {
|
||||
mProgressTaskState.value = ProgressTaskState.Start
|
||||
update(value)
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
mProgressTaskState.value = ProgressTaskState.Stop
|
||||
fun hide() {
|
||||
mProgressTaskState.value = ProgressTaskState.Hide
|
||||
}
|
||||
|
||||
sealed class ProgressTaskState {
|
||||
object Start: ProgressTaskState()
|
||||
object Stop: ProgressTaskState()
|
||||
data class Show(val value: ProgressMessage): ProgressTaskState()
|
||||
object Hide: ProgressTaskState()
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@ class DatabaseViewModel(application: Application): AndroidViewModel(application)
|
||||
val database: ContextualDatabase?
|
||||
get() = databaseState.value
|
||||
|
||||
private val mActionState = MutableStateFlow<ActionState>(ActionState.Loading)
|
||||
private val mActionState = MutableStateFlow<ActionState>(ActionState.Wait)
|
||||
val actionState: StateFlow<ActionState> = mActionState
|
||||
|
||||
private var mDatabaseTaskProvider: DatabaseTaskProvider = DatabaseTaskProvider(
|
||||
@@ -469,7 +469,7 @@ class DatabaseViewModel(application: Application): AndroidViewModel(application)
|
||||
}
|
||||
|
||||
sealed class ActionState {
|
||||
object Loading: ActionState()
|
||||
object Wait: ActionState()
|
||||
object OnDatabaseReloaded: ActionState()
|
||||
data class OnDatabaseActionRequested(
|
||||
val bundle: Bundle? = null,
|
||||
|
||||
@@ -71,7 +71,7 @@ object AppOriginEntryField {
|
||||
/**
|
||||
* Useful to detect if an other KeePass compatibility app already add a web domain or an app id
|
||||
*/
|
||||
private fun EntryInfo.containsDomainOrApplicationId(search: String): Boolean {
|
||||
fun EntryInfo.containsDomainOrApplicationId(search: String): Boolean {
|
||||
if (url.contains(search))
|
||||
return true
|
||||
return customFields.find {
|
||||
|
||||
@@ -27,6 +27,7 @@ import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.element.Field
|
||||
import com.kunzisoft.keepass.database.element.Tags
|
||||
import com.kunzisoft.keepass.database.element.entry.AutoType
|
||||
import com.kunzisoft.keepass.model.AppOriginEntryField.containsDomainOrApplicationId
|
||||
import com.kunzisoft.keepass.model.AppOriginEntryField.setAppOrigin
|
||||
import com.kunzisoft.keepass.model.AppOriginEntryField.setApplicationId
|
||||
import com.kunzisoft.keepass.model.AppOriginEntryField.setWebDomain
|
||||
@@ -183,6 +184,18 @@ class EntryInfo : NodeInfo {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* True if this entry contains domain or applicationId,
|
||||
* OTP is ignored and considered not present
|
||||
*/
|
||||
fun containsSearchInfo(searchInfo: SearchInfo): Boolean {
|
||||
return searchInfo.webDomain?.let { webDomain ->
|
||||
containsDomainOrApplicationId(webDomain)
|
||||
} ?: searchInfo.applicationId?.let { applicationId ->
|
||||
containsDomainOrApplicationId(applicationId)
|
||||
} ?: false
|
||||
}
|
||||
|
||||
/**
|
||||
* Add searchInfo to current EntryInfo
|
||||
*/
|
||||
|
||||
4
fastlane/metadata/android/en-US/changelogs/148.txt
Normal file
4
fastlane/metadata/android/en-US/changelogs/148.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
* Fix multiple Passkey selection #2253
|
||||
* Fix database dialog subtitle #2254
|
||||
* Fix save search info if URL present #2255
|
||||
* Small fixes
|
||||
4
fastlane/metadata/android/fr-FR/changelogs/148.txt
Normal file
4
fastlane/metadata/android/fr-FR/changelogs/148.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
* Correction de la selection multiple des Passkeys #2253
|
||||
* Correction du sous-titre du dialogue de la base de données #2254
|
||||
* Correction de la sauvegarde des infos de recherchesi l'URL est present #2255
|
||||
* Petites corrections
|
||||
Reference in New Issue
Block a user