Bladeren bron

working on autofill

minhaj 1 week geleden
bovenliggende
commit
15f710be7e

+ 1 - 0
.idea/gradle.xml

@@ -4,6 +4,7 @@
   <component name="GradleSettings">
     <option name="linkedExternalProjectsSettings">
       <GradleProjectSettings>
+        <option name="testRunner" value="CHOOSE_PER_TEST" />
         <option name="externalProjectPath" value="$PROJECT_DIR$" />
         <option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
         <option name="modules">

+ 27 - 0
.idea/inspectionProfiles/Project_Default.xml

@@ -1,6 +1,30 @@
 <component name="InspectionProjectProfileManager">
   <profile version="1.0">
     <option name="myName" value="Project Default" />
+    <inspection_tool class="ComposePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
+      <option name="composableFile" value="true" />
+    </inspection_tool>
+    <inspection_tool class="ComposePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
+      <option name="composableFile" value="true" />
+    </inspection_tool>
+    <inspection_tool class="ComposePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
+      <option name="composableFile" value="true" />
+    </inspection_tool>
+    <inspection_tool class="ComposePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
+      <option name="composableFile" value="true" />
+    </inspection_tool>
+    <inspection_tool class="GlancePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
+      <option name="composableFile" value="true" />
+    </inspection_tool>
+    <inspection_tool class="GlancePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
+      <option name="composableFile" value="true" />
+    </inspection_tool>
+    <inspection_tool class="GlancePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
+      <option name="composableFile" value="true" />
+    </inspection_tool>
+    <inspection_tool class="GlancePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
+      <option name="composableFile" value="true" />
+    </inspection_tool>
     <inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
       <option name="composableFile" value="true" />
       <option name="previewFile" value="true" />
@@ -9,6 +33,9 @@
       <option name="composableFile" value="true" />
       <option name="previewFile" value="true" />
     </inspection_tool>
+    <inspection_tool class="PreviewDeviceShouldUseNewSpec" enabled="true" level="WEAK WARNING" enabled_by_default="true">
+      <option name="composableFile" value="true" />
+    </inspection_tool>
     <inspection_tool class="PreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
       <option name="composableFile" value="true" />
       <option name="previewFile" value="true" />

+ 2 - 1
app/build.gradle.kts

@@ -11,7 +11,7 @@ android {
 
     defaultConfig {
         applicationId = "com.fastest.pass"
-        minSdk = 21
+        minSdk = 26
         targetSdk = 34
         versionCode = 1
         versionName = "1.0"
@@ -78,6 +78,7 @@ dependencies {
     implementation(libs.androidx.fragment)
     implementation(libs.accompanist.pager)
     implementation(libs.accompanist.pager.indicators)
+//    implementation(libs.auto.fill)
 
     implementation(libs.ccp)
 

+ 14 - 0
app/src/main/AndroidManifest.xml

@@ -5,6 +5,7 @@
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
     <uses-permission android:name="android.permission.INTERNET" />
 
+
     <application
         android:name="com.fastest.pass.app.App"
         android:allowBackup="true"
@@ -29,6 +30,19 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
+
+        <service
+            android:name=".autofill.FastestPassAutofillService"
+            android:exported="true"
+            android:permission="android.permission.BIND_AUTOFILL_SERVICE">
+            <intent-filter>
+                <action android:name="android.service.autofill.AutofillService" />
+            </intent-filter>
+            <meta-data
+                android:name="android.autofill"
+                android:resource="@xml/autofill_service_configuration" />
+        </service>
+
     </application>
 
 </manifest>

+ 180 - 0
app/src/main/java/com/fastest/pass/autofill/FastestPassAutofillService.kt

@@ -0,0 +1,180 @@
+package com.fastest.pass.autofill
+
+import android.app.assist.AssistStructure
+import android.service.autofill.AutofillService
+import android.service.autofill.Dataset
+import android.service.autofill.FillCallback
+import android.service.autofill.FillContext
+import android.service.autofill.FillRequest
+import android.service.autofill.FillResponse
+import android.view.autofill.AutofillId
+import android.view.autofill.AutofillValue
+import android.widget.RemoteViews
+import com.fastest.pass.home.domain.model.AddPassword
+import com.fastest.pass.sharedpref.CredentialManager
+
+class FastestPassAutofillService : AutofillService() {
+
+    override fun onFillRequest(
+        request: FillRequest,
+        cancellationSignal: android.os.CancellationSignal,
+        callback: FillCallback
+    ) {
+        // Extract structure
+        val context: List<FillContext> = request.fillContexts
+        val structure: AssistStructure = context.last().structure
+
+        val packageName = getRequestingPackageName(structure)
+
+        if (packageName == null) {
+            callback.onFailure("Unable to determine requesting package")
+            return
+        }
+
+        // Fetch credentials matching the package name or domain
+       /* val data = getCredentials().filter {
+            it.url.contains(packageName)
+        }*/
+        val data = getCredentials()
+
+        if (data.isEmpty()) {
+            callback.onFailure("No matching credentials found")
+            return
+        }
+
+        val fillResponseBuilder = FillResponse.Builder()
+        for (i in 0 until structure.windowNodeCount) {
+            val windowNode = structure.getWindowNodeAt(i)
+            val viewNode = windowNode.rootViewNode
+         parseViewNode(viewNode, data, fillResponseBuilder)
+
+        }
+
+    callback.onSuccess(fillResponseBuilder.build())
+    }
+
+    override fun onSaveRequest(
+        request: android.service.autofill.SaveRequest,
+        callback: android.service.autofill.SaveCallback
+    ) {
+      /*  val credentialManager = CredentialManager(applicationContext)
+
+        // Extracting the FillContexts from the SaveRequest
+        val fillContexts = request.fillContexts
+
+        // For each field in the SaveRequest, extract the autofill values (email and password)
+        val credentials = mutableListOf<AddPassword>()
+
+        for (context in fillContexts) {
+            val structure = context.structure
+
+            // Loop through the views in the structure and extract the autofill values
+            for (i in 0 until structure.windowNodeCount) {
+                val windowNode = structure.getWindowNodeAt(i)
+                val rootNode = windowNode.rootViewNode
+
+                // Extract the values from the autofill fields
+                val title = extractAutofillValue(rootNode, "personFirstName") ?: ""
+                val url = extractAutofillValue(rootNode, "personLastName") ?: ""
+                val username = extractAutofillValue(rootNode, "username") ?: ""
+                val password = extractAutofillValue(rootNode, "password") ?: ""
+                val notes = extractAutofillValue(rootNode, "addressStreet") ?: ""
+
+                // Create a new Credential object and add it to the list
+                val credential = AddPassword(title, url, username, password, notes)
+                credentials.add(credential)
+            }
+        }
+
+        // Save the credentials in SharedPreferences
+        credentialManager.saveCredentials(credentials)*/
+
+        callback.onSuccess()
+    }
+
+    private fun extractAutofillValue(node: AssistStructure.ViewNode, hint: String): String? {
+        val autofillHints = node.autofillHints
+
+        // Check if the hint matches the autofill field (e.g., "emailAddress" or "password")
+        if (autofillHints != null && autofillHints.contains(hint)) {
+            val value = node.autofillValue
+            return if (value != null && value.isText) value.textValue.toString() else null
+        }
+
+        // Recursively check child nodes if no value is found
+        for (i in 0 until node.childCount) {
+            val childNode = node.getChildAt(i)
+            val result = extractAutofillValue(childNode, hint)
+            if (result != null) return result
+        }
+
+        return null
+    }
+
+    private fun getCredentials(): List<AddPassword> {
+        val credentialManager = CredentialManager(applicationContext)
+        return credentialManager.getCredentials()
+    }
+
+    private fun parseViewNode(
+        node: AssistStructure.ViewNode,
+        credentials: List<AddPassword>,
+        fillResponseBuilder: FillResponse.Builder
+    )  {
+        val autofillHints = node.autofillHints
+        if (autofillHints != null) {
+            // Collect Autofill IDs for username and password fields
+            val emailAddress: AutofillId? =
+                if ("emailAddress" in autofillHints) node.autofillId else null
+            val username: AutofillId? =
+                if ("username" in autofillHints) node.autofillId else null
+            val passwordId: AutofillId? = if ("password" in autofillHints) node.autofillId else null
+
+            if (username != null || emailAddress != null || passwordId != null) {
+                credentials.forEach { credential ->
+                    val presentation =
+                        RemoteViews(packageName, android.R.layout.simple_list_item_1).apply {
+                            setTextViewText(
+                                android.R.id.text1,
+                                "${credential.username} @ ${credential.url}"
+                            )
+                        }
+                    val datasetBuilder = Dataset.Builder()
+                    emailAddress?.let {
+                        datasetBuilder.setValue(
+                            it,
+                            AutofillValue.forText(credential.username),
+                            presentation
+                        )
+                    }
+                    passwordId?.let {
+                        datasetBuilder.setValue(
+                            it,
+                            AutofillValue.forText(credential.password),
+                            presentation
+                        )
+                    }
+
+                    username?.let {
+                        datasetBuilder.setValue(
+                            it,
+                            AutofillValue.forText(credential.username),
+                            presentation
+                        )
+                    }
+
+                    fillResponseBuilder.addDataset(datasetBuilder.build())
+                }
+            }
+        }
+
+        // Recursively parse child nodes
+        for (i in 0 until node.childCount) {
+           parseViewNode(node.getChildAt(i), credentials, fillResponseBuilder)
+        }
+    }
+
+    private fun getRequestingPackageName(structure: AssistStructure): String? {
+        return structure.activityComponent?.packageName
+    }
+}

+ 9 - 0
app/src/main/java/com/fastest/pass/home/domain/model/AddPassword.kt

@@ -0,0 +1,9 @@
+package com.fastest.pass.home.domain.model
+
+data class AddPassword(
+    var title:String,
+    var url:String,
+    var username:String,
+    var password:String,
+    var notes:String,
+)

+ 140 - 44
app/src/main/java/com/fastest/pass/home/presentation/ui/components/AddPasswordFormScreen.kt

@@ -1,22 +1,22 @@
+@file:OptIn(ExperimentalComposeUiApi::class)
+
 package com.fastest.pass.home.presentation.ui.components
 
+import android.util.Log
 import androidx.compose.foundation.Image
 import androidx.compose.foundation.background
 import androidx.compose.foundation.border
 import androidx.compose.foundation.clickable
-import androidx.compose.foundation.gestures.detectTapGestures
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.BoxScope
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.ColumnScope
-import androidx.compose.foundation.layout.ExperimentalLayoutApi
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.defaultMinSize
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.imeNestedScroll
 import androidx.compose.foundation.layout.imePadding
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
@@ -41,12 +41,13 @@ import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
+import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.autofill.AutofillType
 import androidx.compose.ui.draw.scale
 import androidx.compose.ui.focus.FocusManager
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.ColorFilter
-import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.platform.LocalFocusManager
 import androidx.compose.ui.platform.LocalSoftwareKeyboardController
 import androidx.compose.ui.platform.SoftwareKeyboardController
@@ -61,12 +62,20 @@ import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
 import com.fastest.pass.R
+import com.fastest.pass.home.domain.model.AddPassword
+import com.fastest.pass.login.presentation.ui.components.autoFill
 
 @Composable
-fun AddPasswordFormScreen() {
+fun AddPasswordFormScreen(onSave: (Any?) -> Unit) {
     val keyboardController = LocalSoftwareKeyboardController.current
     val focusManager = LocalFocusManager.current
 
+    var titleText by remember { mutableStateOf("") }
+    var urlText by remember { mutableStateOf("") }
+    var usernameText by remember { mutableStateOf("") }
+    var passwordText by remember { mutableStateOf("") }
+    var notesText by remember { mutableStateOf("") }
+
     Box(
         modifier = Modifier
             .fillMaxSize()
@@ -75,7 +84,17 @@ fun AddPasswordFormScreen() {
             .background(Color.Transparent)
             .imePadding()
     ) {
-        SaveButtonAPWFS(buttonText = R.string.save)
+        SaveButtonAPWFS(buttonText = R.string.save) {
+            onSave.invoke(
+                AddPassword(
+                    titleText,
+                    urlText,
+                    usernameText,
+                    passwordText,
+                    notesText
+                )
+            )
+        }
 
         Column(
             modifier = Modifier
@@ -86,15 +105,49 @@ fun AddPasswordFormScreen() {
                 .verticalScroll(rememberScrollState())
         ) {
             Spacer(modifier = Modifier.height(20.dp))
-            TitleTextFieldAPS(keyboardController = keyboardController, focusManager = focusManager)
+            TitleTextFieldAPS(
+                titleText,
+                keyboardController = keyboardController,
+                focusManager = focusManager
+            ) {
+                titleText = it
+            }
             Spacer(modifier = Modifier.height(20.dp))
-            UrlTextFieldAPS(keyboardController = keyboardController, focusManager = focusManager)
+            UrlTextFieldAPS(
+                urlText,
+                keyboardController = keyboardController, focusManager = focusManager
+            ) {
+                urlText = it
+            }
             Spacer(modifier = Modifier.height(20.dp))
-            UsernameTextFieldAPS(keyboardController = keyboardController, focusManager = focusManager)
+            UsernameTextFieldAPS(
+                usernameText,
+                keyboardController = keyboardController,
+                focusManager = focusManager,
+                onUsernameText = {
+                    usernameText = it
+                },
+                onAutofillSelection = {
+                    titleText = "testing"
+                    urlText = "---testingggg"
+                }
+            )
             Spacer(modifier = Modifier.height(20.dp))
-            PasswordTextFieldAPS(keyboardController = keyboardController, focusManager = focusManager)
+            PasswordTextFieldAPS(
+                passwordText,
+                keyboardController = keyboardController,
+                focusManager = focusManager
+            ) {
+                passwordText = it
+            }
             Spacer(modifier = Modifier.height(20.dp))
-            NotesTextFieldAPS(keyboardController = keyboardController, focusManager = focusManager)
+            NotesTextFieldAPS(
+                notesText,
+                keyboardController = keyboardController,
+                focusManager = focusManager
+            ) {
+                notesText = it
+            }
             Spacer(modifier = Modifier.height(25.dp))
             OptionsText()
             Spacer(modifier = Modifier.height(25.dp))
@@ -108,18 +161,24 @@ fun AddPasswordFormScreen() {
 
 @Composable
 fun ColumnScope.TitleTextFieldAPS(
+    titleText: String,
     keyboardController: SoftwareKeyboardController?,
-    focusManager: FocusManager
+    focusManager: FocusManager,
+    onTitleText: (String) -> Unit
 ) {
-    var titleText by remember { mutableStateOf("") }
-
     TextField(
         value = titleText,
         onValueChange = {
-            titleText = it
+            onTitleText.invoke(it)
         },
         textStyle = MaterialTheme.typography.displayMedium,
         modifier = Modifier
+            .autoFill(
+                autofillTypes = listOf(AutofillType.PersonFirstName),
+                onFill = {
+                    onTitleText.invoke(it)
+                }
+            )
             .align(Alignment.Start)
             .fillMaxWidth()
             .defaultMinSize(minHeight = 60.dp)
@@ -139,7 +198,8 @@ fun ColumnScope.TitleTextFieldAPS(
 //            )
 //        },
         label = {
-            Text(text = stringResource(R.string.title),
+            Text(
+                text = stringResource(R.string.title),
                 style = MaterialTheme.typography.titleSmall.copy(
                     color = colorResource(id = R.color.gray_text)
                 )
@@ -177,18 +237,25 @@ fun ColumnScope.TitleTextFieldAPS(
 
 @Composable
 fun ColumnScope.UrlTextFieldAPS(
+    urlText: String,
     keyboardController: SoftwareKeyboardController?,
-    focusManager: FocusManager
+    focusManager: FocusManager,
+    onUrlText: (String) -> Unit
 ) {
-    var urlText by remember { mutableStateOf("") }
-
     TextField(
         value = urlText,
         onValueChange = {
-            urlText = it
+            onUrlText.invoke(it)
         },
         textStyle = MaterialTheme.typography.displayMedium,
         modifier = Modifier
+            .autoFill(
+                autofillTypes = listOf(AutofillType.PersonLastName),
+                onFill = {
+                    onUrlText.invoke(it)
+                    Log.d("asdf", "this value was received from autofill: $it")
+                }
+            )
             .align(Alignment.Start)
             .fillMaxWidth()
             .defaultMinSize(minHeight = 60.dp)
@@ -208,7 +275,8 @@ fun ColumnScope.UrlTextFieldAPS(
 //            )
 //        },
         label = {
-            Text(text = stringResource(R.string.url),
+            Text(
+                text = stringResource(R.string.url),
                 style = MaterialTheme.typography.titleSmall.copy(
                     color = colorResource(id = R.color.gray_text)
                 )
@@ -225,9 +293,9 @@ fun ColumnScope.UrlTextFieldAPS(
         trailingIcon = {
             IconButton(
                 onClick = {
-                    urlText = ""
+                    onUrlText.invoke("")
                 },
-                ) {
+            ) {
                 if (urlText.isNotEmpty()) {
                     Image(
                         painter = painterResource(id = R.drawable.clear_button),
@@ -261,18 +329,27 @@ fun ColumnScope.UrlTextFieldAPS(
 
 @Composable
 fun ColumnScope.UsernameTextFieldAPS(
+    usernameText: String,
     keyboardController: SoftwareKeyboardController?,
-    focusManager: FocusManager
+    focusManager: FocusManager,
+    onUsernameText: (String) -> Unit,
+    onAutofillSelection: (String) -> Unit
 ) {
-    var usernameText by remember { mutableStateOf("") }
-
     TextField(
         value = usernameText,
         onValueChange = {
-            usernameText = it
+            onUsernameText.invoke(it)
         },
         textStyle = MaterialTheme.typography.displayMedium,
         modifier = Modifier
+            .autoFill(
+                autofillTypes = listOf(AutofillType.Username),
+                onFill = {
+                    onUsernameText.invoke(it)
+                    onAutofillSelection.invoke(it)
+                    Log.d("asdf", "this value was received from autofill: $it")
+                }
+            )
             .align(Alignment.Start)
             .fillMaxWidth()
             .defaultMinSize(minHeight = 60.dp)
@@ -292,7 +369,8 @@ fun ColumnScope.UsernameTextFieldAPS(
 //            )
 //        },
         label = {
-            Text(text = stringResource(R.string.username),
+            Text(
+                text = stringResource(R.string.username),
                 style = MaterialTheme.typography.titleSmall.copy(
                     color = colorResource(id = R.color.gray_text)
                 )
@@ -330,19 +408,27 @@ fun ColumnScope.UsernameTextFieldAPS(
 
 @Composable
 fun ColumnScope.PasswordTextFieldAPS(
+    passwordText: String,
     keyboardController: SoftwareKeyboardController?,
-    focusManager: FocusManager
+    focusManager: FocusManager,
+    onPasswordText: (String) -> Unit
 ) {
-    var passwordText by remember { mutableStateOf("") }
     var passwordVisible by remember { mutableStateOf(false) }
 
     TextField(
         value = passwordText,
         onValueChange = {
-            passwordText = it
+            onPasswordText.invoke(it)
         },
         textStyle = MaterialTheme.typography.displayMedium,
         modifier = Modifier
+            .autoFill(
+                autofillTypes = listOf(AutofillType.Password),
+                onFill = {
+                    onPasswordText.invoke(it)
+                    Log.d("asdf", "this value was received from autofill: $it")
+                }
+            )
             .align(Alignment.Start)
             .fillMaxWidth()
             .defaultMinSize(minHeight = 60.dp)
@@ -362,7 +448,8 @@ fun ColumnScope.PasswordTextFieldAPS(
 //            )
 //        },
         label = {
-            Text(text = stringResource(R.string.password),
+            Text(
+                text = stringResource(R.string.password),
                 style = MaterialTheme.typography.titleSmall.copy(
                     color = colorResource(id = R.color.gray_text)
                 )
@@ -405,8 +492,7 @@ fun ColumnScope.PasswordTextFieldAPS(
                         contentDescription = description,
                         modifier = Modifier.size(24.dp)
                     )
-                }
-                else {
+                } else {
                     Image(
                         painter = painterResource(id = R.drawable.eye_slash3x),
                         contentDescription = description,
@@ -424,18 +510,26 @@ fun ColumnScope.PasswordTextFieldAPS(
 
 @Composable
 fun ColumnScope.NotesTextFieldAPS(
+    notesText: String,
     keyboardController: SoftwareKeyboardController?,
-    focusManager: FocusManager
+    focusManager: FocusManager,
+    onNoteText: (String) -> Unit
 ) {
-    var notesText by remember { mutableStateOf("") }
 
     TextField(
         value = notesText,
         onValueChange = {
-            notesText = it
+            onNoteText.invoke(it)
         },
         textStyle = MaterialTheme.typography.displayMedium,
         modifier = Modifier
+            .autoFill(
+                autofillTypes = listOf(AutofillType.AddressStreet),
+                onFill = {
+                    onNoteText.invoke(it)
+                    Log.d("asdf", "this value was received from autofill: $it")
+                }
+            )
             .align(Alignment.Start)
             .fillMaxWidth()
             .defaultMinSize(minHeight = 60.dp)
@@ -445,8 +539,7 @@ fun ColumnScope.NotesTextFieldAPS(
                 color = colorResource(id = R.color.gray_border_textfield),
                 shape = RoundedCornerShape(16.dp)
             )
-            .background(color = colorResource(id = R.color.transparent))
-        ,
+            .background(color = colorResource(id = R.color.transparent)),
         shape = RoundedCornerShape(16.dp),
 //        placeholder = {
 //            Text(
@@ -456,7 +549,8 @@ fun ColumnScope.NotesTextFieldAPS(
 //            )
 //        },
         label = {
-            Text(text = stringResource(R.string.notes),
+            Text(
+                text = stringResource(R.string.notes),
                 style = MaterialTheme.typography.titleSmall.copy(
                     color = colorResource(id = R.color.gray_text)
                 )
@@ -558,7 +652,7 @@ fun ColumnScope.SaveButtonAPFS(buttonText: Int) {
             .background(colorResource(id = R.color.transparent))
             .fillMaxWidth()
             .height(60.dp)
-            .clickable() { },
+            .clickable { },
         onClick = {},
         shape = RoundedCornerShape(15.dp),
 //            border = BorderStroke(25.dp, colorResource(id = R.color.black)),
@@ -580,15 +674,17 @@ fun ColumnScope.SaveButtonAPFS(buttonText: Int) {
 }
 
 @Composable
-fun BoxScope.SaveButtonAPWFS(buttonText: Int) {
+fun BoxScope.SaveButtonAPWFS(buttonText: Int, onClick: () -> Unit) {
     Button(
         modifier = Modifier
             .background(colorResource(id = R.color.transparent))
             .fillMaxWidth()
             .height(60.dp)
             .align(Alignment.BottomCenter)
-            .clickable() { },
-        onClick = {},
+            .clickable { },
+        onClick = {
+            onClick.invoke()
+        },
         shape = RoundedCornerShape(15.dp),
 //            border = BorderStroke(25.dp, colorResource(id = R.color.black)),
         colors = ButtonDefaults.buttonColors(

+ 5 - 2
app/src/main/java/com/fastest/pass/home/presentation/ui/components/NewItemFormScreen.kt

@@ -46,7 +46,8 @@ fun NewItemFormScreen(
     clickType: (ClickTypeNewItemForm) -> Unit,
     onSearchTextChanged: (String) -> Unit,
     onCountryList: (List<CountryInfo>, String) -> Unit,
-    countryListDisplayed: List<CountryInfo>
+    countryListDisplayed: List<CountryInfo>,
+    onSave:(Any?)->Unit
 ) {
     val keyboardController = LocalSoftwareKeyboardController.current
     val focusManager = LocalFocusManager.current
@@ -81,7 +82,9 @@ fun NewItemFormScreen(
 
         when (screenNameType) {
             ClickTypeAddNewItem.Password -> {
-                AddPasswordFormScreen()
+                AddPasswordFormScreen(onSave = { any ->
+                    onSave.invoke(any)
+                })
             }
             ClickTypeAddNewItem.Bank -> {
                 AddBankAccountFormScreen()

+ 18 - 1
app/src/main/java/com/fastest/pass/home/presentation/ui/fragment/NewItemFormFragment.kt

@@ -18,6 +18,7 @@ import androidx.fragment.app.activityViewModels
 import androidx.fragment.app.viewModels
 import com.fastest.pass.app.BaseFragment
 import com.fastest.pass.R
+import com.fastest.pass.home.domain.model.AddPassword
 import com.fastest.pass.home.presentation.ui.components.ClickTypeAddNewItem
 import com.fastest.pass.home.presentation.ui.components.ClickTypeNewItemForm
 import com.fastest.pass.home.presentation.ui.components.NewItemFormScreen
@@ -25,6 +26,7 @@ import com.fastest.pass.home.presentation.viewmodels.AddNewItemsViewModel
 import com.fastest.pass.home.presentation.viewmodels.NewItemFormViewModel
 import com.fastest.pass.home.utils.NewItemFormNavigation
 import com.fastest.pass.home.utils.NewItemFormRoute
+import com.fastest.pass.sharedpref.CredentialManager
 import com.fastest.pass.ui.theme.FastestPassTheme
 import dagger.hilt.android.AndroidEntryPoint
 import javax.inject.Inject
@@ -109,7 +111,22 @@ class NewItemFormFragment : BaseFragment() {
                                 onCountryList = { list, text ->
                                     viewmodel.getCountries(text =  text, countryList = list)
                                 },
-                                countryListDisplayed = countryList.value
+                                countryListDisplayed = countryList.value,
+                                onSave = { any ->
+                                    if(any is AddPassword){
+                                        val credentialManager = CredentialManager(requireContext())
+                                        val credentials = credentialManager.getCredentials().toMutableList()
+
+                                        // Add new credentials (example values)
+                                        credentials.add(AddPassword(any.title,any.url,
+                                            any.username,any.password,any.notes))
+
+                                        // Save updated credentials
+                                        credentialManager.saveCredentials(credentials)
+                                        // save pass to database
+                                        viewmodel.navigateTo(NewItemFormRoute.GoBackAddNewItemsScreen)
+                                    }
+                                }
                             )
                         }
                     }

+ 5 - 0
app/src/main/java/com/fastest/pass/login/presentation/ui/LoginFragment.kt

@@ -1,17 +1,21 @@
 package com.fastest.pass.login.presentation.ui
 
+import android.content.Context
 import android.os.Bundle
 import android.util.Log
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
+import android.view.autofill.AutofillManager
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.padding
 import androidx.compose.material.Scaffold
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.onFocusChanged
 import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.colorResource
 import androidx.fragment.app.viewModels
 import com.fastest.pass.app.BaseFragment
@@ -97,4 +101,5 @@ class LoginFragment : BaseFragment() {
             }
         }
     }
+
 }

+ 83 - 28
app/src/main/java/com/fastest/pass/login/presentation/ui/components/LoginScreen.kt

@@ -1,3 +1,5 @@
+@file:OptIn(ExperimentalComposeUiApi::class)
+
 package com.fastest.pass.login.presentation.ui.components
 
 import android.util.Log
@@ -25,7 +27,6 @@ import androidx.compose.foundation.text.KeyboardActions
 import androidx.compose.foundation.text.KeyboardOptions
 import androidx.compose.material3.Button
 import androidx.compose.material3.ButtonDefaults
-import androidx.compose.material3.CircularProgressIndicator
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
 import androidx.compose.material3.MaterialTheme
@@ -37,18 +38,26 @@ import androidx.compose.material3.TextFieldDefaults
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableFloatStateOf
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
+import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.autofill.AutofillNode
+import androidx.compose.ui.autofill.AutofillType
+import androidx.compose.ui.composed
 import androidx.compose.ui.draw.alpha
 import androidx.compose.ui.draw.clip
 import androidx.compose.ui.focus.FocusManager
+import androidx.compose.ui.focus.onFocusChanged
 import androidx.compose.ui.graphics.ColorFilter
 import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.layout.boundsInWindow
+import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.platform.LocalAutofill
+import androidx.compose.ui.platform.LocalAutofillTree
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalFocusManager
 import androidx.compose.ui.platform.LocalSoftwareKeyboardController
@@ -66,9 +75,9 @@ import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
 import com.fastest.pass.R
 import com.fastest.views.ShowCustomSnackBar
-import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
 
+
 enum class ClickType {
     SIGNUP_CLICK,
     FORGOT_PASSWORD_CLICK,
@@ -89,6 +98,13 @@ fun LoginScreen(
     var passwordLogin by remember { mutableStateOf("") }
     val snackBarStateRed = remember { SnackbarHostState() }
 
+
+    LaunchedEffect(true) {
+        emailLogin = "eng.asix@gmail.com"
+        passwordLogin = "password"
+    }
+
+
     Box(
         modifier = Modifier
             .background(colorResource(id = R.color.blue_login))
@@ -126,27 +142,29 @@ fun LoginScreen(
             ) {
 
                 ShowWelcomeText(R.string.login_to_continue)
-                LoginTextField(keyboardController, focusManager, onEmailText = {
+                LoginTextField(
+                    emailLogin,
+                    keyboardController, focusManager, onEmailText = {
                     emailLogin = it
                 })
                 Spacer(modifier = Modifier.height(20.dp))
-                PasswordTextField(keyboardController, focusManager, onPasswordText = {
+                PasswordTextField(passwordLogin,keyboardController, focusManager, onPasswordText = {
                     passwordLogin = it
                 })
                 Spacer(modifier = Modifier.height(25.dp))
                 LoginButton(
                     buttonText = R.string.login,
                     clickType = { clickType ->
-                    clickType(clickType)
-                },
+                        clickType(clickType)
+                    },
                     email = emailLogin,
                     password = passwordLogin,
                     onLoginClickCredentials = { email, password ->
-                    onLoginClickCredentials.invoke(email, password)
-                },
+                        onLoginClickCredentials.invoke(email, password)
+                    },
                     snackBarStateRed = snackBarStateRed
                 )
-                ForgotPasswordText() { clickType ->
+                ForgotPasswordText { clickType ->
                     clickType(clickType)
                 }
                 CreateAccountText { clickType ->
@@ -226,22 +244,55 @@ fun ColumnScope.ShowWelcomeText(
     )
 }
 
+@OptIn(ExperimentalComposeUiApi::class)
+fun Modifier.autoFill(
+    autofillTypes: List<AutofillType>,
+    onFill: ((String) -> Unit),
+) = composed {
+    val autofill = LocalAutofill.current
+    val autofillNode = AutofillNode(onFill = onFill, autofillTypes = autofillTypes)
+    LocalAutofillTree.current += autofillNode
+
+    this
+        .onGloballyPositioned {
+            autofillNode.boundingBox = it.boundsInWindow()
+        }
+        .onFocusChanged { focusState ->
+            autofill?.run {
+                if (focusState.isFocused) {
+                    requestAutofillForNode(autofillNode)
+                } else {
+                    cancelAutofillForNode(autofillNode)
+                }
+            }
+        }
+}
+
 @Composable
 fun ColumnScope.LoginTextField(
+    emailText:String,
     keyboardController: SoftwareKeyboardController?,
     focusManager: FocusManager,
     onEmailText: (String) -> Unit
 ) {
-    var emailText by remember { mutableStateOf("") }
+
+
 
     TextField(
         value = emailText,
         onValueChange = {
-            emailText = it
-            onEmailText.invoke(emailText)
+            onEmailText.invoke(it)
         },
+
         textStyle = MaterialTheme.typography.displayMedium,
         modifier = Modifier
+            .autoFill(
+                autofillTypes = listOf(AutofillType.EmailAddress),
+                onFill = {
+                    onEmailText.invoke(it)
+                    Log.d("asdf", "this value was received from autofill: $it")
+                }
+            )
             .padding(start = 30.dp, end = 30.dp, top = 50.dp)
             .align(Alignment.Start)
             .fillMaxWidth()
@@ -283,7 +334,7 @@ fun ColumnScope.LoginTextField(
             disabledIndicatorColor = colorResource(id = R.color.transparent),
             unfocusedIndicatorColor = colorResource(id = R.color.transparent),
             cursorColor = colorResource(id = R.color.gray_splash),
-            ),
+        ),
         keyboardOptions = KeyboardOptions(
             keyboardType = KeyboardType.Email,
             imeAction = ImeAction.Done
@@ -297,23 +348,28 @@ fun ColumnScope.LoginTextField(
     )
 }
 
+
 @Composable
 fun ColumnScope.PasswordTextField(
+    passwordText:String,
     keyboardController: SoftwareKeyboardController?,
     focusManager: FocusManager,
     onPasswordText: (String) -> Unit
 ) {
-    var passwordText by remember { mutableStateOf("") }
     var passwordVisible by remember { mutableStateOf(false) }
 
     TextField(
         value = passwordText,
         onValueChange = {
-            passwordText = it
-            onPasswordText.invoke(passwordText)
+            onPasswordText.invoke(it)
         },
         textStyle = MaterialTheme.typography.displayMedium,
         modifier = Modifier
+            .autoFill(autofillTypes = listOf(AutofillType.Password),
+                onFill = {
+                    onPasswordText.invoke(it)
+                    Log.d("asdf", "this value was received from autofill: $it")
+                })
             .padding(start = 30.dp, end = 30.dp)
             .align(Alignment.Start)
             .fillMaxWidth()
@@ -324,14 +380,14 @@ fun ColumnScope.PasswordTextField(
                 shape = RoundedCornerShape(16.dp)
             )
             .background(color = colorResource(id = R.color.transparent)),
-                shape = RoundedCornerShape(16.dp),
-                placeholder = {
-                    Text(
-                        text = stringResource(id = R.string.enter_password),
-                        color = colorResource(id = R.color.gray_splash),
-                        style = MaterialTheme.typography.displayMedium
-                        )
-                },
+        shape = RoundedCornerShape(16.dp),
+        placeholder = {
+            Text(
+                text = stringResource(id = R.string.enter_password),
+                color = colorResource(id = R.color.gray_splash),
+                style = MaterialTheme.typography.displayMedium
+            )
+        },
 //        label = {
 //            Text(text = context.getString(R.string.password),
 //                style = MaterialTheme.typography.customTypography.bodyLarge
@@ -383,8 +439,7 @@ fun ColumnScope.PasswordTextField(
                         contentDescription = description,
                         modifier = Modifier.size(24.dp)
                     )
-                }
-                else {
+                } else {
                     Image(
                         painter = painterResource(id = R.drawable.eye_slash3x),
                         contentDescription = description,
@@ -418,7 +473,7 @@ fun ColumnScope.LoginButton(
             .background(colorResource(id = R.color.transparent))
             .fillMaxWidth()
             .height(60.dp)
-            .clickable() { },
+            .clickable { },
         onClick = {
             Log.d("test_api_login", "ClickType.LOGIN_CLICK")
 //            clickType.invoke(ClickType.LOGIN_CLICK)

+ 31 - 0
app/src/main/java/com/fastest/pass/sharedpref/CredentialManager.kt

@@ -0,0 +1,31 @@
+package com.fastest.pass.sharedpref
+
+import android.content.Context
+import android.content.SharedPreferences
+import com.fastest.pass.home.domain.model.AddPassword
+import com.google.gson.Gson
+import com.google.gson.reflect.TypeToken
+
+class CredentialManager(context: Context) {
+
+    private val sharedPreferences: SharedPreferences =
+        context.getSharedPreferences("AutofillPrefs", Context.MODE_PRIVATE)
+
+    private val gson = Gson()
+
+    fun saveCredentials(credentials: List<AddPassword>) {
+        val editor = sharedPreferences.edit()
+        val json = gson.toJson(credentials)
+        editor.putString("credentials", json)
+        editor.apply()
+    }
+
+    fun getCredentials(): List<AddPassword> {
+        val json = sharedPreferences.getString("credentials", null)
+        if (json.isNullOrEmpty()) {
+            return emptyList()
+        }
+        val type = object : TypeToken<List<AddPassword>>() {}.type
+        return gson.fromJson(json, type)
+    }
+}

+ 7 - 0
app/src/main/res/layout/item_auto_fill.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+</LinearLayout>

+ 1 - 0
app/src/main/res/values/strings.xml

@@ -136,5 +136,6 @@
     <string name="randomized_symbols">Randomized symbols (!#$)</string>
     <string name="email_field_req">The email field is required.</string>
     <string name="password_field_req">The password field is required.</string>
+    <string name="auto_fill_description">Here is the fastest pass auto fill description</string>
 
 </resources>

+ 5 - 0
app/src/main/res/xml/autofill_service_configuration.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<autofill-service xmlns:android="http://schemas.android.com/apk/res/android"
+    android:settingsActivity=".MainActivity"
+    android:summary="@string/about"
+    android:description="@string/account_number" />

+ 3 - 1
gradle/libs.versions.toml

@@ -9,7 +9,7 @@ junitVersion = "1.2.1"
 espressoCore = "3.6.1"
 lifecycleRuntimeKtx = "2.8.7"
 activityCompose = "1.9.3"
-composeBom = "2024.04.01"
+composeBom = "2024.12.01"
 org-jetbrains-kotlin-android = "1.9.0"
 hilt-lifecycle-viewmodel = "1.0.0-alpha03"
 androidx-hilt-lifecycle = "1.2.0"
@@ -22,8 +22,10 @@ androidx-fragment = "1.8.5"
 navigationFragment = "2.8.4"
 accompanist-pager = "0.31.3-beta"
 coreI18n = "1.0.0-alpha01"
+#auto-fill = "1.3.0-beta01"
 
 [libraries]
+#auto-fill= { group = "androidx.autofill", name = "autofill", version.ref = "auto-fill" }
 androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
 ccp = { module = "com.hbb20:ccp", version.ref = "ccp" }
 hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hiltAndroid" }