Skip to content

Commit

Permalink
"Added Password Protection by Encrypted Shared Preferences"
Browse files Browse the repository at this point in the history
  • Loading branch information
anuragkanojiya1 committed Oct 8, 2024
1 parent 4a519c3 commit 28af217
Show file tree
Hide file tree
Showing 7 changed files with 247 additions and 13 deletions.
2 changes: 1 addition & 1 deletion android/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ dependencies {
implementation ("androidx.glance:glance-appwidget:1.0.0")
implementation ("androidx.glance:glance-material3:1.0.0")
implementation ("androidx.lifecycle:lifecycle-livedata-ktx:2.8.6")

implementation("androidx.security:security-crypto:1.1.0-alpha04")
}

kapt{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
package rocks.poopjournal.fucksgiven

import android.app.LocaleConfig
import android.app.LocaleManager
import android.os.Build
import android.os.Build.VERSION.SDK_INT
import android.os.Bundle
import android.os.LocaleList
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatDelegate
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.runtime.collectAsState
import androidx.core.os.LocaleListCompat
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.navigation.compose.rememberNavController
import dagger.hilt.android.AndroidEntryPoint
import rocks.poopjournal.fucksgiven.data.getPasswordProtectionEnabled
import rocks.poopjournal.fucksgiven.presentation.navigation.NavGraph
import rocks.poopjournal.fucksgiven.presentation.screens.PasswordPromptScreen
import rocks.poopjournal.fucksgiven.presentation.ui.theme.FucksGivenTheme
import rocks.poopjournal.fucksgiven.presentation.ui.utils.AppTheme
import rocks.poopjournal.fucksgiven.presentation.ui.utils.ThemeSetting
Expand All @@ -39,7 +39,15 @@ class MainActivity : ComponentActivity() {
AppTheme.DARK -> true
}
FucksGivenTheme(darkTheme = useDarkColors) {
NavGraph(navController = rememberNavController(), themeSetting = themeSetting, context = this)
var isAuthenticated by remember { mutableStateOf(false) }
val isPasswordProtectionEnabled = getPasswordProtectionEnabled(context = this)
if (isAuthenticated || !isPasswordProtectionEnabled) {
NavGraph(navController = rememberNavController(), themeSetting = themeSetting, context = this)
} else {
PasswordPromptScreen(context = this) {
isAuthenticated = true
}
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package rocks.poopjournal.fucksgiven.data

import android.content.Context
import android.content.SharedPreferences
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKeys

fun getEncryptedSharedPreferences(context: Context): SharedPreferences {
val masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)

return EncryptedSharedPreferences.create(
"secure_prefs",
masterKeyAlias,
context,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
}

fun savePassword(context: Context, password: String) {
val sharedPreferences = getEncryptedSharedPreferences(context)
val editor = sharedPreferences.edit()
editor.putString("user_password", password)
editor.apply()
}

fun getPassword(context: Context): String? {
val sharedPreferences = getEncryptedSharedPreferences(context)
return sharedPreferences.getString("user_password", null)
}

fun clearStoredPassword(context: Context) {
val sharedPreferences = getEncryptedSharedPreferences(context)
val editor = sharedPreferences.edit()
editor.remove("user_password")
editor.apply()
}

fun setPasswordProtectionEnabled(context: Context, enabled: Boolean) {
val sharedPreferences = getEncryptedSharedPreferences(context)
val editor = sharedPreferences.edit()
editor.putBoolean("password_protection_enabled", enabled)
editor.apply()
}

fun getPasswordProtectionEnabled(context: Context): Boolean {
val sharedPreferences = getEncryptedSharedPreferences(context)
return sharedPreferences.getBoolean("password_protection_enabled", false)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ package rocks.poopjournal.fucksgiven.presentation.navigation
const val HOME_SCREEN = "HomeScreen"
const val SETTINGS_SCREEN = "SettingsScreen"
const val STATS_SCREEN = "StatsScreen"
const val ABOUT_SCREEN = "AboutScreen"
const val ABOUT_SCREEN = "AboutScreen"
const val PASSWORD_PROMPT_SCREEN = "PasswordPromptScreen"
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import androidx.navigation.compose.composable
import rocks.poopjournal.fucksgiven.presentation.screens.AboutScreen
import rocks.poopjournal.fucksgiven.presentation.viewmodel.HomeViewModel
import rocks.poopjournal.fucksgiven.presentation.screens.HomeScreen
import rocks.poopjournal.fucksgiven.presentation.screens.PasswordPromptScreen
import rocks.poopjournal.fucksgiven.presentation.screens.SettingScreen
import rocks.poopjournal.fucksgiven.presentation.screens.StatsScreen
import rocks.poopjournal.fucksgiven.presentation.ui.utils.ThemeSetting
Expand All @@ -19,7 +20,7 @@ import rocks.poopjournal.fucksgiven.presentation.viewmodel.StatsViewModel

@RequiresApi(Build.VERSION_CODES.P)
@Composable
fun NavGraph(navController: NavHostController,themeSetting: ThemeSetting,context: Context){
fun NavGraph(navController: NavHostController,themeSetting: ThemeSetting, context: Context){
val viewModel : HomeViewModel = hiltViewModel()
val statsViewModel : StatsViewModel = hiltViewModel()
val settingsViewModel : SettingsViewModel = hiltViewModel()
Expand All @@ -37,8 +38,12 @@ fun NavGraph(navController: NavHostController,themeSetting: ThemeSetting,context
}

composable(route = SETTINGS_SCREEN){
SettingScreen(navController = navController, viewModel = settingsViewModel)
SettingScreen(navController = navController, viewModel = settingsViewModel, context = context)
}
composable(route = PASSWORD_PROMPT_SCREEN){
PasswordPromptScreen(context, onAuthenticated = {
navController.navigate(HOME_SCREEN)
})
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package rocks.poopjournal.fucksgiven.presentation.screens
import rocks.poopjournal.fucksgiven.data.getPassword

import android.content.Context
import android.widget.Toast
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp
import rocks.poopjournal.fucksgiven.R

@Composable
fun PasswordPromptScreen(context: Context, onAuthenticated: () -> Unit) {
var enteredPassword by remember { mutableStateOf("") }
val storedPassword = getPassword(context)

Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Image(
painter = painterResource(R.drawable.fucks),
contentDescription = "Logo",
modifier = Modifier.size(200.dp)
.align(Alignment.CenterHorizontally)
.padding(vertical = 24.dp)
)
OutlinedTextField(
value = enteredPassword,
onValueChange = { enteredPassword = it },
label = { Text("Enter Password") },
modifier = Modifier.padding(vertical = 16.dp),
visualTransformation = PasswordVisualTransformation()
)
OutlinedButton(onClick = {
if (storedPassword == enteredPassword) {
onAuthenticated()
} else {
Toast.makeText(context, "Password is not correct", Toast.LENGTH_SHORT).show()
}
},
modifier = Modifier.fillMaxWidth(0.3f)
) {
Text(text = "Login")
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package rocks.poopjournal.fucksgiven.presentation.screens

import android.content.Context
import android.widget.Toast
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
Expand All @@ -20,7 +23,9 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
Expand All @@ -31,23 +36,31 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController
import rocks.poopjournal.fucksgiven.R
import rocks.poopjournal.fucksgiven.data.getPasswordProtectionEnabled
import rocks.poopjournal.fucksgiven.data.savePassword
import rocks.poopjournal.fucksgiven.data.setPasswordProtectionEnabled
import rocks.poopjournal.fucksgiven.presentation.component.ThemeContent
import rocks.poopjournal.fucksgiven.presentation.navigation.ABOUT_SCREEN
import rocks.poopjournal.fucksgiven.presentation.ui.utils.ThemeSetting
import rocks.poopjournal.fucksgiven.presentation.viewmodel.SettingsViewModel

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SettingScreen(navController: NavHostController, viewModel: SettingsViewModel) {
fun SettingScreen(navController: NavHostController, viewModel: SettingsViewModel, context: Context) {
var showDialog by remember { mutableStateOf(false) }
val toastMessage = stringResource(id = R.string.backup_success)
var isPasswordProtectionEnabled by remember { mutableStateOf(getPasswordProtectionEnabled(context)) }
var showPasswordDialog by remember { mutableStateOf(false) }

Scaffold(
topBar = {
TopAppBar(
Expand Down Expand Up @@ -105,6 +118,56 @@ fun SettingScreen(navController: NavHostController, viewModel: SettingsViewModel
}
}
}
Column {
Row(
modifier = Modifier
.fillMaxWidth()
.height(50.dp)
.background(MaterialTheme.colorScheme.secondary),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "Security",
color = MaterialTheme.colorScheme.primary,
style = MaterialTheme.typography.labelSmall,
modifier = Modifier.padding(start = 12.dp)
)
}
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(16.dp)
) {
Text(text = "Enable Password Protection")
Switch(
modifier = Modifier.padding(start = 4.dp),
checked = isPasswordProtectionEnabled,
onCheckedChange = { enabled ->
isPasswordProtectionEnabled = enabled

if (enabled) {
showPasswordDialog = true
} else{
isPasswordProtectionEnabled = false
setPasswordProtectionEnabled(context, false)
}
}
)
}
}
if (showPasswordDialog) {
SetPasswordScreen(
context = context,
onPasswordSet = {
showPasswordDialog = false
},
onDismissRequest = {
showPasswordDialog = false
isPasswordProtectionEnabled = false
setPasswordProtectionEnabled(context, false)
}
)
}

Column {
Row(
modifier = Modifier
Expand Down Expand Up @@ -221,3 +284,47 @@ fun ThemeSelectionDialog(
}
)
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SetPasswordScreen(context: Context, onPasswordSet: () -> Unit, onDismissRequest: () -> Unit) {
var password by remember { mutableStateOf("") }
var confirmPassword by remember { mutableStateOf("") }

AlertDialog(onDismissRequest = onDismissRequest){
Column(
modifier = Modifier.clipToBounds(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
TextField(
value = password,
onValueChange = { password = it },
label = { Text("Enter Password") },
modifier = Modifier.padding(16.dp),
visualTransformation = PasswordVisualTransformation()
)
TextField(
value = confirmPassword,
onValueChange = { confirmPassword = it },
label = { Text("Confirm Password") },
modifier = Modifier.padding(16.dp),
visualTransformation = PasswordVisualTransformation()
)
Button(
onClick = {
if (password == confirmPassword && password.isNotBlank()) {
setPasswordProtectionEnabled(context, true)
savePassword(context, password)
onPasswordSet()
} else {
password = ""
confirmPassword = ""
Toast.makeText(context, "Password didn't match", Toast.LENGTH_SHORT ).show()
}
}) {
Text(text = "Set Password")
}
}
}
}

0 comments on commit 28af217

Please sign in to comment.