Skip to content

Commit 1316309

Browse files
committed
chore:bump version code and versionName
1 parent ea23d10 commit 1316309

4 files changed

Lines changed: 132 additions & 97 deletions

File tree

.idea/workspace.xml

Lines changed: 0 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ android {
1313
applicationId "com.opennotes"
1414
minSdk 26
1515
targetSdk 36
16-
versionCode 7
17-
versionName "1.3.3"
16+
versionCode 8
17+
versionName "1.3.4"
1818

1919
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
2020
vectorDrawables {

app/src/main/java/com/opennotes/feature_node/presentation/MainActivity.kt

Lines changed: 123 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,12 @@ import androidx.compose.animation.ExitTransition
99
import androidx.compose.foundation.isSystemInDarkTheme
1010
import androidx.compose.material3.MaterialTheme
1111
import androidx.compose.material3.Surface
12+
import androidx.compose.runtime.LaunchedEffect
1213
import androidx.compose.runtime.collectAsState
1314
import androidx.compose.runtime.getValue
15+
import androidx.compose.runtime.mutableStateOf
16+
import androidx.compose.runtime.remember
17+
import androidx.compose.runtime.setValue
1418
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
1519
import androidx.navigation.NavType
1620
import androidx.navigation.compose.NavHost
@@ -39,119 +43,150 @@ import kotlinx.coroutines.launch
3943
class MainActivity : FragmentActivity() {
4044

4145
private val settingsViewModel: SettingsViewModel by viewModels()
42-
private var hasShownBiometric = false
4346

4447
override fun onCreate(savedInstanceState: Bundle?) {
4548
val splashScreen = installSplashScreen()
4649
super.onCreate(savedInstanceState)
47-
splashScreen.setKeepOnScreenCondition {
48-
!settingsViewModel.isLoaded.value
49-
50-
}
51-
52-
50+
splashScreen.setKeepOnScreenCondition { !settingsViewModel.isLoaded.value }
5351

5452
setContent {
5553
val currentSettings by settingsViewModel.settings.collectAsState()
54+
val isAppUnlocked by settingsViewModel.isAppUnlocked.collectAsState()
55+
val isLoaded by settingsViewModel.isLoaded.collectAsState()
56+
57+
var showContent by remember { mutableStateOf(false) }
58+
59+
LaunchedEffect(isAppUnlocked, currentSettings.biometricLock, isLoaded) {
60+
if (!isLoaded) return@LaunchedEffect // wait for real settings to load
61+
when {
62+
!currentSettings.biometricLock || isAppUnlocked -> showContent = true
63+
else -> {
64+
showBiometricPrompt(
65+
onSuccess = {
66+
settingsViewModel.setAppUnlocked(true)
67+
showContent = true
68+
},
69+
onError = { errorCode, errString ->
70+
handleBiometricError(errorCode, errString) {
71+
showContent = true
72+
}
73+
}
74+
)
75+
}
76+
}
77+
}
5678

5779
OpenNotesTheme(settings = currentSettings) {
5880
Surface(color = MaterialTheme.colorScheme.background) {
59-
val navController = rememberNavController()
60-
61-
NavHost(
62-
navController = navController,
63-
startDestination = Screen.NotesScreen.route,
64-
enterTransition = { EnterTransition.None },
65-
exitTransition = { ExitTransition.None },
66-
popEnterTransition = { EnterTransition.None },
67-
popExitTransition = { ExitTransition.None }
68-
) {
69-
composable(route = Screen.NotesScreen.route) {
70-
NotesScreen(navController = navController)
71-
}
72-
composable(
73-
route = Screen.AddEditNoteScreen.route +
74-
"?noteId={noteId}&noteColor={noteColor}",
75-
arguments = listOf(
76-
navArgument("noteId") {
77-
type = NavType.IntType
78-
defaultValue = -1
79-
},
80-
navArgument("noteColor") {
81-
type = NavType.IntType
82-
defaultValue = -1
81+
if (showContent) {
82+
val navController = rememberNavController()
83+
NavHost(
84+
navController = navController,
85+
startDestination = Screen.NotesScreen.route,
86+
enterTransition = { EnterTransition.None },
87+
exitTransition = { ExitTransition.None },
88+
popEnterTransition = { EnterTransition.None },
89+
popExitTransition = { ExitTransition.None }
90+
) {
91+
composable(route = Screen.NotesScreen.route) {
92+
NotesScreen(navController = navController)
93+
}
94+
composable(
95+
route = Screen.AddEditNoteScreen.route +
96+
"?noteId={noteId}&noteColor={noteColor}",
97+
arguments = listOf(
98+
navArgument("noteId") {
99+
type = NavType.IntType
100+
defaultValue = -1
101+
},
102+
navArgument("noteColor") {
103+
type = NavType.IntType
104+
defaultValue = -1
105+
}
106+
)
107+
) { backStackEntry ->
108+
val color = backStackEntry.arguments?.getInt("noteColor")
109+
?.takeIf { it != -1 }
110+
val isDarkTheme = when (currentSettings.themeMode) {
111+
ThemeMode.SYSTEM -> isSystemInDarkTheme()
112+
ThemeMode.LIGHT -> false
113+
ThemeMode.DARK -> true
83114
}
84-
)
85-
) { backStackEntry ->
86-
val color = backStackEntry.arguments?.getInt("noteColor")
87-
?.takeIf { it != -1 }
88-
val isDarkTheme = when (currentSettings.themeMode) {
89-
ThemeMode.SYSTEM ->
90-
isSystemInDarkTheme()
91-
ThemeMode.LIGHT -> false
92-
ThemeMode.DARK -> true
115+
val resolvedColor = color ?: if (isDarkTheme) {
116+
NoteColorPalette.Dark.first().toArgb()
117+
} else {
118+
NoteColorPalette.Light.first().toArgb()
119+
}
120+
AddEditNoteScreen(
121+
navController = navController,
122+
noteColor = resolvedColor,
123+
isDarkTheme = isDarkTheme
124+
)
93125
}
94-
95-
val resolvedColor = color ?: if (isDarkTheme) {
96-
NoteColorPalette.Dark.first().toArgb()
97-
} else {
98-
NoteColorPalette.Light.first().toArgb()
126+
composable(route = Screen.SettingsScreen.route) {
127+
SettingsScreen(
128+
navController = navController,
129+
viewModel = settingsViewModel
130+
)
131+
}
132+
composable(route = Screen.AboutScreen.route) {
133+
AboutScreen(navController = navController)
99134
}
100-
101-
AddEditNoteScreen(
102-
navController = navController,
103-
noteColor = resolvedColor,
104-
isDarkTheme = isDarkTheme
105-
)
106-
}
107-
composable(route = Screen.SettingsScreen.route) {
108-
SettingsScreen(
109-
navController = navController,
110-
viewModel = settingsViewModel
111-
)
112-
}
113-
composable(route = Screen.AboutScreen.route) {
114-
AboutScreen(
115-
navController = navController
116-
)
117135
}
118-
136+
} else {
137+
// Blank surface shown until auth completes
138+
Surface(color = MaterialTheme.colorScheme.background) {}
119139
}
120140
}
121141
}
122142
}
123-
124-
125-
}
126-
127-
override fun onWindowFocusChanged(hasFocus: Boolean) {
128-
super.onWindowFocusChanged(hasFocus)
129-
if (hasFocus && !hasShownBiometric) {
130-
hasShownBiometric = true
131-
handleBiometricLock()
132-
}
133143
}
134144

135-
private fun handleBiometricLock() {
136-
lifecycleScope.launch {
137-
settingsViewModel.isLoaded.filter { it }.first()
138-
if (!settingsViewModel.settings.value.biometricLock) return@launch
139-
140-
val promptInfo = BiometricPrompt.PromptInfo.Builder()
145+
private fun showBiometricPrompt(
146+
onSuccess: () -> Unit,
147+
onError: (Int, CharSequence) -> Unit
148+
) {
149+
BiometricPrompt(
150+
this,
151+
ContextCompat.getMainExecutor(this),
152+
object : BiometricPrompt.AuthenticationCallback() {
153+
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
154+
onSuccess()
155+
}
156+
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
157+
onError(errorCode, errString)
158+
}
159+
override fun onAuthenticationFailed() = Unit
160+
}
161+
).authenticate(
162+
BiometricPrompt.PromptInfo.Builder()
141163
.setTitle("Unlock OpenNotes")
142164
.setSubtitle("Confirm your fingerprint to access your notes")
143165
.setNegativeButtonText("Cancel")
144166
.build()
167+
)
168+
}
145169

146-
BiometricPrompt(
147-
this@MainActivity,
148-
ContextCompat.getMainExecutor(this@MainActivity),
149-
object : BiometricPrompt.AuthenticationCallback() {
150-
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) = Unit
151-
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) = finish()
152-
override fun onAuthenticationFailed() = Unit
153-
}
154-
).authenticate(promptInfo)
170+
private fun handleBiometricError(
171+
errorCode: Int,
172+
errString: CharSequence,
173+
onComplete: () -> Unit
174+
) {
175+
when (errorCode) {
176+
BiometricPrompt.ERROR_USER_CANCELED,
177+
BiometricPrompt.ERROR_NEGATIVE_BUTTON,
178+
BiometricPrompt.ERROR_CANCELED -> finish()
179+
180+
BiometricPrompt.ERROR_NO_BIOMETRICS,
181+
BiometricPrompt.ERROR_HW_NOT_PRESENT,
182+
BiometricPrompt.ERROR_HW_UNAVAILABLE -> {
183+
settingsViewModel.setAppUnlocked(true)
184+
// disable biometric lock since hardware is unavailable
185+
settingsViewModel.onBiometricLockToggleRequest(false)
186+
onComplete()
187+
}
188+
189+
else -> finish()
155190
}
156191
}
157192
}

app/src/main/java/com/opennotes/feature_node/presentation/settings/SettingsViewModel.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,13 @@ class SettingsViewModel @Inject constructor(
4040
initialValue = Settings() // Default settings while loading
4141
)
4242

43+
private val _isAppUnlocked = MutableStateFlow(false)
44+
val isAppUnlocked: StateFlow<Boolean> = _isAppUnlocked.asStateFlow()
45+
46+
fun setAppUnlocked(unlocked: Boolean) {
47+
_isAppUnlocked.value = unlocked
48+
}
49+
4350
private val _isLoaded = MutableStateFlow(false)
4451
val isLoaded: StateFlow<Boolean> = _isLoaded.asStateFlow()
4552

0 commit comments

Comments
 (0)