Skip to content

Commit 899eaef

Browse files
committed
[ + ] Make vault incognito and autofill
1 parent 336381c commit 899eaef

4 files changed

Lines changed: 176 additions & 8 deletions

File tree

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package com.kin.easynotes.presentation.components
2+
3+
import android.view.autofill.AutofillManager
4+
import androidx.compose.runtime.Composable
5+
import androidx.compose.runtime.remember
6+
import androidx.compose.ui.ExperimentalComposeUiApi
7+
import androidx.compose.ui.Modifier
8+
import androidx.compose.ui.autofill.Autofill
9+
import androidx.compose.ui.autofill.AutofillNode
10+
import androidx.compose.ui.autofill.AutofillType
11+
import androidx.compose.ui.focus.onFocusChanged
12+
import androidx.compose.ui.geometry.Rect
13+
import androidx.compose.ui.layout.boundsInWindow
14+
import androidx.compose.ui.layout.onGloballyPositioned
15+
import androidx.compose.ui.platform.LocalAutofill
16+
import androidx.compose.ui.platform.LocalAutofillTree
17+
import androidx.compose.ui.platform.LocalContext
18+
import androidx.compose.ui.platform.LocalView
19+
import kotlin.math.roundToInt
20+
21+
fun Modifier.connectNode(handler: AutoFillHandler): Modifier {
22+
return with(handler) { fillBounds() }
23+
}
24+
25+
fun Modifier.defaultFocusChangeAutoFill(handler: AutoFillHandler): Modifier {
26+
return this.then(
27+
Modifier.onFocusChanged {
28+
if (it.isFocused) {
29+
handler.request()
30+
} else {
31+
handler.cancel()
32+
}
33+
}
34+
)
35+
}
36+
37+
@OptIn(ExperimentalComposeUiApi::class)
38+
@Composable
39+
fun AutoFillRequestHandler(
40+
autofillTypes: List<AutofillType> = listOf(),
41+
onFill: (String) -> Unit,
42+
): AutoFillHandler {
43+
val view = LocalView.current
44+
val context = LocalContext.current
45+
var isFillRecently = remember { false }
46+
val autoFillNode = remember {
47+
AutofillNode(
48+
autofillTypes = autofillTypes,
49+
onFill = {
50+
isFillRecently = true
51+
onFill(it)
52+
}
53+
)
54+
}
55+
val autofill = LocalAutofill.current
56+
LocalAutofillTree.current += autoFillNode
57+
return remember {
58+
object : AutoFillHandler {
59+
val autofillManager = context.getSystemService(AutofillManager::class.java)
60+
override fun requestManual() {
61+
autofillManager.requestAutofill(
62+
view,
63+
autoFillNode.id,
64+
autoFillNode.boundingBox?.toAndroidRect() ?: error("BoundingBox is not provided yet")
65+
)
66+
}
67+
68+
override fun requestVerifyManual() {
69+
if (isFillRecently) {
70+
isFillRecently = false
71+
requestManual()
72+
}
73+
}
74+
75+
override val autoFill: Autofill?
76+
get() = autofill
77+
78+
override val autoFillNode: AutofillNode
79+
get() = autoFillNode
80+
81+
override fun request() {
82+
autofill?.requestAutofillForNode(autofillNode = autoFillNode)
83+
}
84+
85+
override fun cancel() {
86+
autofill?.cancelAutofillForNode(autofillNode = autoFillNode)
87+
}
88+
89+
override fun Modifier.fillBounds(): Modifier {
90+
return this.then(
91+
Modifier.onGloballyPositioned {
92+
autoFillNode.boundingBox = it.boundsInWindow()
93+
})
94+
}
95+
}
96+
}
97+
}
98+
99+
fun Rect.toAndroidRect(): android.graphics.Rect {
100+
return android.graphics.Rect(
101+
left.roundToInt(),
102+
top.roundToInt(),
103+
right.roundToInt(),
104+
bottom.roundToInt()
105+
)
106+
}
107+
108+
interface AutoFillHandler {
109+
@OptIn(ExperimentalComposeUiApi::class)
110+
val autoFill: Autofill?
111+
@OptIn(ExperimentalComposeUiApi::class)
112+
val autoFillNode: AutofillNode
113+
fun requestVerifyManual()
114+
fun requestManual()
115+
fun request()
116+
fun cancel()
117+
fun Modifier.fillBounds(): Modifier
118+
}

app/src/main/java/com/kin/easynotes/presentation/screens/edit/EditScreen.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import androidx.compose.runtime.Composable
4040
import androidx.compose.runtime.DisposableEffect
4141
import androidx.compose.runtime.rememberCoroutineScope
4242
import androidx.compose.ui.Alignment
43+
import androidx.compose.ui.ExperimentalComposeUiApi
4344
import androidx.compose.ui.Modifier
4445
import androidx.compose.ui.draw.clip
4546
import androidx.compose.ui.focus.onFocusChanged
@@ -291,7 +292,7 @@ fun MinimalisticMode(
291292

292293

293294

294-
@OptIn(ExperimentalFoundationApi::class)
295+
@OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class)
295296
@Composable
296297
fun EditScreen(viewModel: EditViewModel,settingsViewModel: SettingsViewModel, pagerState: PagerState,onClickBack: () -> Unit) {
297298

app/src/main/java/com/kin/easynotes/presentation/screens/edit/components/CustomTextField.kt

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,31 @@ import androidx.compose.runtime.Composable
1111
import androidx.compose.runtime.getValue
1212
import androidx.compose.runtime.mutableStateOf
1313
import androidx.compose.runtime.setValue
14+
import androidx.compose.ui.ExperimentalComposeUiApi
1415
import androidx.compose.ui.Modifier
1516
import androidx.compose.ui.draw.clip
1617
import androidx.compose.ui.graphics.Color
1718
import androidx.compose.ui.text.TextRange
18-
import androidx.compose.ui.text.TextStyle
1919
import androidx.compose.ui.text.font.FontFamily
20-
import androidx.compose.ui.text.font.FontStyle
20+
import androidx.compose.ui.text.input.KeyboardCapitalization
21+
import androidx.compose.ui.text.input.KeyboardType
2122
import androidx.compose.ui.text.input.PasswordVisualTransformation
2223
import androidx.compose.ui.text.input.TextFieldValue
2324
import androidx.compose.ui.text.input.VisualTransformation
2425
import androidx.compose.ui.unit.dp
2526
import androidx.compose.ui.unit.sp
27+
import androidx.compose.ui.autofill.AutofillNode
28+
import androidx.compose.ui.autofill.AutofillType
29+
import androidx.compose.ui.focus.onFocusChanged
30+
import androidx.compose.ui.layout.boundsInWindow
31+
import androidx.compose.ui.layout.onGloballyPositioned
32+
import androidx.compose.ui.platform.LocalAutofill
33+
import androidx.compose.ui.platform.LocalAutofillTree
34+
import com.kin.easynotes.presentation.components.AutoFillRequestHandler
35+
import com.kin.easynotes.presentation.components.connectNode
36+
import com.kin.easynotes.presentation.components.defaultFocusChangeAutoFill
2637

38+
@OptIn(ExperimentalComposeUiApi::class)
2739
@Composable
2840
fun CustomTextField(
2941
value: TextFieldValue,
@@ -34,24 +46,53 @@ fun CustomTextField(
3446
singleLine: Boolean = false,
3547
modifier: Modifier = Modifier,
3648
hideContent: Boolean = false,
37-
useMonoSpaceFont: Boolean = false
49+
useMonoSpaceFont: Boolean = false,
50+
autofillTypes: List<AutofillType>? = null
3851
) {
52+
val autoFillHandler = if (autofillTypes != null) AutoFillRequestHandler(autofillTypes = autofillTypes,
53+
onFill = {
54+
onValueChange(TextFieldValue(it))
55+
}
56+
) else null
3957

4058
val visualTransformation = if (hideContent) {
4159
PasswordVisualTransformation()
4260
} else {
4361
VisualTransformation.None
4462
}
63+
64+
// Determine if this is a password field based on autofill types or hideContent flag
65+
val isPasswordField = hideContent || (autofillTypes != null &&
66+
(autofillTypes.contains(AutofillType.Password) ||
67+
autofillTypes.contains(AutofillType.NewPassword)))
68+
4569

70+
4671
TextField(
4772
value = value,
48-
textStyle = if (useMonoSpaceFont) LocalTextStyle.current.copy(fontFamily = FontFamily.Monospace) else LocalTextStyle.current,
73+
textStyle = if (useMonoSpaceFont) LocalTextStyle.current.copy(fontFamily = FontFamily.Monospace) else LocalTextStyle.current,
4974
visualTransformation = visualTransformation,
50-
onValueChange = onValueChange,
75+
onValueChange = {
76+
onValueChange(it)
77+
if (it.text.isEmpty()) autoFillHandler?.requestVerifyManual()
78+
},
5179
interactionSource = interactionSource,
80+
keyboardOptions = androidx.compose.foundation.text.KeyboardOptions(
81+
autoCorrect = !isPasswordField,
82+
keyboardType = if (isPasswordField) KeyboardType.Password else KeyboardType.Text,
83+
capitalization = if (isPasswordField) KeyboardCapitalization.None else KeyboardCapitalization.Sentences,
84+
),
5285
modifier = modifier
5386
.fillMaxWidth()
54-
.clip(shape),
87+
.clip(shape)
88+
.then(
89+
if (autoFillHandler != null) {
90+
Modifier
91+
.connectNode(handler = autoFillHandler)
92+
.defaultFocusChangeAutoFill(handler = autoFillHandler)
93+
} else Modifier
94+
),
95+
5596
singleLine = singleLine,
5697
colors = TextFieldDefaults.colors(
5798
focusedContainerColor = Color.Transparent,

app/src/main/java/com/kin/easynotes/presentation/screens/settings/settings/Backup.kt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,11 @@ import androidx.compose.runtime.mutableStateOf
3030
import androidx.compose.runtime.remember
3131
import androidx.compose.runtime.setValue
3232
import androidx.compose.ui.Alignment
33+
import androidx.compose.ui.ExperimentalComposeUiApi
3334
import androidx.compose.ui.Modifier
35+
import androidx.compose.ui.autofill.AutofillType
3436
import androidx.compose.ui.platform.LocalContext
37+
import androidx.compose.ui.platform.LocalFocusManager
3538
import androidx.compose.ui.res.stringResource
3639
import androidx.compose.ui.text.input.TextFieldValue
3740
import androidx.compose.ui.unit.dp
@@ -186,9 +189,12 @@ fun currentDateTime(): String {
186189
return formattedDateTime
187190
}
188191

192+
@OptIn(ExperimentalComposeUiApi::class)
189193
@Composable
190194
fun PasswordPrompt(context: Context, text: String, settingsViewModel: SettingsViewModel, onExit: (TextFieldValue?) -> Unit, onBackup: () -> Unit = {}) {
191195
var password by remember { mutableStateOf(TextFieldValue("")) }
196+
val focusManager = LocalFocusManager.current
197+
192198
Dialog(
193199
onDismissRequest = { onExit(null) },
194200
properties = DialogProperties(usePlatformDefaultWidth = false),
@@ -223,7 +229,8 @@ fun PasswordPrompt(context: Context, text: String, settingsViewModel: SettingsVi
223229
hideContent = true,
224230
value = password,
225231
onValueChange = { password = it },
226-
placeholder = stringResource(id = R.string.password_prompt)
232+
placeholder = stringResource(id = R.string.password_prompt),
233+
autofillTypes = listOf(AutofillType.Password)
227234
)
228235
}
229236
Button(
@@ -233,6 +240,7 @@ fun PasswordPrompt(context: Context, text: String, settingsViewModel: SettingsVi
233240
.align(Alignment.End),
234241
onClick = {
235242
if (password.text.isNotBlank()) {
243+
focusManager.clearFocus() // Clear focus to dismiss autofill
236244
onExit(password)
237245
onBackup()
238246
} else {

0 commit comments

Comments
 (0)