move account and sign in/out options to overlay menu

This commit is contained in:
Owen LeJeune
2023-06-05 10:59:42 -04:00
parent 1aee028cb7
commit 88f6932140
15 changed files with 572 additions and 363 deletions

View File

@@ -1,20 +1,18 @@
package com.owenlejeune.tvtime package com.owenlejeune.tvtime
import android.os.Bundle import android.os.Bundle
import android.util.Log
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.compose.animation.rememberSplineBasedDecay import androidx.compose.animation.rememberSplineBasedDecay
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Scaffold import androidx.compose.material.Scaffold
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.compositeOver
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalFocusManager
@@ -23,7 +21,6 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.navigation.NavType import androidx.navigation.NavType
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
@@ -31,11 +28,17 @@ import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument import androidx.navigation.navArgument
import androidx.navigation.navDeepLink
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import com.kieronquinn.monetcompat.app.MonetCompatActivity import com.kieronquinn.monetcompat.app.MonetCompatActivity
import com.owenlejeune.tvtime.extensions.WindowSizeClass import com.owenlejeune.tvtime.extensions.WindowSizeClass
import com.owenlejeune.tvtime.extensions.navigateInBottomBar import com.owenlejeune.tvtime.extensions.navigateInBottomBar
import com.owenlejeune.tvtime.extensions.rememberWindowSizeClass import com.owenlejeune.tvtime.extensions.rememberWindowSizeClass
import com.owenlejeune.tvtime.preferences.AppPreferences import com.owenlejeune.tvtime.preferences.AppPreferences
import com.owenlejeune.tvtime.ui.components.AccountIcon
import com.owenlejeune.tvtime.ui.components.ProfileMenuContainer
import com.owenlejeune.tvtime.ui.components.ProfileMenuDefaults
import com.owenlejeune.tvtime.ui.components.ProfileMenuOverlay
import com.owenlejeune.tvtime.ui.navigation.BottomNavItem import com.owenlejeune.tvtime.ui.navigation.BottomNavItem
import com.owenlejeune.tvtime.ui.navigation.MainNavGraph import com.owenlejeune.tvtime.ui.navigation.MainNavGraph
import com.owenlejeune.tvtime.ui.navigation.MainNavItem import com.owenlejeune.tvtime.ui.navigation.MainNavItem
@@ -89,8 +92,6 @@ class MainActivity : MonetCompatActivity() {
preferences: AppPreferences = get(AppPreferences::class.java) preferences: AppPreferences = get(AppPreferences::class.java)
) { ) {
val navController = rememberNavController() val navController = rememberNavController()
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
val decayAnimationSpec = rememberSplineBasedDecay<Float>() val decayAnimationSpec = rememberSplineBasedDecay<Float>()
val topAppBarScrollState = rememberTopAppBarScrollState() val topAppBarScrollState = rememberTopAppBarScrollState()
@@ -102,6 +103,23 @@ class MainActivity : MonetCompatActivity() {
val appBarActions = remember { mutableStateOf<@Composable RowScope.() -> Unit>({}) } val appBarActions = remember { mutableStateOf<@Composable RowScope.() -> Unit>({}) }
val fab = remember { mutableStateOf<@Composable () -> Unit>({}) } val fab = remember { mutableStateOf<@Composable () -> Unit>({}) }
val showProfileMenuOverlay = remember { mutableStateOf(false) }
val navigationIcon = @Composable {
AccountIcon(
modifier = Modifier.padding(horizontal = 12.dp),
size = 32.dp,
onClick = { showProfileMenuOverlay.value = true }
)
}
val defaultNavBarColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.08f).compositeOver(background = MaterialTheme.colorScheme.surface)
ProfileMenuContainer(
appNavController = appNavController,
visible = showProfileMenuOverlay.value,
onDismissRequest = { showProfileMenuOverlay.value = false },
colors = ProfileMenuDefaults.systemBarColors(navBarColor = defaultNavBarColor)
) {
Scaffold( Scaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = { topBar = {
@@ -110,7 +128,8 @@ class MainActivity : MonetCompatActivity() {
appNavController = appNavController, appNavController = appNavController,
title = appBarTitle.value, title = appBarTitle.value,
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
appBarActions = appBarActions appBarActions = appBarActions,
navigationIcon = navigationIcon
) )
} }
}, },
@@ -132,28 +151,22 @@ class MainActivity : MonetCompatActivity() {
appBarTitle = appBarTitle, appBarTitle = appBarTitle,
appBarActions = appBarActions, appBarActions = appBarActions,
topBarScrollBehaviour = scrollBehavior, topBarScrollBehaviour = scrollBehavior,
mainNavStartRoute = mainNavStartRoute mainNavStartRoute = mainNavStartRoute,
navigationIcon = navigationIcon
) )
} }
} }
} }
}
@Composable @Composable
private fun TopBar( private fun TopBar(
appNavController: NavHostController, appNavController: NavHostController,
title: @Composable () -> Unit, title: @Composable () -> Unit,
scrollBehavior: TopAppBarScrollBehavior, scrollBehavior: TopAppBarScrollBehavior,
appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({}) appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({}),
navigationIcon: @Composable () -> Unit = {}
) { ) {
val defaultAppBarActions: @Composable RowScope.() -> Unit = {
IconButton(
onClick = {
appNavController.navigate(MainNavItem.SettingsView.route)
}
) {
Icon(imageVector = Icons.Filled.Settings, contentDescription = stringResource(id = R.string.nav_settings_title))
}
}
LargeTopAppBar( LargeTopAppBar(
title = title, title = title,
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
@@ -162,9 +175,9 @@ class MainActivity : MonetCompatActivity() {
scrolledContainerColor = MaterialTheme.colorScheme.background scrolledContainerColor = MaterialTheme.colorScheme.background
), ),
actions = { actions = {
defaultAppBarActions()
appBarActions.value(this) appBarActions.value(this)
} },
navigationIcon = navigationIcon
) )
} }
@@ -208,6 +221,7 @@ class MainActivity : MonetCompatActivity() {
topBarScrollBehaviour: TopAppBarScrollBehavior, topBarScrollBehaviour: TopAppBarScrollBehavior,
appBarTitle: MutableState<@Composable () -> Unit>, appBarTitle: MutableState<@Composable () -> Unit>,
appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({}), appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({}),
navigationIcon: @Composable () -> Unit = {},
mainNavStartRoute: String = BottomNavItem.SortedItems[0].route mainNavStartRoute: String = BottomNavItem.SortedItems[0].route
) { ) {
if (windowSize == WindowSizeClass.Expanded) { if (windowSize == WindowSizeClass.Expanded) {
@@ -218,7 +232,8 @@ class MainActivity : MonetCompatActivity() {
appBarTitle = appBarTitle, appBarTitle = appBarTitle,
appBarActions = appBarActions, appBarActions = appBarActions,
topBarScrollBehaviour = topBarScrollBehaviour, topBarScrollBehaviour = topBarScrollBehaviour,
mainNavStartRoute = mainNavStartRoute mainNavStartRoute = mainNavStartRoute,
navigationIcon = navigationIcon
) )
} else { } else {
SingleColumnMainContent( SingleColumnMainContent(
@@ -259,6 +274,7 @@ class MainActivity : MonetCompatActivity() {
topBarScrollBehaviour: TopAppBarScrollBehavior, topBarScrollBehaviour: TopAppBarScrollBehavior,
appBarTitle: MutableState<@Composable () -> Unit>, appBarTitle: MutableState<@Composable () -> Unit>,
appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({}), appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({}),
navigationIcon: @Composable () -> Unit = {},
mainNavStartRoute: String = BottomNavItem.SortedItems[0].route, mainNavStartRoute: String = BottomNavItem.SortedItems[0].route,
preferences: AppPreferences = get(AppPreferences::class.java) preferences: AppPreferences = get(AppPreferences::class.java)
) { ) {
@@ -291,7 +307,8 @@ class MainActivity : MonetCompatActivity() {
appNavController = appNavController, appNavController = appNavController,
title = appBarTitle.value, title = appBarTitle.value,
scrollBehavior = topBarScrollBehaviour, scrollBehavior = topBarScrollBehaviour,
appBarActions = appBarActions appBarActions = appBarActions,
navigationIcon = navigationIcon
) )
MainMediaView( MainMediaView(
appNavController = appNavController, appNavController = appNavController,
@@ -435,6 +452,18 @@ class MainActivity : MonetCompatActivity() {
WebLinkView(url = url, appNavController = appNavController) WebLinkView(url = url, appNavController = appNavController)
} }
} }
composable(
route = MainNavItem.AccountView.route,
deepLinks = listOf(
navDeepLink { uriPattern = "app://tvtime.auth.{${NavConstants.ACCOUNT_KEY}}" }
)
) {
val deepLink = it.arguments?.getString(NavConstants.ACCOUNT_KEY)
AccountView(
appNavController = appNavController,
doSignInPartTwo = deepLink == NavConstants.AUTH_REDIRECT_PAGE
)
}
} }
} }

View File

@@ -1,141 +1,282 @@
package com.owenlejeune.tvtime.ui.components package com.owenlejeune.tvtime.ui.components
import android.widget.Toast
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.layout.Box
import androidx.compose.material.DropdownMenu import androidx.compose.foundation.layout.BoxScope
import androidx.compose.material3.Divider import androidx.compose.foundation.layout.Column
import androidx.compose.material3.IconButton import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Login
import androidx.compose.material.icons.outlined.Logout
import androidx.compose.material.icons.outlined.Person
import androidx.compose.material.icons.outlined.PersonAdd
import androidx.compose.material.icons.outlined.Settings
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.compositeOver
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import com.owenlejeune.tvtime.BuildConfig
import com.owenlejeune.tvtime.R
import com.owenlejeune.tvtime.ui.navigation.MainNavItem
import com.owenlejeune.tvtime.utils.SessionManager
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
private const val ALPHA = 0.7f
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun TopAppBarDropdownMenu( fun ProfileMenuOverlay(
icon: @Composable () -> Unit = {}, appNavController: NavController,
content: @Composable ColumnScope.(expanded: MutableState<Boolean>) -> Unit = {} visible: Boolean,
) { onDismissRequest: () -> Unit
val expanded = remember { mutableStateOf(false) }
Box(
modifier = Modifier.wrapContentSize(Alignment.TopEnd)
) {
IconButton(
onClick = {
expanded.value = true
}
) {
icon()
}
}
DropdownMenu(
modifier = Modifier.background(color = MaterialTheme.colorScheme.background),
expanded = expanded.value,
onDismissRequest = { expanded.value = false }
) {
content(this, expanded)
}
}
@Composable
fun CustomTopAppBarDropdownMenu(
icon: @Composable () -> Unit = {},
content: @Composable ColumnScope.(expanded: MutableState<Boolean>) -> Unit = {}
) {
val expanded = remember { mutableStateOf(false) }
Box(modifier = Modifier.wrapContentSize(Alignment.TopEnd).padding(end = 12.dp)) {
IconButton(onClick = { expanded.value = true }) {
icon()
}
}
DropdownMenu(
expanded = expanded.value,
onDismissRequest = { expanded.value = false},
modifier = Modifier
.background(color = MaterialTheme.colorScheme.background)
.shadow(elevation = 0.dp),
offset = DpOffset(16.dp, 0.dp)
) {
content(this, expanded)
}
}
@Composable
fun CustomMenuItem(
text: String,
onClick: () -> Unit
) { ) {
if (visible) {
val context = LocalContext.current
Box( Box(
modifier = Modifier modifier = Modifier
.clip(RoundedCornerShape(30.dp)) .fillMaxSize()
.background(color = Color.Black.copy(alpha = ALPHA))
.clickable(onClick = onDismissRequest)
) {
Card(
modifier = Modifier
.align(Alignment.TopCenter)
.padding(vertical = 100.dp, horizontal = 12.dp)
.fillMaxWidth() .fillMaxWidth()
.background(color = MaterialTheme.colorScheme.primary) .wrapContentHeight(),
colors = CardDefaults.cardColors(
containerColor = Color.Black.copy(alpha = 0.6f).compositeOver(MaterialTheme.colorScheme.primaryContainer),
contentColor = Color.White.copy(alpha = 0.8f).compositeOver(MaterialTheme.colorScheme.onPrimaryContainer)
)
) { ) {
Column(
modifier = Modifier.padding(vertical = 12.dp)
) {
val currentSessionState = remember { SessionManager.currentSession }
val currentSession = currentSessionState.value
currentSession?.let {
ProfileMenuItem {
AccountIcon(
size = 48.dp,
enabled = false
)
Text( Text(
text = text, text = currentSession.accountDetails.value?.name ?: "",
color = MaterialTheme.colorScheme.background, fontSize = 20.sp,
modifier = Modifier fontWeight = FontWeight.Bold
.padding(horizontal = 15.dp, vertical = 10.dp)
.clickable(onClick = onClick)
.fillMaxWidth(),
textAlign = TextAlign.Center
) )
} }
}
@Composable MenuDivider()
fun CustomMenuDivider() {
Divider(color = Color.Transparent, modifier = Modifier.padding(vertical = 2.dp))
}
@Composable ProfileMenuItem(
fun TopAppBarDialogMenu(
icon: @Composable () -> Unit = {},
content: @Composable (showing: MutableState<Boolean>) -> Unit = {}
) {
val expanded = remember { mutableStateOf(false) }
Box(
modifier = Modifier.wrapContentSize(Alignment.TopEnd)
) {
IconButton(
onClick = { onClick = {
expanded.value = true Toast.makeText(context, "Under construction", Toast.LENGTH_SHORT).show()
} }
) { ) {
icon() Icon(
imageVector = Icons.Outlined.PersonAdd,
contentDescription = null
)
Text(
text = stringResource(id = R.string.account_add),
fontSize = 16.sp
)
}
} ?: run {
ProfileMenuItem {
Text(
text = stringResource(id = R.string.account_not_logged_in),
fontSize = 16.sp
)
} }
} }
if (expanded.value) { MenuDivider()
Dialog(
onDismissRequest = { expanded.value = false }, currentSession?.let {
content = { content(expanded) } ProfileMenuItem(
onClick = {
onDismissRequest()
appNavController.navigate(MainNavItem.AccountView.route)
}
) {
Icon(
imageVector = Icons.Outlined.Person,
contentDescription = null
)
Text(
text = stringResource(id = R.string.nav_account_title),
fontSize = 16.sp
)
}
}
ProfileMenuItem(
onClick = {
onDismissRequest()
appNavController.navigate(MainNavItem.SettingsView.route)
}
) {
Icon(
imageVector = Icons.Outlined.Settings,
contentDescription = null
)
Text(
text = stringResource(id = R.string.nav_settings_title),
fontSize = 16.sp
)
}
MenuDivider()
ProfileMenuItem(
onClick = {
onDismissRequest()
CoroutineScope(Dispatchers.IO).launch {
if (currentSession != null) {
SessionManager.clearSession()
} else {
SessionManager.signInPart1(context) {
appNavController.navigate(
MainNavItem.WebLinkView.route.plus("/$it")
)
}
}
}
}
) {
if (currentSession != null) {
Icon(
imageVector = Icons.Outlined.Logout,
contentDescription = null
)
Text(
text = stringResource(id = R.string.action_sign_out),
fontSize = 16.sp
)
} else {
Icon(
imageVector = Icons.Outlined.Login,
contentDescription = null
)
Text(
text = stringResource(id = R.string.action_sign_in),
fontSize = 16.sp
)
}
}
MenuDivider()
Text(
text = "${stringResource(id = R.string.app_name)} v${BuildConfig.VERSION_NAME}",
fontSize = 10.sp,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
}
}
}
}
}
@Composable
private fun MenuDivider() {
MyDivider(modifier = Modifier.padding(vertical = 9.dp))
}
@Composable
private fun ProfileMenuItem(
onClick: (() -> Unit)? = null,
content: @Composable RowScope.() -> Unit
) {
Box(
modifier = Modifier
.clickable(
enabled = onClick != null,
onClick = onClick ?: {}
)
) {
Row(
horizontalArrangement = Arrangement.spacedBy(18.dp),
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.padding(horizontal = 24.dp, vertical = 9.dp)
.fillMaxWidth(),
content = content
)
}
}
class ProfileMenuColors internal constructor(val statusBarColor: Color, val navBarColor: Color)
object ProfileMenuDefaults {
@Composable
fun systemBarColors(
statusBarColor: Color = MaterialTheme.colorScheme.background,
navBarColor: Color = MaterialTheme.colorScheme.background
) = ProfileMenuColors(statusBarColor, navBarColor)
}
@Composable
fun ProfileMenuContainer(
colors: ProfileMenuColors = ProfileMenuDefaults.systemBarColors(),
appNavController: NavController,
visible: Boolean,
onDismissRequest: () -> Unit,
content: @Composable BoxScope.() -> Unit
) {
val statusBarColor = if (visible) {
Color.Black.copy(alpha = ALPHA).compositeOver(background = colors.statusBarColor)
} else {
colors.statusBarColor
}
val navBarColor = if (visible) {
Color.Black.copy(alpha = ALPHA).compositeOver(background = colors.navBarColor)
} else {
colors.navBarColor
}
val systemUiController = rememberSystemUiController()
systemUiController.setStatusBarColor(statusBarColor)
systemUiController.setNavigationBarColor(navBarColor)
Box {
content()
ProfileMenuOverlay(
visible = visible,
onDismissRequest = onDismissRequest,
appNavController = appNavController
) )
// AlertDialog(
// modifier = Modifier
// .fillMaxWidth()
// .wrapContentHeight(),
// backgroundColor = MaterialTheme.colorScheme.background,
// onDismissRequest = { expanded.value = false },
// text = { content(expanded) },
// buttons = {}
// )
} }
} }

View File

@@ -247,13 +247,16 @@ fun PosterItem(
colors = CardDefaults.cardColors(containerColor = Color.Transparent) colors = CardDefaults.cardColors(containerColor = Color.Transparent)
) { ) {
var backgroundColor by remember { mutableStateOf(Color.Gray) } var backgroundColor by remember { mutableStateOf(Color.Gray) }
val m = if (backgroundColor == Color.Transparent) {
Modifier.wrapContentHeight()
} else {
Modifier.height(POSTER_HEIGHT)
}
Box( Box(
modifier = Modifier modifier = m
.width(width = width) .width(width = width)
.height(height = POSTER_HEIGHT)
.background(color = backgroundColor) .background(color = backgroundColor)
.clip(RoundedCornerShape(5.dp)) .clip(RoundedCornerShape(5.dp))
.onGloballyPositioned { sizeImage = it.size }
) { ) {
var bgIcon by remember { mutableStateOf(placeholder) } var bgIcon by remember { mutableStateOf(placeholder) }
Icon( Icon(
@@ -286,7 +289,7 @@ fun PosterItem(
}, },
model = url, model = url,
contentDescription = title, contentDescription = title,
contentScale = ContentScale.FillWidth, contentScale = ContentScale.FillBounds,
onSuccess = { backgroundColor = Color.Transparent } onSuccess = { backgroundColor = Color.Transparent }
) )

View File

@@ -17,12 +17,14 @@ import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AccountCircle
import androidx.compose.material.icons.filled.ArrowDropDown import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.material.icons.filled.ArrowDropUp import androidx.compose.material.icons.filled.ArrowDropUp
import androidx.compose.material.icons.filled.Error import androidx.compose.material.icons.filled.Error
import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.filled.Visibility import androidx.compose.material.icons.filled.Visibility
import androidx.compose.material.icons.filled.VisibilityOff import androidx.compose.material.icons.filled.VisibilityOff
import androidx.compose.material.icons.outlined.AccountCircle
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
@@ -65,9 +67,11 @@ import coil.compose.rememberAsyncImagePainter
import com.google.accompanist.flowlayout.FlowRow import com.google.accompanist.flowlayout.FlowRow
import com.owenlejeune.tvtime.R import com.owenlejeune.tvtime.R
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.AuthorDetails import com.owenlejeune.tvtime.api.tmdb.api.v3.model.AuthorDetails
import com.owenlejeune.tvtime.extensions.unlessEmpty
import com.owenlejeune.tvtime.preferences.AppPreferences import com.owenlejeune.tvtime.preferences.AppPreferences
import com.owenlejeune.tvtime.ui.navigation.MainNavItem import com.owenlejeune.tvtime.ui.navigation.MainNavItem
import com.owenlejeune.tvtime.ui.screens.main.MediaViewType import com.owenlejeune.tvtime.ui.screens.main.MediaViewType
import com.owenlejeune.tvtime.utils.SessionManager
import com.owenlejeune.tvtime.utils.TmdbUtils import com.owenlejeune.tvtime.utils.TmdbUtils
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -696,34 +700,90 @@ fun AvatarImage(
contentDescription = "" contentDescription = ""
) )
} else { } else {
val text = if (author.name.isNotEmpty()) author.name[0] else author.username[0] val name = author.name.unlessEmpty(author.username)
RoundedLetterImage( UserInitials(size = size, name = name)
size = size, }
character = text }
@Composable
fun UserInitials(
size: Dp,
name: String,
fontSize: TextUnit = 16.sp
) {
val sanitizedName = name.replace("\\s+".toRegex(), " ")
val userName = if(sanitizedName.contains(" ")) {
sanitizedName.split(" ")[0][0].toString() + sanitizedName.split(" ")[1][0].toString()
} else {
if (sanitizedName.length < 3) name else { sanitizedName.substring(0, 2) }
}
Box(
modifier = Modifier
.clip(CircleShape)
.size(size)
.background(color = MaterialTheme.colorScheme.secondary)
) {
Text(
modifier = Modifier.align(Alignment.Center),
text = userName.uppercase(),
textAlign = TextAlign.Center,
style = TextStyle(
color = MaterialTheme.colorScheme.background,
fontSize = fontSize
)
) )
} }
} }
@Composable @Composable
fun RoundedLetterImage( fun AccountIcon(
size: Dp, modifier: Modifier = Modifier,
character: Char, size: Dp = 60.dp,
modifier: Modifier = Modifier onClick: () -> Unit = {},
enabled: Boolean = true
) { ) {
Box( val accountDetails = SessionManager.currentSession.value?.accountDetails?.value
modifier = modifier val avatarUrl = accountDetails?.let {
when {
accountDetails.avatar.tmdb?.avatarPath?.isNotEmpty() == true -> {
TmdbUtils.getAccountAvatarUrl(accountDetails)
}
accountDetails.avatar.gravatar?.isDefault() == false -> {
TmdbUtils.getAccountGravatarUrl(accountDetails)
}
else -> null
}
}
Box(modifier = modifier
.clip(CircleShape) .clip(CircleShape)
.size(size) .clickable(
.background(color = MaterialTheme.colorScheme.tertiary) enabled = enabled,
) { onClick = onClick
Text(
modifier = Modifier
.align(Alignment.Center),
text = character.uppercase(),
color = MaterialTheme.colorScheme.onTertiary,
textAlign = TextAlign.Center,
style = MaterialTheme.typography.titleLarge
) )
) {
if (accountDetails == null) {
Icon(
imageVector = Icons.Outlined.AccountCircle,
contentDescription = null,
modifier = Modifier.size(size),
tint = MaterialTheme.colorScheme.secondary
)
} else if (avatarUrl == null) {
val name = accountDetails.name.ifEmpty { accountDetails.username }
UserInitials(size = size, name = name)
} else {
Box(modifier = Modifier.size(size)) {
AsyncImage(
model = avatarUrl,
contentDescription = "",
modifier = Modifier
.size(60.dp)
.clip(CircleShape),
contentScale = ContentScale.Fit
)
}
}
} }
} }
@@ -1036,3 +1096,8 @@ fun SearchBar(
} }
) )
} }
@Composable
fun MyDivider(modifier: Modifier = Modifier) {
Divider(thickness = 0.5.dp, modifier = modifier, color = MaterialTheme.colorScheme.secondaryContainer)
}

View File

@@ -3,6 +3,7 @@ package com.owenlejeune.tvtime.ui.navigation
import com.owenlejeune.tvtime.R import com.owenlejeune.tvtime.R
import com.owenlejeune.tvtime.preferences.AppPreferences import com.owenlejeune.tvtime.preferences.AppPreferences
import com.owenlejeune.tvtime.utils.ResourceUtils import com.owenlejeune.tvtime.utils.ResourceUtils
import com.owenlejeune.tvtime.utils.SessionManager
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
import org.koin.core.component.inject import org.koin.core.component.inject
@@ -41,9 +42,39 @@ sealed class BottomNavItem(
} }
} }
object Movies: BottomNavItem(R.string.nav_movies_title, R.drawable.ic_movie, "movies_route", { it.moviesTabPosition }, { p, i -> p.moviesTabPosition = i } ) object Movies: BottomNavItem(
object TV: BottomNavItem(R.string.nav_tv_title, R.drawable.ic_tv, "tv_route", { it.tvTabPosition }, { p, i -> p.tvTabPosition = i } ) R.string.nav_movies_title,
object Account: BottomNavItem(R.string.nav_account_title, R.drawable.ic_person, "account_route", { it.accountTabPosition }, { p, i -> p.accountTabPosition = i } ) R.drawable.ic_movie,
object People: BottomNavItem(R.string.nav_people_title, R.drawable.ic_face, "people_route", { it.peopleTabPosition }, { p, i -> p.peopleTabPosition = i } ) "movies_route",
{ it.moviesTabPosition },
{ p, i -> p.moviesTabPosition = i }
)
object TV: BottomNavItem(
R.string.nav_tv_title,
R.drawable.ic_tv,
"tv_route",
{ it.tvTabPosition },
{ p, i -> p.tvTabPosition = i }
)
object Account: BottomNavItem(
R.string.nav_account_title,
R.drawable.ic_person,
"account_route",
{
// if (SessionManager.currentSession.value?.isAuthorized == true) {
// it.accountTabPosition
// } else {
-2
// }
},
{ p, i -> p.accountTabPosition = i }
)
object People: BottomNavItem(
R.string.nav_people_title,
R.drawable.ic_face,
"people_route",
{ it.peopleTabPosition },
{ p, i -> p.peopleTabPosition = i }
)
} }

View File

@@ -12,4 +12,6 @@ sealed class MainNavItem(val route: String) {
object SearchView: MainNavItem("search_route") object SearchView: MainNavItem("search_route")
object WebLinkView: MainNavItem("web_link_route") object WebLinkView: MainNavItem("web_link_route")
object AccountView: MainNavItem("account_route")
} }

View File

@@ -1,16 +1,13 @@
package com.owenlejeune.tvtime.ui.navigation package com.owenlejeune.tvtime.ui.navigation
import android.util.Log
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.RowScope
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.navArgument
import androidx.navigation.navDeepLink import androidx.navigation.navDeepLink
import com.owenlejeune.tvtime.ui.screens.main.MediaViewType import com.owenlejeune.tvtime.ui.screens.main.MediaViewType
import com.owenlejeune.tvtime.ui.screens.main.* import com.owenlejeune.tvtime.ui.screens.main.*
@@ -45,19 +42,8 @@ fun MainNavGraph(
fab = fab fab = fab
) )
} }
composable( composable(route = BottomNavItem.Account.route) {
route = BottomNavItem.Account.route, AccountViewContent(appNavController = appNavController)
deepLinks = listOf(
navDeepLink { uriPattern = "app://tvtime.auth.{${NavConstants.ACCOUNT_KEY}}" }
)
) {
val deepLink = it.arguments?.getString(NavConstants.ACCOUNT_KEY)
AccountTab(
appBarTitle = appBarTitle,
appNavController = appNavController,
appBarActions = appBarActions,
doSignInPartTwo = deepLink == NavConstants.AUTH_REDIRECT_PAGE
)
fab.value = {} fab.value = {}
} }
composable(BottomNavItem.People.route) { composable(BottomNavItem.People.route) {

View File

@@ -1,29 +1,25 @@
package com.owenlejeune.tvtime.ui.screens.main package com.owenlejeune.tvtime.ui.screens.main
import androidx.compose.animation.rememberSplineBasedDecay
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AccountCircle import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.layout.ContentScale
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.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.paging.compose.collectAsLazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems
import coil.compose.AsyncImage
import com.google.accompanist.pager.ExperimentalPagerApi import com.google.accompanist.pager.ExperimentalPagerApi
import com.google.accompanist.pager.HorizontalPager import com.google.accompanist.pager.HorizontalPager
import com.google.accompanist.pager.PagerState import com.google.accompanist.pager.PagerState
@@ -31,63 +27,104 @@ import com.google.accompanist.pager.rememberPagerState
import com.owenlejeune.tvtime.R import com.owenlejeune.tvtime.R
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.* import com.owenlejeune.tvtime.api.tmdb.api.v3.model.*
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.V4AccountList import com.owenlejeune.tvtime.api.tmdb.api.v4.model.V4AccountList
import com.owenlejeune.tvtime.api.tmdb.viewmodel.RecommendedMediaViewModel
import com.owenlejeune.tvtime.extensions.unlessEmpty import com.owenlejeune.tvtime.extensions.unlessEmpty
import com.owenlejeune.tvtime.ui.components.AccountIcon
import com.owenlejeune.tvtime.ui.components.PagingPosterGrid import com.owenlejeune.tvtime.ui.components.PagingPosterGrid
import com.owenlejeune.tvtime.ui.components.RoundedLetterImage import com.owenlejeune.tvtime.ui.components.ProfileMenuContainer
import com.owenlejeune.tvtime.ui.components.ProfileMenuOverlay
import com.owenlejeune.tvtime.ui.navigation.AccountTabNavItem import com.owenlejeune.tvtime.ui.navigation.AccountTabNavItem
import com.owenlejeune.tvtime.ui.navigation.ListFetchFun import com.owenlejeune.tvtime.ui.navigation.ListFetchFun
import com.owenlejeune.tvtime.ui.navigation.MainNavItem import com.owenlejeune.tvtime.ui.navigation.MainNavItem
import com.owenlejeune.tvtime.ui.screens.main.tabs.top.ScrollableTabs import com.owenlejeune.tvtime.ui.screens.main.tabs.top.ScrollableTabs
import com.owenlejeune.tvtime.api.tmdb.viewmodel.RecommendedMediaViewModel
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 kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlin.reflect.KClass import kotlin.reflect.KClass
@OptIn(ExperimentalPagerApi::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun AccountTab( fun AccountView(
appNavController: NavHostController, appNavController: NavHostController,
appBarTitle: MutableState<@Composable () -> Unit>,
appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({}),
doSignInPartTwo: Boolean = false doSignInPartTwo: Boolean = false
) { ) {
val currentSessionState = remember { SessionManager.currentSession } val currentSessionState = remember { SessionManager.currentSession }
val currentSession = currentSessionState.value val currentSession = currentSessionState.value
val showProfileMenuOverlay = remember { mutableStateOf(false) }
ProfileMenuContainer(
appNavController = appNavController,
visible = showProfileMenuOverlay.value,
onDismissRequest = { showProfileMenuOverlay.value = false }
) {
val decayAnimationSpec = rememberSplineBasedDecay<Float>()
val topAppBarScrollState = rememberTopAppBarScrollState()
val scrollBehavior = remember(decayAnimationSpec) {
TopAppBarDefaults.exitUntilCollapsedScrollBehavior(decayAnimationSpec, topAppBarScrollState)
}
Scaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
LargeTopAppBar(
scrollBehavior = scrollBehavior,
navigationIcon = {
IconButton(
onClick = { appNavController.popBackStack() }
) {
Icon(
Icons.Filled.ArrowBack,
contentDescription = null
)
}
},
title = {
if (currentSession?.isAuthorized == false) {
Text(text = stringResource(id = R.string.account_not_logged_in))
} else {
val accountDetails = remember { currentSession!!.accountDetails }
Text(text = getAccountName(accountDetails.value))
}
},
colors = TopAppBarDefaults.largeTopAppBarColors(scrolledContainerColor = MaterialTheme.colorScheme.background),
actions = {
AccountIcon(
modifier = Modifier.padding(start = 12.dp),
size = 32.dp,
onClick = { showProfileMenuOverlay.value = true }
)
Spacer(modifier = Modifier.width(8.dp))
}
)
}
) {
Box(modifier = Modifier.padding(it)) {
AccountViewContent(appNavController = appNavController, doSignInPartTwo = doSignInPartTwo)
}
}
}
}
@OptIn(ExperimentalPagerApi::class)
@Composable
fun AccountViewContent(
appNavController: NavHostController,
doSignInPartTwo: Boolean = false
) {
val currentSessionState = remember { SessionManager.currentSession }
val currentSession = currentSessionState.value
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
if (currentSession?.isAuthorized == false) { if (currentSession?.isAuthorized != true && doSignInPartTwo) {
appBarTitle.value = { Text(text = stringResource(id = R.string.account_not_logged_in)) }
if (doSignInPartTwo) {
AccountLoadingView() AccountLoadingView()
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
scope.launch { scope.launch {
SessionManager.singInPart2() SessionManager.signInPart2()
}
} }
} }
} else { } else {
if (currentSession?.isAuthorized == true) {
val accountDetails = remember { currentSession.accountDetails }
appBarTitle.value = { Text(text = stringResource(id = R.string.account_header_title_formatted, getAccountName(accountDetails.value))) }
} else {
appBarTitle.value = { Text(text = stringResource(id = R.string.account_not_logged_in)) }
}
appBarActions.value = {
AccountDropdownMenu(
session = currentSession,
appNavController = appNavController
)
}
currentSession?.let { currentSession?.let {
Column { Column {
AuthorizedSessionIcon()
val tabs = AccountTabNavItem.AuthorizedItems val tabs = AccountTabNavItem.AuthorizedItems
val pagerState = rememberPagerState() val pagerState = rememberPagerState()
ScrollableTabs(tabs = tabs, pagerState = pagerState) ScrollableTabs(tabs = tabs, pagerState = pagerState)
@@ -288,95 +325,6 @@ private fun MediaItemRow(
) )
} }
@Composable
private fun AccountDropdownMenu(session: SessionManager.Session?, appNavController: NavHostController) {
val expanded = remember { mutableStateOf(false) }
IconButton(
onClick = { expanded.value = true }
) {
Icon(imageVector = Icons.Filled.AccountCircle, contentDescription = stringResource(id = R.string.nav_account_title))
}
DropdownMenu(
expanded = expanded.value,
onDismissRequest = { expanded.value = false }
) {
if(session?.isAuthorized == true) {
AuthorizedSessionMenuItems(expanded = expanded)
} else {
NoSessionMenuItems(expanded = expanded, appNavController = appNavController)
}
}
}
@Composable
private fun AuthorizedSessionMenuItems(expanded: MutableState<Boolean>) {
DropdownMenuItem(
text = { Text(text = stringResource(id = R.string.action_sign_out)) },
onClick = {
signOut()
expanded.value = false
}
)
}
@Composable
private fun NoSessionMenuItems(expanded: MutableState<Boolean>, appNavController: NavHostController) {
val context = LocalContext.current
DropdownMenuItem(
text = { Text(text = stringResource(id = R.string.action_sign_in)) },
onClick = {
CoroutineScope(Dispatchers.IO).launch {
SessionManager.signInPart1(context) {
appNavController.navigate(MainNavItem.WebLinkView.route.plus("/$it"))
}
}
expanded.value = false
}
)
}
@Composable
private fun AuthorizedSessionIcon() {
val accountDetails = SessionManager.currentSession.value?.accountDetails?.value
val avatarUrl = accountDetails?.let {
when {
accountDetails.avatar.tmdb?.avatarPath?.isNotEmpty() == true -> {
TmdbUtils.getAccountAvatarUrl(accountDetails)
}
accountDetails.avatar.gravatar?.isDefault() == false -> {
TmdbUtils.getAccountGravatarUrl(accountDetails)
}
else -> null
}
}
Box(modifier = Modifier.padding(start = 12.dp)) {
if (accountDetails == null || avatarUrl == null) {
val accLetter = (accountDetails?.name?.ifEmpty { accountDetails.username } ?: " ")[0]
RoundedLetterImage(size = 60.dp, character = accLetter)
} else {
Box(modifier = Modifier.size(60.dp)) {
AsyncImage(
model = avatarUrl,
contentDescription = "",
modifier = Modifier
.size(60.dp)
.clip(CircleShape),
contentScale = ContentScale.Fit
)
}
}
}
}
private fun signOut() {
CoroutineScope(Dispatchers.IO).launch {
SessionManager.clearSession()
}
}
@OptIn(ExperimentalPagerApi::class) @OptIn(ExperimentalPagerApi::class)
@Composable @Composable

View File

@@ -2,7 +2,7 @@ package com.owenlejeune.tvtime.ui.screens.main
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@@ -22,7 +22,7 @@ fun PeopleTab(
appNavController: NavHostController, appNavController: NavHostController,
fab: MutableState<@Composable () -> Unit> fab: MutableState<@Composable () -> Unit>
) { ) {
val titleText = stringResource(id = R.string.nav_people_title) val titleText = stringResource(id = R.string.popular_today_header)
appBarTitle.value = { Text(text = titleText) } appBarTitle.value = { Text(text = titleText) }
Column { Column {
@@ -38,10 +38,10 @@ fun PeopleTab(
PagingPeoplePosterGrid( PagingPeoplePosterGrid(
lazyPagingItems = peopleList, lazyPagingItems = peopleList,
header = { header = {
Text( // Text(
text = stringResource(R.string.popular_today_header), // text = stringResource(R.string.popular_today_header),
modifier = Modifier.padding(start = 8.dp) // modifier = Modifier.padding(start = 8.dp)
) // )
}, },
onClick = { id -> onClick = { id ->
appNavController.navigate( appNavController.navigate(

View File

@@ -382,7 +382,7 @@ private fun HomeScreenPreferences(
SwitchPreference( SwitchPreference(
titleText = stringResource(R.string.preference_show_poster_titles_title), titleText = stringResource(R.string.preference_show_poster_titles_title),
subtitleText = stringResource(R.string.preference_show_poster_titles_subtitle), subtitleText = stringResource(R.string.preference_show_poster_titles_subtitle),
checkState = showTabLabels.value, checkState = showPosterTitles.value,
onCheckedChange = { isChecked -> onCheckedChange = { isChecked ->
showPosterTitles.value = isChecked showPosterTitles.value = isChecked
preferences.showPosterTitles = isChecked preferences.showPosterTitles = isChecked

View File

@@ -22,6 +22,7 @@ import androidx.navigation.NavController
import com.google.accompanist.web.AccompanistWebViewClient import com.google.accompanist.web.AccompanistWebViewClient
import com.google.accompanist.web.WebView import com.google.accompanist.web.WebView
import com.google.accompanist.web.rememberWebViewState import com.google.accompanist.web.rememberWebViewState
import com.owenlejeune.tvtime.utils.SessionManager
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
import org.koin.core.component.inject import org.koin.core.component.inject
@@ -37,7 +38,12 @@ fun WebLinkView(
title = {}, title = {},
navigationIcon = { navigationIcon = {
IconButton( IconButton(
onClick = { appNavController.popBackStack() } onClick = {
if (url.contains("auth")) {
SessionManager.cancelSignIn()
}
appNavController.popBackStack()
}
) { ) {
Icon( Icon(
imageVector = Icons.Filled.Close, imageVector = Icons.Filled.Close,
@@ -47,8 +53,8 @@ fun WebLinkView(
} }
) )
} }
) { ) { paddingValues ->
Box(modifier = Modifier.padding(it)) { Box(modifier = Modifier.padding(paddingValues)) {
val webViewState = rememberWebViewState(url = url) val webViewState = rememberWebViewState(url = url)
WebView( WebView(
state = webViewState, state = webViewState,

View File

@@ -115,13 +115,7 @@ fun TVTimeTheme(
content = { content = {
val systemUiController = rememberSystemUiController() val systemUiController = rememberSystemUiController()
systemUiController.setStatusBarColor(color = androidx.compose.material3.MaterialTheme.colorScheme.background) systemUiController.setStatusBarColor(color = androidx.compose.material3.MaterialTheme.colorScheme.background)
systemUiController.setNavigationBarColor( systemUiController.setNavigationBarColor(color = androidx.compose.material3.MaterialTheme.colorScheme.background)
color = androidx.compose.material3.MaterialTheme.colorScheme.primary.copy(
alpha = 0.08f
).compositeOver(
background = androidx.compose.material3.MaterialTheme.colorScheme.surface
)
)
content() content()
} }

View File

@@ -33,7 +33,7 @@ class HomeTabRecyclerAdapter: RecyclerView.Adapter<HomeTabRecyclerAdapter.TabVie
init { init {
val visiblePages = BottomNavItem.Items.filter { it.order > -1 }.sortedBy { it.order } val visiblePages = BottomNavItem.Items.filter { it.order > -1 }.sortedBy { it.order }
val hiddenPages = BottomNavItem.Items.filter { it.order < 0 } val hiddenPages = BottomNavItem.Items.filter { it.order == -1 }
pages = ArrayList<BottomNavItem?>().apply { pages = ArrayList<BottomNavItem?>().apply {
addAll(visiblePages) addAll(visiblePages)
add(null) add(null)

View File

@@ -1,8 +1,6 @@
package com.owenlejeune.tvtime.utils package com.owenlejeune.tvtime.utils
import android.content.Context import android.content.Context
import android.content.Intent
import android.net.Uri
import android.widget.Toast import android.widget.Toast
import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@@ -18,7 +16,6 @@ import com.owenlejeune.tvtime.api.tmdb.api.v4.model.AuthDeleteBody
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.AuthRequestBody import com.owenlejeune.tvtime.api.tmdb.api.v4.model.AuthRequestBody
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.V4AccountList import com.owenlejeune.tvtime.api.tmdb.api.v4.model.V4AccountList
import com.owenlejeune.tvtime.preferences.AppPreferences import com.owenlejeune.tvtime.preferences.AppPreferences
import com.owenlejeune.tvtime.ui.navigation.AccountTabNavItem
import com.owenlejeune.tvtime.ui.screens.main.MediaViewType import com.owenlejeune.tvtime.ui.screens.main.MediaViewType
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -59,6 +56,12 @@ object SessionManager: KoinComponent {
} }
} }
fun cancelSignIn() {
if (currentSession.value is InProgressSession) {
currentSession.value = null
}
}
suspend fun initialize() { suspend fun initialize() {
preferences.authorizedSessionValues?.let { values -> preferences.authorizedSessionValues?.let { values ->
val session = AuthorizedSession( val session = AuthorizedSession(
@@ -79,17 +82,17 @@ object SessionManager: KoinComponent {
val requestTokenResponse = service.createRequestToken(AuthRequestBody(redirect = "app://tvtime.auth.return")) val requestTokenResponse = service.createRequestToken(AuthRequestBody(redirect = "app://tvtime.auth.return"))
if (requestTokenResponse.isSuccessful) { if (requestTokenResponse.isSuccessful) {
requestTokenResponse.body()?.let { ctr -> requestTokenResponse.body()?.let { ctr ->
currentSession.value = InProgressSession(ctr.requestToken)
val url = context.getString(R.string.tmdb_auth_url, ctr.requestToken) val url = context.getString(R.string.tmdb_auth_url, ctr.requestToken)
val encodedUrl = URLEncoder.encode(url, StandardCharsets.UTF_8.toString()) val encodedUrl = URLEncoder.encode(url, StandardCharsets.UTF_8.toString())
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
onRedirect(encodedUrl) onRedirect(encodedUrl)
} }
currentSession.value = InProgressSession(ctr.requestToken)
} }
} }
} }
suspend fun singInPart2( suspend fun signInPart2(
context: Context = get(Context::class.java) context: Context = get(Context::class.java)
) { ) {
if (currentSession.value is InProgressSession) { if (currentSession.value is InProgressSession) {

View File

@@ -114,6 +114,7 @@
<string name="account_header_title_formatted">Hello, %1$s!</string> <string name="account_header_title_formatted">Hello, %1$s!</string>
<string name="account_name_guest">Guest</string> <string name="account_name_guest">Guest</string>
<string name="account_not_logged_in">Not logged in</string> <string name="account_not_logged_in">Not logged in</string>
<string name="account_add">Add Account</string>
<string name="no_rated_content_message">No rated content</string> <string name="no_rated_content_message">No rated content</string>
<string name="rating_test">Rating: %1$d</string> <string name="rating_test">Rating: %1$d</string>