various changes

This commit is contained in:
Owen LeJeune
2024-01-23 11:04:54 -05:00
parent 8ea66ac3c3
commit a6e0f71229
8 changed files with 116 additions and 5 deletions

View File

@@ -93,6 +93,8 @@ dependencies {
implementation "androidx.paging:paging-compose:$compose_paging" implementation "androidx.paging:paging-compose:$compose_paging"
implementation "androidx.constraintlayout:constraintlayout-compose:$compose_constraint_layout" implementation "androidx.constraintlayout:constraintlayout-compose:$compose_constraint_layout"
implementation "com.google.accompanist:accompanist-webview:$compose_accompanist" implementation "com.google.accompanist:accompanist-webview:$compose_accompanist"
implementation "androidx.biometric:biometric-ktx:1.2.0-alpha05"
// material you // material you
def monet_compat = "0.4.1" def monet_compat = "0.4.1"

View File

@@ -1,7 +1,6 @@
package com.owenlejeune.tvtime.api.tmdb.api package com.owenlejeune.tvtime.api.tmdb.api
import android.content.Context import android.content.Context
import android.widget.Toast
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
@@ -12,7 +11,6 @@ import androidx.paging.PagingData
import androidx.paging.PagingSource import androidx.paging.PagingSource
import androidx.paging.PagingState import androidx.paging.PagingState
import androidx.paging.cachedIn import androidx.paging.cachedIn
import com.owenlejeune.tvtime.R
import com.owenlejeune.tvtime.ui.viewmodel.ViewModelConstants import com.owenlejeune.tvtime.ui.viewmodel.ViewModelConstants
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent

View File

@@ -40,6 +40,7 @@ class AppPreferences(context: Context) {
private val FLOATING_BOTTOM_BAR = "floating_bottom_bar" private val FLOATING_BOTTOM_BAR = "floating_bottom_bar"
private val RECENT_SEARCHES = "recent_searches" private val RECENT_SEARCHES = "recent_searches"
private val SHOW_GOT_QUOTES = "show_got_quotes" private val SHOW_GOT_QUOTES = "show_got_quotes"
private val USE_ACCOUNT_BIOMETRICS = "use_account_biometrics"
} }
private val preferences: SharedPreferences = context.getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE) private val preferences: SharedPreferences = context.getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE)
@@ -81,6 +82,11 @@ class AppPreferences(context: Context) {
get() = preferences.getInt(SELECTED_COLOR, selectedColorDefault) get() = preferences.getInt(SELECTED_COLOR, selectedColorDefault)
set(value) { preferences.put(SELECTED_COLOR, value) } set(value) { preferences.put(SELECTED_COLOR, value) }
val accountBiometricsDefault: Boolean = false
var accountBiometrics: Boolean
get() = preferences.getBoolean(USE_ACCOUNT_BIOMETRICS, accountBiometricsDefault)
set(value) { preferences.put(USE_ACCOUNT_BIOMETRICS, value) }
/******* Session Tokens ********/ /******* Session Tokens ********/
var authorizedSessionValues: SessionManager.AuthorizedSessionValues? var authorizedSessionValues: SessionManager.AuthorizedSessionValues?
get() = preferences.getString(AUTHORIZED_SESSION_VALUES, null)?.let { get() = preferences.getString(AUTHORIZED_SESSION_VALUES, null)?.let {

View File

@@ -13,10 +13,12 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.paging.compose.LazyPagingItems import androidx.paging.compose.LazyPagingItems
@@ -34,6 +36,7 @@ import com.owenlejeune.tvtime.api.tmdb.api.v4.model.RatedTv
import com.owenlejeune.tvtime.extensions.getCalendarYear import com.owenlejeune.tvtime.extensions.getCalendarYear
import com.owenlejeune.tvtime.extensions.lazyPagingItems import com.owenlejeune.tvtime.extensions.lazyPagingItems
import com.owenlejeune.tvtime.extensions.unlessEmpty import com.owenlejeune.tvtime.extensions.unlessEmpty
import com.owenlejeune.tvtime.preferences.AppPreferences
import com.owenlejeune.tvtime.ui.components.AccountIcon import com.owenlejeune.tvtime.ui.components.AccountIcon
import com.owenlejeune.tvtime.ui.components.BackButton import com.owenlejeune.tvtime.ui.components.BackButton
import com.owenlejeune.tvtime.ui.components.MediaResultCard import com.owenlejeune.tvtime.ui.components.MediaResultCard
@@ -44,16 +47,38 @@ import com.owenlejeune.tvtime.ui.navigation.AppNavItem
import com.owenlejeune.tvtime.ui.screens.tabs.AccountTabNavItem import com.owenlejeune.tvtime.ui.screens.tabs.AccountTabNavItem
import com.owenlejeune.tvtime.ui.viewmodel.AccountViewModel import com.owenlejeune.tvtime.ui.viewmodel.AccountViewModel
import com.owenlejeune.tvtime.ui.viewmodel.ApplicationViewModel import com.owenlejeune.tvtime.ui.viewmodel.ApplicationViewModel
import com.owenlejeune.tvtime.utils.BiometricUtils
import com.owenlejeune.tvtime.utils.SessionManager import com.owenlejeune.tvtime.utils.SessionManager
import com.owenlejeune.tvtime.utils.TmdbUtils import com.owenlejeune.tvtime.utils.TmdbUtils
import com.owenlejeune.tvtime.utils.types.MediaViewType import com.owenlejeune.tvtime.utils.types.MediaViewType
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.koin.java.KoinJavaComponent.get
import java.util.Date import java.util.Date
import kotlin.reflect.KClass import kotlin.reflect.KClass
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun AccountScreen( fun AccountScreen(
appNavController: NavHostController,
doSignInPartTwo: Boolean = false,
appPreferences: AppPreferences = get(AppPreferences::class.java)
) {
var proceed by remember { mutableStateOf(!appPreferences.accountBiometrics) }
if (!proceed) {
val activity = LocalContext.current as FragmentActivity
BiometricUtils.showBiometricPrompt(
activity,
onFailed = { appNavController.popBackStack() },
onError = { appNavController.popBackStack() },
onSuccess = { proceed = true }
)
} else {
AccountView(appNavController = appNavController, doSignInPartTwo = doSignInPartTwo)
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AccountView(
appNavController: NavHostController, appNavController: NavHostController,
doSignInPartTwo: Boolean = false doSignInPartTwo: Boolean = false
) { ) {

View File

@@ -1115,7 +1115,7 @@ private fun ReviewsCard(
if (isExpanded) { if (isExpanded) {
reviews reviews
} else { } else {
reviews.subList(0, 3) reviews.subList(0, minOf(reviews.size, 3))
} }
} ?: emptyList() } ?: emptyList()
Column( Column(

View File

@@ -60,6 +60,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.viewinterop.AndroidView
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper
@@ -85,7 +86,9 @@ import com.owenlejeune.tvtime.ui.viewmodel.ApplicationViewModel
import com.owenlejeune.tvtime.ui.viewmodel.SettingsViewModel import com.owenlejeune.tvtime.ui.viewmodel.SettingsViewModel
import com.owenlejeune.tvtime.ui.views.HomeTabRecyclerAdapter import com.owenlejeune.tvtime.ui.views.HomeTabRecyclerAdapter
import com.owenlejeune.tvtime.ui.views.ItemMoveCallback import com.owenlejeune.tvtime.ui.views.ItemMoveCallback
import com.owenlejeune.tvtime.utils.BiometricUtils
import com.owenlejeune.tvtime.utils.SessionManager import com.owenlejeune.tvtime.utils.SessionManager
import com.owenlejeune.tvtime.utils.canShowBiometricsPrompt
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.koin.java.KoinJavaComponent.get import org.koin.java.KoinJavaComponent.get
@@ -237,7 +240,7 @@ fun DesignPreferences(
settingsNavController: NavController, settingsNavController: NavController,
) { ) {
val settingsViewModel = viewModel<SettingsViewModel>() val settingsViewModel = viewModel<SettingsViewModel>()
val activity = LocalContext.current as Activity val activity = LocalContext.current as FragmentActivity
Column( Column(
modifier = Modifier modifier = Modifier
@@ -315,6 +318,24 @@ fun DesignPreferences(
if (showWallpaperPicker.value) { if (showWallpaperPicker.value) {
WallpaperPicker(showPopup = showWallpaperPicker) WallpaperPicker(showPopup = showWallpaperPicker)
} }
if (canShowBiometricsPrompt()) {
SwitchPreference(
titleText = "Biometrics",
subtitleText = "###",
checkState = settingsViewModel.useAccountBiometrics.collectAsState(),
onCheckedChange = {
settingsViewModel.toggleUseAccountBiometrics()
if (it) {
BiometricUtils.showBiometricPrompt(
activity,
onFailed = { settingsViewModel.setUseAccountBiometrics(false) },
onError = { settingsViewModel.setUseAccountBiometrics(false) }
)
}
}
)
}
} }
} }

View File

@@ -26,6 +26,7 @@ class SettingsViewModel: ViewModel() {
private val _showBackdropGallery = MutableStateFlow(preferences.showBackdropGallery) private val _showBackdropGallery = MutableStateFlow(preferences.showBackdropGallery)
private val _showNextMcuProduction = MutableStateFlow(preferences.showNextMcuProduction) private val _showNextMcuProduction = MutableStateFlow(preferences.showNextMcuProduction)
private val _showGotQuotes = MutableStateFlow(preferences.showGotQuotes) private val _showGotQuotes = MutableStateFlow(preferences.showGotQuotes)
private val _useAccountBiometrics = MutableStateFlow(preferences.accountBiometrics)
} }
val showSearchBar = _showSearchBar.asStateFlow() val showSearchBar = _showSearchBar.asStateFlow()
@@ -42,6 +43,7 @@ class SettingsViewModel: ViewModel() {
val showBackdropGallery = _showBackdropGallery.asStateFlow() val showBackdropGallery = _showBackdropGallery.asStateFlow()
val showNextMcuProduction = _showNextMcuProduction.asStateFlow() val showNextMcuProduction = _showNextMcuProduction.asStateFlow()
val showGotQuotes = _showGotQuotes.asStateFlow() val showGotQuotes = _showGotQuotes.asStateFlow()
val useAccountBiometrics = _useAccountBiometrics.asStateFlow()
fun toggleShowSearchBar() { fun toggleShowSearchBar() {
_showSearchBar.value = _showSearchBar.value.not() _showSearchBar.value = _showSearchBar.value.not()
@@ -168,4 +170,14 @@ class SettingsViewModel: ViewModel() {
preferences.showGotQuotes = value preferences.showGotQuotes = value
} }
fun toggleUseAccountBiometrics() {
_useAccountBiometrics.value = _useAccountBiometrics.value.not()
preferences.accountBiometrics = _useAccountBiometrics.value
}
fun setUseAccountBiometrics(value: Boolean) {
_useAccountBiometrics.value = value
preferences.accountBiometrics = value
}
} }

View File

@@ -0,0 +1,47 @@
package com.owenlejeune.tvtime.utils
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG
import androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL
import androidx.biometric.BiometricPrompt
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentActivity
object BiometricUtils {
fun showBiometricPrompt(activity: FragmentActivity, onError: () -> Unit = {}, onFailed: () -> Unit = {}, onSuccess: () -> Unit = {}) {
val executor = ContextCompat.getMainExecutor(activity)
val biometricPrompt = BiometricPrompt(activity, executor, object: BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
super.onAuthenticationError(errorCode, errString)
onError()
}
override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
onFailed()
}
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
super.onAuthenticationSucceeded(result)
onSuccess()
}
})
val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle("Title")
.setSubtitle("Subtitle")
.setNegativeButtonText("Cancel")
.build()
biometricPrompt.authenticate(promptInfo)
}
}
@Composable
fun canShowBiometricsPrompt(): Boolean {
val context = LocalContext.current
val biometricManager = BiometricManager.from(context)
return biometricManager.canAuthenticate(BIOMETRIC_STRONG or DEVICE_CREDENTIAL) == BiometricManager.BIOMETRIC_SUCCESS
}