Hi all! I would love for you to help me with my little kotlin program. It is a small password generator. The problem I have is that when I restart the program, the passwords stop being copied, and if I don’t, then everything is fine. I think the problem is in preservation… Can you tell me how to correct this mistake?
MainActivity:
@file:OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class,
ExperimentalMaterial3Api::class, DelicateCoroutinesApi::class
)
@file:Suppress("DEPRECATION")
package com.example.passwords
import android.content.Context
import android.os.Bundle
import android.os.VibrationEffect
import android.os.Vibrator
import android.util.Log
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.passwords.ui.theme.PasswordsTheme
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.security.Key
import javax.crypto.KeyGenerator
val secretKey: Key = KeyGenerator.getInstance("AES").apply {
init(256)
Log.d("PasswordLoading", "Loading password data...")
}.generateKey()
fun generatePassword(length: Int): String {
val characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()-_"
return (1..length)
.map { characters.random() }
.joinToString("")
Log.d("PasswordLoading", "Loading password data...")
}
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
PasswordsTheme {
PasswordGeneratorScreen(viewModel = PasswordViewModel(applicationContext))
}
}
}
}
@Composable
fun PasswordGeneratorScreen(viewModel: PasswordViewModel = viewModel()) {
var password by rememberSaveable { mutableStateOf("") }
var isPasswordGenerated by rememberSaveable { mutableStateOf(false) }
var showSaveDialog by remember { mutableStateOf(false) }
var showOverwriteDialog by remember { mutableStateOf(false) }
var passwordToOverwrite by remember { mutableStateOf<PasswordData?>(null) }
var showDeleteDialog by remember { mutableStateOf<PasswordData?>(null) }
val savedPasswords = viewModel.savedPasswords
val context = LocalContext.current
val vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as android.media.AudioManager
Scaffold(
modifier = Modifier.fillMaxSize(),
topBar = {
TopAppBar(
title = {
Box(
modifier = Modifier.fillMaxWidth(),
contentAlignment = Alignment.Center
) {
Text(
text = "Password Generator:3",
style = MaterialTheme.typography.headlineMedium,
modifier = Modifier
.fillMaxWidth()
)
}
}
)
},
content = { innerPadding ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(innerPadding)
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = if (password.isNotEmpty()) "Generated Password: $password" else "No password generated",
style = MaterialTheme.typography.bodyLarge,
modifier = Modifier.padding(bottom = 16.dp)
)
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier.fillMaxWidth()
) {
Button(
onClick = {
password = generatePassword(16)
isPasswordGenerated = true
},
modifier = Modifier.weight(1f)
) {
Text(text = "Generate Password")
}
Button(
onClick = {
if (isPasswordGenerated) {
showSaveDialog = true
}
},
enabled = isPasswordGenerated,
modifier = Modifier.weight(1f)
) {
Text(text = "Save Password")
}
}
Spacer(modifier = Modifier.height(16.dp))
Text(
text = "Saved Passwords",
style = MaterialTheme.typography.headlineMedium,
modifier = Modifier.padding(vertical = 16.dp)
)
LazyVerticalGrid(
columns = GridCells.Fixed(2),
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
items(savedPasswords) { passwordData ->
Box(
modifier = Modifier
.size(100.dp)
.background(Color.Gray, RoundedCornerShape(18.dp))
.combinedClickable(
onClick = {
GlobalScope.launch(Dispatchers.Main) {
try {
val decryptedPassword = withContext(Dispatchers.IO) {
decryptPassword(passwordData.encryptedPassword, passwordData.iv)
}
copyPasswordToClipboard(decryptedPassword, context)
Toast.makeText(context, "Password copied!", Toast.LENGTH_SHORT).show()
Log.d("PasswordCopy", "Decrypted Password: $decryptedPassword")
} catch (e: Exception) {
Toast.makeText(context, "Error decrypting password", Toast.LENGTH_SHORT).show()
}
}
},
onLongClick = {
if (audioManager.ringerMode == android.media.AudioManager.RINGER_MODE_NORMAL) {
vibrator.vibrate(VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE))
}
showDeleteDialog = passwordData
}
),
contentAlignment = Alignment.Center
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(
text = passwordData.appName,
style = MaterialTheme.typography.bodyLarge,
color = Color.White,
fontWeight = FontWeight.Bold,
fontSize = 24.sp
)
Text(
text = passwordData.login,
style = MaterialTheme.typography.bodySmall,
color = Color.White,
fontSize= 12.sp
)
Text(
text = "••••••••",
style = MaterialTheme.typography.bodyMedium,
color = Color.White,
modifier = Modifier.padding(8.dp),
fontSize = 28.sp
)
}
}
}
}
}
if (showSaveDialog) {
SavePasswordDialog(
onDismiss = { showSaveDialog = false },
onSave = { appName, login ->
if (audioManager.ringerMode == android.media.AudioManager.RINGER_MODE_NORMAL) {
vibrator.vibrate(VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE))
}
GlobalScope.launch(Dispatchers.Main) {
val existingPassword = withContext(Dispatchers.IO) {
viewModel.findPassword(appName, login)
}
if (existingPassword != null) {
passwordToOverwrite = existingPassword
showOverwriteDialog = true
showSaveDialog = false
} else {
val (encryptedPassword, iv) = withContext(Dispatchers.IO) {
encryptPassword(password)
}
withContext(Dispatchers.IO) {
viewModel.addPassword(PasswordData(encryptedPassword, iv, appName, login))
}
withContext(Dispatchers.Main) {
Toast.makeText(context, "Password saved!", Toast.LENGTH_SHORT).show()
password = ""
isPasswordGenerated = false
showSaveDialog = false
}
}
}
}
)
}
if (showOverwriteDialog) {
OverwritePasswordDialog(
onDismiss = { showOverwriteDialog = false },
onConfirm = {
if (audioManager.ringerMode == android.media.AudioManager.RINGER_MODE_NORMAL) {
vibrator.vibrate(VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE))
}
GlobalScope.launch(Dispatchers.Main) {
passwordToOverwrite?.let { oldData ->
val (encryptedPassword, iv) = withContext(Dispatchers.IO) {
encryptPassword(password)
}
val newData = PasswordData(encryptedPassword, iv, oldData.appName, oldData.login)
withContext(Dispatchers.IO) {
viewModel.updatePassword(oldData, newData)
}
withContext(Dispatchers.Main) {
Toast.makeText(context, "Password overwritten!", Toast.LENGTH_SHORT).show()
password = ""
isPasswordGenerated = false
}
}
showOverwriteDialog = false
}
}
)
}
showDeleteDialog?.let { passwordData ->
DeletePasswordDialog(
onDismiss = { showDeleteDialog = null },
onConfirm = {
viewModel.removePassword(passwordData)
Toast.makeText(context, "Password deleted!", Toast.LENGTH_SHORT).show()
showDeleteDialog = null
}
)
}
}
)
}
@Composable
fun SavePasswordDialog(onDismiss: () -> Unit, onSave: (String, String) -> Unit) {
var appName by remember { mutableStateOf("") }
var login by remember { mutableStateOf("") }
var isError by remember { mutableStateOf(false) }
AlertDialog(
onDismissRequest = onDismiss,
title = { Text("Save Password") },
text = {
Column {
OutlinedTextField(
value = appName,
onValueChange = { appName = it },
label = { Text("App Name") },
isError = isError,
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(8.dp))
OutlinedTextField(
value = login,
onValueChange = { login = it },
label = { Text("Login") },
isError = isError,
modifier = Modifier.fillMaxWidth()
)
if (isError) {
Text("Both fields are required.", color = MaterialTheme.colorScheme.error)
}
}
},
confirmButton = {
Button(onClick = {
if (appName.isNotBlank() && login.isNotBlank()) {
onSave(appName, login)
} else {
isError = true
}
}) {
Text("Save")
}
},
dismissButton = {
Button(onClick = onDismiss) {
Text("Cancel")
}
}
)
}
@Composable
fun OverwritePasswordDialog(onDismiss: () -> Unit, onConfirm: () -> Unit) {
AlertDialog(
onDismissRequest = onDismiss,
title = { Text("Overwrite Password") },
text = { Text("A password for this application and login already exists. Do you want to overwrite it?") },
confirmButton = {
Button(onClick = onConfirm) {
Text("Yes")
}
},
dismissButton = {
Button(onClick = onDismiss) {
Text("No")
}
}
)
}
@Composable
fun DeletePasswordDialog(onDismiss: () -> Unit, onConfirm: () -> Unit) {
AlertDialog(
onDismissRequest = onDismiss,
title = { Text("Delete Password") },
text = { Text("Are you sure you want to delete this password?") },
confirmButton = {
Button(onClick = onConfirm) {
Text("Yes")
}
},
dismissButton = {
Button(onClick = onDismiss) {
Text("No")
}
}
)
}
@Preview(showBackground = true)
@Composable
fun PasswordGeneratorScreenPreview() {
PasswordsTheme {
PasswordGeneratorScreen()
}
}
PasswordData:
package com.example.passwords
import java.security.SecureRandom
import javax.crypto.Cipher
import javax.crypto.spec.GCMParameterSpec
import java.io.Serializable
import android.util.Base64
import android.content.Context
import android.util.Log
import android.widget.Toast
data class PasswordData(
val encryptedPassword: String,
val iv: String,
val appName: String,
val login: String
) : Serializable
fun generateIv(): ByteArray {
return ByteArray(12).apply {
SecureRandom().nextBytes(this)
Log.d("PasswordLoading", "Loading password data...")
}
}
fun copyPasswordToClipboard(password: String, context: Context) {
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as android.content.ClipboardManager
val clip = android.content.ClipData.newPlainText("password", password)
clipboard.setPrimaryClip(clip)
Toast.makeText(context, "Password copied!", Toast.LENGTH_SHORT).show()
Log.d("PasswordLoading", "Loading password data...")
}
fun encryptPassword(password: String): Pair<String, String> {
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
val iv = generateIv() // Генерация уникального IV
cipher.init(Cipher.ENCRYPT_MODE, secretKey, GCMParameterSpec(128, iv))
val encryptedPassword = cipher.doFinal(password.toByteArray())
val encryptedPasswordBase64 = Base64.encodeToString(encryptedPassword, Base64.NO_WRAP)
val ivBase64 = Base64.encodeToString(iv, Base64.NO_WRAP)
return encryptedPasswordBase64 to ivBase64
}
fun decryptPassword(encryptedData: String, iv: String): String {
val encryptedPassword = Base64.decode(encryptedData, Base64.NO_WRAP)
val ivBytes = Base64.decode(iv, Base64.NO_WRAP)
Log.d("PasswordLoading", "Loading password data...")
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
cipher.init(Cipher.DECRYPT_MODE, secretKey, GCMParameterSpec(128, ivBytes))
return String(cipher.doFinal(encryptedPassword))
Log.d("PasswordLoading", "Loading password data...")
}
PasswordViewModel:
package com.example.passwords
import android.content.Context
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
class PasswordViewModel(private val context: Context) : ViewModel() {
var savedPasswords by mutableStateOf(listOf<PasswordData>())
private set
init {
loadPasswords()
}
private fun savePasswords() {
val sharedPreferences = context.getSharedPreferences("passwords", Context.MODE_PRIVATE)
val editor = sharedPreferences.edit()
val passwordListString = savedPasswords.joinToString("|") { passwordData ->
"${passwordData.encryptedPassword},${passwordData.iv},${passwordData.appName},${passwordData.login}"
}
editor.putString("password_list", passwordListString)
editor.apply()
}
private fun loadPasswords() {
val sharedPreferences = context.getSharedPreferences("passwords", Context.MODE_PRIVATE)
val passwordListString = sharedPreferences.getString("password_list", null)
if (passwordListString != null) {
val passwordDataList = passwordListString.split("|").mapNotNull { item ->
val parts = item.split(",")
if (parts.size == 4) {
val encryptedPassword = parts[0]
val iv = parts[1]
val appName = parts[2]
val login = parts[3]
PasswordData(encryptedPassword, iv, appName, login)
} else {
null
}
}
savedPasswords = passwordDataList
}
}
fun addPassword(passwordData: PasswordData) {
savedPasswords = savedPasswords + passwordData
savePasswords()
}
fun updatePassword(oldData: PasswordData, newData: PasswordData) {
savedPasswords = savedPasswords - oldData + newData
savePasswords()
}
fun removePassword(passwordData: PasswordData) {
savedPasswords = savedPasswords - passwordData
savePasswords()
}
fun findPassword(appName: String, login: String): PasswordData? {
return savedPasswords.find { it.appName == appName && it.login == login }
}
}