Skip to content

Commit 703c5bd

Browse files
Merge pull request #16506 from nextcloud/app-passcode-m3-dialog
feat(settings-activity): app passcode m3 dialog
2 parents 36dd404 + bf87784 commit 703c5bd

File tree

8 files changed

+294
-98
lines changed

8 files changed

+294
-98
lines changed

app/src/main/java/com/nextcloud/client/di/ComponentsModule.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@
7878
import com.owncloud.android.ui.activity.UploadListActivity;
7979
import com.owncloud.android.ui.activity.UserInfoActivity;
8080
import com.owncloud.android.ui.dialog.AccountRemovalDialog;
81+
import com.owncloud.android.ui.dialog.AppPassCodeDialog;
8182
import com.owncloud.android.ui.dialog.ChooseRichDocumentsTemplateDialogFragment;
8283
import com.owncloud.android.ui.dialog.ChooseTemplateDialogFragment;
8384
import com.owncloud.android.ui.dialog.ConfirmationDialogFragment;
@@ -427,6 +428,9 @@ abstract class ComponentsModule {
427428
@ContributesAndroidInjector
428429
abstract ThemeSelectionDialog themeSelectionDialog();
429430

431+
@ContributesAndroidInjector
432+
abstract AppPassCodeDialog appPassCodeDialog();
433+
430434
@ContributesAndroidInjector
431435
abstract SharePasswordDialogFragment sharePasswordDialogFragment();
432436

app/src/main/java/com/owncloud/android/ui/activity/ExtendedSettingsActivity.kt

Lines changed: 12 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,10 @@ import com.owncloud.android.ui.model.ExtendedSettingsActivityDialog
1515

1616
class ExtendedSettingsActivity : AppCompatActivity() {
1717

18-
private var dialogShown = false
19-
2018
@Suppress("ReturnCount")
2119
override fun onCreate(savedInstanceState: Bundle?) {
2220
super.onCreate(savedInstanceState)
2321

24-
if (savedInstanceState != null) {
25-
dialogShown = savedInstanceState.getBoolean(KEY_DIALOG_SHOWN, false)
26-
}
27-
28-
if (dialogShown) {
29-
return
30-
}
31-
3222
val dialogKey = intent.getStringExtra(EXTRA_DIALOG_TYPE) ?: run {
3323
finish()
3424
return
@@ -39,22 +29,22 @@ class ExtendedSettingsActivity : AppCompatActivity() {
3929
return
4030
}
4131

42-
dialogType.showDialog(this)
43-
dialogShown = true
44-
}
45-
46-
override fun onSaveInstanceState(outState: Bundle) {
47-
super.onSaveInstanceState(outState)
48-
outState.putBoolean(KEY_DIALOG_SHOWN, dialogShown)
32+
val dismissable = intent.getBooleanExtra(EXTRA_DISMISSABLE, true)
33+
dialogType.showDialog(this, dismissable)
4934
}
5035

5136
companion object {
37+
private const val EXTRA_DISMISSABLE = "dismissable"
5238
private const val EXTRA_DIALOG_TYPE = "dialog_type"
53-
private const val KEY_DIALOG_SHOWN = "dialog_shown"
5439

55-
fun createIntent(context: Context, dialogType: ExtendedSettingsActivityDialog): Intent =
56-
Intent(context, ExtendedSettingsActivity::class.java).apply {
57-
putExtra(EXTRA_DIALOG_TYPE, dialogType.key)
58-
}
40+
@JvmOverloads
41+
fun createIntent(
42+
context: Context,
43+
dialogType: ExtendedSettingsActivityDialog,
44+
dismissable: Boolean = true
45+
): Intent = Intent(context, ExtendedSettingsActivity::class.java).apply {
46+
putExtra(EXTRA_DIALOG_TYPE, dialogType.key)
47+
putExtra(EXTRA_DISMISSABLE, dismissable)
48+
}
5949
}
6050
}

app/src/main/java/com/owncloud/android/ui/activity/SettingsActivity.java

Lines changed: 39 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
import android.os.Bundle;
2828
import android.os.Handler;
2929
import android.os.Looper;
30-
import android.preference.ListPreference;
3130
import android.preference.Preference;
3231
import android.preference.PreferenceActivity;
3332
import android.preference.PreferenceCategory;
@@ -65,7 +64,6 @@
6564
import com.owncloud.android.lib.common.ExternalLinkType;
6665
import com.owncloud.android.lib.common.utils.Log_OC;
6766
import com.owncloud.android.providers.DocumentsStorageProvider;
68-
import com.owncloud.android.ui.ListPreferenceDialog;
6967
import com.owncloud.android.ui.ThemeableSwitchPreference;
7068
import com.owncloud.android.ui.asynctasks.LoadingVersionNumberTask;
7169
import com.owncloud.android.ui.dialog.setupEncryption.SetupEncryptionDialogFragment;
@@ -80,7 +78,6 @@
8078
import com.owncloud.android.utils.theme.CapabilityUtils;
8179
import com.owncloud.android.utils.theme.ViewThemeUtils;
8280

83-
import java.util.ArrayList;
8481
import java.util.Objects;
8582

8683
import javax.inject.Inject;
@@ -129,7 +126,7 @@ public class SettingsActivity extends PreferenceActivity
129126

130127
private Uri serverBaseUri;
131128

132-
private ListPreferenceDialog lock;
129+
private Preference lock;
133130
private ThemeableSwitchPreference showHiddenFiles;
134131
private ThemeableSwitchPreference showEcosystemApps;
135132
private AppCompatDelegate delegate;
@@ -212,9 +209,8 @@ public void onBackPressed() {
212209

213210
private void showPasscodeDialogIfEnforceAppProtection() {
214211
if (MDMConfig.INSTANCE.enforceProtection(this) && Objects.equals(preferences.getLockPreference(), SettingsActivity.LOCK_NONE) && lock != null) {
215-
lock.showDialog();
216-
lock.dismissible(false);
217-
lock.enableCancelButton(false);
212+
Intent intent = ExtendedSettingsActivity.Companion.createIntent(this, ExtendedSettingsActivityDialog.AppPasscode, false);
213+
startActivityForResult(intent, ExtendedSettingsActivityDialog.AppPasscode.getResultId());
218214
}
219215
}
220216

@@ -742,63 +738,33 @@ private void setupShowEcosystemAppsPreference(PreferenceCategory preferenceCateg
742738
private void setupLockPreference(PreferenceCategory preferenceCategoryDetails,
743739
boolean passCodeEnabled,
744740
boolean deviceCredentialsEnabled) {
745-
boolean enforceProtection = MDMConfig.INSTANCE.enforceProtection(this);
746-
lock = (ListPreferenceDialog) findPreference(PREFERENCE_LOCK);
747-
int optionSize = 3;
748-
if (enforceProtection) {
749-
optionSize = 2;
750-
}
751-
741+
lock = findPreference(PREFERENCE_LOCK);
752742
if (lock != null && (passCodeEnabled || deviceCredentialsEnabled)) {
753-
ArrayList<String> lockEntries = new ArrayList<>(optionSize);
754-
lockEntries.add(getString(R.string.prefs_lock_using_passcode));
755-
lockEntries.add(getString(R.string.prefs_lock_using_device_credentials));
756-
757-
ArrayList<String> lockValues = new ArrayList<>(optionSize);
758-
lockValues.add(LOCK_PASSCODE);
759-
lockValues.add(LOCK_DEVICE_CREDENTIALS);
760-
761-
if (!enforceProtection) {
762-
lockEntries.add(getString(R.string.prefs_lock_none));
763-
lockValues.add(LOCK_NONE);
764-
}
743+
String currentLock = preferences.getLockPreference();
744+
updateLockSummary(lock, currentLock);
765745

766-
if (!passCodeEnabled) {
767-
lockEntries.remove(getString(R.string.prefs_lock_using_passcode));
768-
lockValues.remove(LOCK_PASSCODE);
769-
} else if (!deviceCredentialsEnabled || !DeviceCredentialUtils.areCredentialsAvailable(getApplicationContext())) {
770-
lockEntries.remove(getString(R.string.prefs_lock_using_device_credentials));
771-
lockValues.remove(LOCK_DEVICE_CREDENTIALS);
772-
}
773-
774-
String[] lockEntriesArr = new String[lockEntries.size()];
775-
lockEntriesArr = lockEntries.toArray(lockEntriesArr);
776-
String[] lockValuesArr = new String[lockValues.size()];
777-
lockValuesArr = lockValues.toArray(lockValuesArr);
778-
779-
lock.setEntries(lockEntriesArr);
780-
lock.setEntryValues(lockValuesArr);
781-
lock.setSummary(lock.getEntry());
782-
783-
lock.setOnPreferenceChangeListener((preference, o) -> {
784-
pendingLock = LOCK_NONE;
785-
String oldValue = ((ListPreference) preference).getValue();
786-
String newValue = (String) o;
787-
if (!oldValue.equals(newValue)) {
788-
if (LOCK_NONE.equals(oldValue)) {
789-
enableLock(newValue);
790-
} else {
791-
pendingLock = newValue;
792-
disableLock(oldValue);
793-
}
794-
}
795-
return false;
746+
lock.setOnPreferenceClickListener(preference -> {
747+
Intent intent = ExtendedSettingsActivity.Companion.createIntent(this, ExtendedSettingsActivityDialog.AppPasscode);
748+
startActivityForResult(intent, ExtendedSettingsActivityDialog.AppPasscode.getResultId());
749+
return true;
796750
});
797751
} else {
798752
preferenceCategoryDetails.removePreference(lock);
799753
}
800754
}
801755

756+
private void updateLockSummary(Preference lockPreference, String lockValue) {
757+
String summary;
758+
if (LOCK_PASSCODE.equals(lockValue)) {
759+
summary = getString(R.string.prefs_lock_using_passcode);
760+
} else if (LOCK_DEVICE_CREDENTIALS.equals(lockValue)) {
761+
summary = getString(R.string.prefs_lock_using_device_credentials);
762+
} else {
763+
summary = getString(R.string.prefs_lock_none);
764+
}
765+
lockPreference.setSummary(summary);
766+
}
767+
802768
private void setupAutoUploadCategory(PreferenceScreen preferenceScreen) {
803769
final PreferenceCategory preferenceCategorySyncedFolders =
804770
(PreferenceCategory) findPreference("synced_folders_category");
@@ -853,8 +819,10 @@ private void enableLock(String lock) {
853819
}
854820

855821
private void changeLockSetting(String value) {
856-
lock.setValue(value);
857-
lock.setSummary(lock.getEntry());
822+
preferences.setLockPreference(value);
823+
if (lock != null) {
824+
updateLockSummary(lock, value);
825+
}
858826
DocumentsStorageProvider.notifyRootsChanged(this);
859827
}
860828

@@ -1067,6 +1035,19 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
10671035
// needed for to change status bar color
10681036
recreate();
10691037
}
1038+
} else if (requestCode == ExtendedSettingsActivityDialog.AppPasscode.getResultId() && data != null) {
1039+
String selectedLock = data.getStringExtra(ExtendedSettingsActivityDialog.AppPasscode.getKey());
1040+
if (selectedLock != null) {
1041+
String currentLock = preferences.getLockPreference();
1042+
if (!currentLock.equals(selectedLock)) {
1043+
if (LOCK_NONE.equals(currentLock)) {
1044+
enableLock(selectedLock);
1045+
} else {
1046+
pendingLock = selectedLock;
1047+
disableLock(currentLock);
1048+
}
1049+
}
1050+
}
10701051
} else if (requestCode == REQ_ALL_FILES_ACCESS) {
10711052
final PreferenceCategory preferenceCategorySync = (PreferenceCategory) findPreference("sync");
10721053
setupAllFilesAccessPreference(preferenceCategorySync);
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
/*
2+
* Nextcloud - Android Client
3+
*
4+
* SPDX-FileCopyrightText: 2026 Alper Ozturk <alper.ozturk@nextcloud.com>
5+
* SPDX-License-Identifier: AGPL-3.0-or-later
6+
*/
7+
8+
package com.owncloud.android.ui.dialog
9+
10+
import android.app.Dialog
11+
import android.os.Bundle
12+
import androidx.appcompat.app.AlertDialog
13+
import androidx.core.os.bundleOf
14+
import androidx.fragment.app.DialogFragment
15+
import androidx.fragment.app.setFragmentResult
16+
import com.google.android.material.button.MaterialButton
17+
import com.google.android.material.dialog.MaterialAlertDialogBuilder
18+
import com.nextcloud.client.di.Injectable
19+
import com.nextcloud.client.preferences.AppPreferences
20+
import com.nextcloud.utils.extensions.setVisibleIf
21+
import com.nextcloud.utils.mdm.MDMConfig
22+
import com.owncloud.android.R
23+
import com.owncloud.android.databinding.DialogAppPasscodeBinding
24+
import com.owncloud.android.ui.activity.SettingsActivity
25+
import com.owncloud.android.ui.model.ExtendedSettingsActivityDialog
26+
import com.owncloud.android.utils.DeviceCredentialUtils
27+
import com.owncloud.android.utils.theme.ViewThemeUtils
28+
import javax.inject.Inject
29+
30+
class AppPassCodeDialog :
31+
DialogFragment(),
32+
Injectable {
33+
34+
@Inject
35+
lateinit var preferences: AppPreferences
36+
37+
@Inject
38+
lateinit var viewThemeUtils: ViewThemeUtils
39+
40+
private lateinit var binding: DialogAppPasscodeBinding
41+
42+
private var currentSelection = SettingsActivity.LOCK_NONE
43+
44+
override fun onStart() {
45+
super.onStart()
46+
val alertDialog = dialog as AlertDialog
47+
48+
val positiveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE) as? MaterialButton
49+
positiveButton?.let {
50+
viewThemeUtils.material.colorMaterialButtonPrimaryTonal(it)
51+
}
52+
checkPositiveButtonActiveness()
53+
54+
val dismissable = arguments?.getBoolean(ARG_DISMISSABLE, true) ?: true
55+
isCancelable = dismissable
56+
}
57+
58+
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
59+
binding = DialogAppPasscodeBinding.inflate(layoutInflater)
60+
61+
currentSelection = preferences.lockPreference ?: SettingsActivity.LOCK_NONE
62+
63+
val passCodeEnabled = resources.getBoolean(R.bool.passcode_enabled)
64+
val deviceCredentialsEnabled = resources.getBoolean(R.bool.device_credentials_enabled)
65+
val enforceProtection = MDMConfig.enforceProtection(requireContext())
66+
val deviceCredentialsAvailable = DeviceCredentialUtils.areCredentialsAvailable(requireContext())
67+
val dismissable = arguments?.getBoolean(ARG_DISMISSABLE, true) ?: true
68+
69+
binding.lockPasscode.setVisibleIf(passCodeEnabled)
70+
binding.lockDeviceCredentials.setVisibleIf(deviceCredentialsEnabled && deviceCredentialsAvailable)
71+
binding.lockNone.setVisibleIf(!enforceProtection)
72+
73+
setupTheme()
74+
setCurrentSelection()
75+
setupListener()
76+
77+
val builder = MaterialAlertDialogBuilder(requireContext())
78+
.setView(binding.root)
79+
.setPositiveButton(R.string.common_ok) { _, _ ->
80+
applySelection()
81+
dismiss()
82+
}
83+
84+
if (!enforceProtection && dismissable) {
85+
builder.setNegativeButton(R.string.common_cancel) { _, _ ->
86+
dismiss()
87+
}
88+
}
89+
90+
builder.setCancelable(dismissable)
91+
92+
viewThemeUtils.dialog.colorMaterialAlertDialogBackground(requireContext(), builder)
93+
94+
return builder.create()
95+
}
96+
97+
private fun setupTheme() {
98+
viewThemeUtils.platform.apply {
99+
colorTextView(binding.dialogTitle)
100+
themeRadioButton(binding.lockPasscode)
101+
themeRadioButton(binding.lockDeviceCredentials)
102+
themeRadioButton(binding.lockNone)
103+
}
104+
}
105+
106+
private fun setCurrentSelection() {
107+
val radioGroup = binding.lockRadioGroup
108+
109+
when (currentSelection) {
110+
SettingsActivity.LOCK_PASSCODE -> radioGroup.check(R.id.lock_passcode)
111+
SettingsActivity.LOCK_DEVICE_CREDENTIALS -> radioGroup.check(R.id.lock_device_credentials)
112+
SettingsActivity.LOCK_NONE -> radioGroup.check(R.id.lock_none)
113+
}
114+
}
115+
116+
private fun setupListener() {
117+
binding.lockRadioGroup.setOnCheckedChangeListener { _, checkedId ->
118+
val selectedLock = when (checkedId) {
119+
R.id.lock_passcode -> SettingsActivity.LOCK_PASSCODE
120+
R.id.lock_device_credentials -> SettingsActivity.LOCK_DEVICE_CREDENTIALS
121+
R.id.lock_none -> SettingsActivity.LOCK_NONE
122+
else -> SettingsActivity.LOCK_NONE
123+
}
124+
125+
currentSelection = selectedLock
126+
checkPositiveButtonActiveness()
127+
}
128+
}
129+
130+
private fun checkPositiveButtonActiveness() {
131+
val positiveButton = (dialog as? AlertDialog)
132+
?.getButton(AlertDialog.BUTTON_POSITIVE) as? MaterialButton
133+
val enforceProtection = MDMConfig.enforceProtection(requireContext())
134+
if (enforceProtection) {
135+
positiveButton?.isEnabled = (binding.lockPasscode.isChecked || binding.lockDeviceCredentials.isChecked)
136+
}
137+
}
138+
139+
private fun applySelection() {
140+
val selectedLock = currentSelection
141+
142+
setFragmentResult(
143+
ExtendedSettingsActivityDialog.AppPasscode.key,
144+
bundleOf(ExtendedSettingsActivityDialog.AppPasscode.key to selectedLock)
145+
)
146+
}
147+
148+
companion object {
149+
private const val ARG_DISMISSABLE = "dismissable"
150+
151+
fun instance(dismissable: Boolean): AppPassCodeDialog = AppPassCodeDialog().apply {
152+
arguments = bundleOf(ARG_DISMISSABLE to dismissable)
153+
}
154+
}
155+
}

0 commit comments

Comments
 (0)