mirror of
https://github.com/owenlejeune/TVTime.git
synced 2025-11-09 05:12:47 -05:00
various changes
This commit is contained in:
@@ -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"
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user