mirror of
https://github.com/owenlejeune/TVTime.git
synced 2025-11-22 19:50:54 -05:00
move account and sign in/out options to overlay menu
This commit is contained in:
@@ -1,20 +1,18 @@
|
||||
package com.owenlejeune.tvtime
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.animation.rememberSplineBasedDecay
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
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.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Modifier
|
||||
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.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
@@ -23,7 +21,6 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavGraph.Companion.findStartDestination
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.NavType
|
||||
import androidx.navigation.compose.NavHost
|
||||
@@ -31,11 +28,17 @@ import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import androidx.navigation.navArgument
|
||||
import androidx.navigation.navDeepLink
|
||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||
import com.kieronquinn.monetcompat.app.MonetCompatActivity
|
||||
import com.owenlejeune.tvtime.extensions.WindowSizeClass
|
||||
import com.owenlejeune.tvtime.extensions.navigateInBottomBar
|
||||
import com.owenlejeune.tvtime.extensions.rememberWindowSizeClass
|
||||
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.MainNavGraph
|
||||
import com.owenlejeune.tvtime.ui.navigation.MainNavItem
|
||||
@@ -89,8 +92,6 @@ class MainActivity : MonetCompatActivity() {
|
||||
preferences: AppPreferences = get(AppPreferences::class.java)
|
||||
) {
|
||||
val navController = rememberNavController()
|
||||
val navBackStackEntry by navController.currentBackStackEntryAsState()
|
||||
val currentRoute = navBackStackEntry?.destination?.route
|
||||
|
||||
val decayAnimationSpec = rememberSplineBasedDecay<Float>()
|
||||
val topAppBarScrollState = rememberTopAppBarScrollState()
|
||||
@@ -102,6 +103,23 @@ class MainActivity : MonetCompatActivity() {
|
||||
val appBarActions = remember { mutableStateOf<@Composable RowScope.() -> 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(
|
||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
topBar = {
|
||||
@@ -110,7 +128,8 @@ class MainActivity : MonetCompatActivity() {
|
||||
appNavController = appNavController,
|
||||
title = appBarTitle.value,
|
||||
scrollBehavior = scrollBehavior,
|
||||
appBarActions = appBarActions
|
||||
appBarActions = appBarActions,
|
||||
navigationIcon = navigationIcon
|
||||
)
|
||||
}
|
||||
},
|
||||
@@ -132,28 +151,22 @@ class MainActivity : MonetCompatActivity() {
|
||||
appBarTitle = appBarTitle,
|
||||
appBarActions = appBarActions,
|
||||
topBarScrollBehaviour = scrollBehavior,
|
||||
mainNavStartRoute = mainNavStartRoute
|
||||
mainNavStartRoute = mainNavStartRoute,
|
||||
navigationIcon = navigationIcon
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TopBar(
|
||||
appNavController: NavHostController,
|
||||
title: @Composable () -> Unit,
|
||||
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(
|
||||
title = title,
|
||||
scrollBehavior = scrollBehavior,
|
||||
@@ -162,9 +175,9 @@ class MainActivity : MonetCompatActivity() {
|
||||
scrolledContainerColor = MaterialTheme.colorScheme.background
|
||||
),
|
||||
actions = {
|
||||
defaultAppBarActions()
|
||||
appBarActions.value(this)
|
||||
}
|
||||
},
|
||||
navigationIcon = navigationIcon
|
||||
)
|
||||
}
|
||||
|
||||
@@ -208,6 +221,7 @@ class MainActivity : MonetCompatActivity() {
|
||||
topBarScrollBehaviour: TopAppBarScrollBehavior,
|
||||
appBarTitle: MutableState<@Composable () -> Unit>,
|
||||
appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({}),
|
||||
navigationIcon: @Composable () -> Unit = {},
|
||||
mainNavStartRoute: String = BottomNavItem.SortedItems[0].route
|
||||
) {
|
||||
if (windowSize == WindowSizeClass.Expanded) {
|
||||
@@ -218,7 +232,8 @@ class MainActivity : MonetCompatActivity() {
|
||||
appBarTitle = appBarTitle,
|
||||
appBarActions = appBarActions,
|
||||
topBarScrollBehaviour = topBarScrollBehaviour,
|
||||
mainNavStartRoute = mainNavStartRoute
|
||||
mainNavStartRoute = mainNavStartRoute,
|
||||
navigationIcon = navigationIcon
|
||||
)
|
||||
} else {
|
||||
SingleColumnMainContent(
|
||||
@@ -259,6 +274,7 @@ class MainActivity : MonetCompatActivity() {
|
||||
topBarScrollBehaviour: TopAppBarScrollBehavior,
|
||||
appBarTitle: MutableState<@Composable () -> Unit>,
|
||||
appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({}),
|
||||
navigationIcon: @Composable () -> Unit = {},
|
||||
mainNavStartRoute: String = BottomNavItem.SortedItems[0].route,
|
||||
preferences: AppPreferences = get(AppPreferences::class.java)
|
||||
) {
|
||||
@@ -291,7 +307,8 @@ class MainActivity : MonetCompatActivity() {
|
||||
appNavController = appNavController,
|
||||
title = appBarTitle.value,
|
||||
scrollBehavior = topBarScrollBehaviour,
|
||||
appBarActions = appBarActions
|
||||
appBarActions = appBarActions,
|
||||
navigationIcon = navigationIcon
|
||||
)
|
||||
MainMediaView(
|
||||
appNavController = appNavController,
|
||||
@@ -435,6 +452,18 @@ class MainActivity : MonetCompatActivity() {
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,141 +1,282 @@
|
||||
package com.owenlejeune.tvtime.ui.components
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.DropdownMenu
|
||||
import androidx.compose.material3.Divider
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxScope
|
||||
import androidx.compose.foundation.layout.Column
|
||||
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.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
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.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.unit.DpOffset
|
||||
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
|
||||
fun TopAppBarDropdownMenu(
|
||||
icon: @Composable () -> Unit = {},
|
||||
content: @Composable ColumnScope.(expanded: MutableState<Boolean>) -> 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
|
||||
fun ProfileMenuOverlay(
|
||||
appNavController: NavController,
|
||||
visible: Boolean,
|
||||
onDismissRequest: () -> Unit
|
||||
) {
|
||||
if (visible) {
|
||||
val context = LocalContext.current
|
||||
Box(
|
||||
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()
|
||||
.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,
|
||||
color = MaterialTheme.colorScheme.background,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 15.dp, vertical = 10.dp)
|
||||
.clickable(onClick = onClick)
|
||||
.fillMaxWidth(),
|
||||
textAlign = TextAlign.Center
|
||||
text = currentSession.accountDetails.value?.name ?: "",
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CustomMenuDivider() {
|
||||
Divider(color = Color.Transparent, modifier = Modifier.padding(vertical = 2.dp))
|
||||
}
|
||||
MenuDivider()
|
||||
|
||||
@Composable
|
||||
fun TopAppBarDialogMenu(
|
||||
icon: @Composable () -> Unit = {},
|
||||
content: @Composable (showing: MutableState<Boolean>) -> Unit = {}
|
||||
) {
|
||||
val expanded = remember { mutableStateOf(false) }
|
||||
|
||||
Box(
|
||||
modifier = Modifier.wrapContentSize(Alignment.TopEnd)
|
||||
) {
|
||||
IconButton(
|
||||
ProfileMenuItem(
|
||||
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) {
|
||||
Dialog(
|
||||
onDismissRequest = { expanded.value = false },
|
||||
content = { content(expanded) }
|
||||
MenuDivider()
|
||||
|
||||
currentSession?.let {
|
||||
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 = {}
|
||||
// )
|
||||
}
|
||||
}
|
||||
@@ -247,13 +247,16 @@ fun PosterItem(
|
||||
colors = CardDefaults.cardColors(containerColor = Color.Transparent)
|
||||
) {
|
||||
var backgroundColor by remember { mutableStateOf(Color.Gray) }
|
||||
val m = if (backgroundColor == Color.Transparent) {
|
||||
Modifier.wrapContentHeight()
|
||||
} else {
|
||||
Modifier.height(POSTER_HEIGHT)
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
modifier = m
|
||||
.width(width = width)
|
||||
.height(height = POSTER_HEIGHT)
|
||||
.background(color = backgroundColor)
|
||||
.clip(RoundedCornerShape(5.dp))
|
||||
.onGloballyPositioned { sizeImage = it.size }
|
||||
) {
|
||||
var bgIcon by remember { mutableStateOf(placeholder) }
|
||||
Icon(
|
||||
@@ -286,7 +289,7 @@ fun PosterItem(
|
||||
},
|
||||
model = url,
|
||||
contentDescription = title,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
contentScale = ContentScale.FillBounds,
|
||||
onSuccess = { backgroundColor = Color.Transparent }
|
||||
)
|
||||
|
||||
|
||||
@@ -17,12 +17,14 @@ import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
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.ArrowDropUp
|
||||
import androidx.compose.material.icons.filled.Error
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.material.icons.filled.Visibility
|
||||
import androidx.compose.material.icons.filled.VisibilityOff
|
||||
import androidx.compose.material.icons.outlined.AccountCircle
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
@@ -65,9 +67,11 @@ import coil.compose.rememberAsyncImagePainter
|
||||
import com.google.accompanist.flowlayout.FlowRow
|
||||
import com.owenlejeune.tvtime.R
|
||||
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.ui.navigation.MainNavItem
|
||||
import com.owenlejeune.tvtime.ui.screens.main.MediaViewType
|
||||
import com.owenlejeune.tvtime.utils.SessionManager
|
||||
import com.owenlejeune.tvtime.utils.TmdbUtils
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -696,34 +700,90 @@ fun AvatarImage(
|
||||
contentDescription = ""
|
||||
)
|
||||
} else {
|
||||
val text = if (author.name.isNotEmpty()) author.name[0] else author.username[0]
|
||||
RoundedLetterImage(
|
||||
size = size,
|
||||
character = text
|
||||
val name = author.name.unlessEmpty(author.username)
|
||||
UserInitials(size = size, name = name)
|
||||
}
|
||||
}
|
||||
|
||||
@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
|
||||
fun RoundedLetterImage(
|
||||
size: Dp,
|
||||
character: Char,
|
||||
modifier: Modifier = Modifier
|
||||
fun AccountIcon(
|
||||
modifier: Modifier = Modifier,
|
||||
size: Dp = 60.dp,
|
||||
onClick: () -> Unit = {},
|
||||
enabled: Boolean = true
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
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
|
||||
.clip(CircleShape)
|
||||
.size(size)
|
||||
.background(color = MaterialTheme.colorScheme.tertiary)
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.align(Alignment.Center),
|
||||
text = character.uppercase(),
|
||||
color = MaterialTheme.colorScheme.onTertiary,
|
||||
textAlign = TextAlign.Center,
|
||||
style = MaterialTheme.typography.titleLarge
|
||||
.clickable(
|
||||
enabled = enabled,
|
||||
onClick = onClick
|
||||
)
|
||||
) {
|
||||
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)
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package com.owenlejeune.tvtime.ui.navigation
|
||||
import com.owenlejeune.tvtime.R
|
||||
import com.owenlejeune.tvtime.preferences.AppPreferences
|
||||
import com.owenlejeune.tvtime.utils.ResourceUtils
|
||||
import com.owenlejeune.tvtime.utils.SessionManager
|
||||
import org.koin.core.component.KoinComponent
|
||||
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 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", { it.accountTabPosition }, { 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 } )
|
||||
object Movies: BottomNavItem(
|
||||
R.string.nav_movies_title,
|
||||
R.drawable.ic_movie,
|
||||
"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 }
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
@@ -12,4 +12,6 @@ sealed class MainNavItem(val route: String) {
|
||||
object SearchView: MainNavItem("search_route")
|
||||
object WebLinkView: MainNavItem("web_link_route")
|
||||
|
||||
object AccountView: MainNavItem("account_route")
|
||||
|
||||
}
|
||||
@@ -1,16 +1,13 @@
|
||||
package com.owenlejeune.tvtime.ui.navigation
|
||||
|
||||
import android.util.Log
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.NavType
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.navArgument
|
||||
import androidx.navigation.navDeepLink
|
||||
import com.owenlejeune.tvtime.ui.screens.main.MediaViewType
|
||||
import com.owenlejeune.tvtime.ui.screens.main.*
|
||||
@@ -45,19 +42,8 @@ fun MainNavGraph(
|
||||
fab = fab
|
||||
)
|
||||
}
|
||||
composable(
|
||||
route = BottomNavItem.Account.route,
|
||||
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
|
||||
)
|
||||
composable(route = BottomNavItem.Account.route) {
|
||||
AccountViewContent(appNavController = appNavController)
|
||||
fab.value = {}
|
||||
}
|
||||
composable(BottomNavItem.People.route) {
|
||||
|
||||
@@ -1,29 +1,25 @@
|
||||
package com.owenlejeune.tvtime.ui.screens.main
|
||||
|
||||
import androidx.compose.animation.rememberSplineBasedDecay
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
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.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import coil.compose.AsyncImage
|
||||
import com.google.accompanist.pager.ExperimentalPagerApi
|
||||
import com.google.accompanist.pager.HorizontalPager
|
||||
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.api.tmdb.api.v3.model.*
|
||||
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.ui.components.AccountIcon
|
||||
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.ListFetchFun
|
||||
import com.owenlejeune.tvtime.ui.navigation.MainNavItem
|
||||
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.TmdbUtils
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
@OptIn(ExperimentalPagerApi::class)
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun AccountTab(
|
||||
fun AccountView(
|
||||
appNavController: NavHostController,
|
||||
appBarTitle: MutableState<@Composable () -> Unit>,
|
||||
appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({}),
|
||||
doSignInPartTwo: Boolean = false
|
||||
) {
|
||||
val currentSessionState = remember { SessionManager.currentSession }
|
||||
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()
|
||||
|
||||
if (currentSession?.isAuthorized == false) {
|
||||
appBarTitle.value = { Text(text = stringResource(id = R.string.account_not_logged_in)) }
|
||||
if (doSignInPartTwo) {
|
||||
if (currentSession?.isAuthorized != true && doSignInPartTwo) {
|
||||
AccountLoadingView()
|
||||
LaunchedEffect(Unit) {
|
||||
scope.launch {
|
||||
SessionManager.singInPart2()
|
||||
}
|
||||
SessionManager.signInPart2()
|
||||
}
|
||||
}
|
||||
} 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 {
|
||||
Column {
|
||||
AuthorizedSessionIcon()
|
||||
|
||||
val tabs = AccountTabNavItem.AuthorizedItems
|
||||
val pagerState = rememberPagerState()
|
||||
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)
|
||||
@Composable
|
||||
@@ -2,7 +2,7 @@ package com.owenlejeune.tvtime.ui.screens.main
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
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.MutableState
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -22,7 +22,7 @@ fun PeopleTab(
|
||||
appNavController: NavHostController,
|
||||
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) }
|
||||
|
||||
Column {
|
||||
@@ -38,10 +38,10 @@ fun PeopleTab(
|
||||
PagingPeoplePosterGrid(
|
||||
lazyPagingItems = peopleList,
|
||||
header = {
|
||||
Text(
|
||||
text = stringResource(R.string.popular_today_header),
|
||||
modifier = Modifier.padding(start = 8.dp)
|
||||
)
|
||||
// Text(
|
||||
// text = stringResource(R.string.popular_today_header),
|
||||
// modifier = Modifier.padding(start = 8.dp)
|
||||
// )
|
||||
},
|
||||
onClick = { id ->
|
||||
appNavController.navigate(
|
||||
|
||||
@@ -382,7 +382,7 @@ private fun HomeScreenPreferences(
|
||||
SwitchPreference(
|
||||
titleText = stringResource(R.string.preference_show_poster_titles_title),
|
||||
subtitleText = stringResource(R.string.preference_show_poster_titles_subtitle),
|
||||
checkState = showTabLabels.value,
|
||||
checkState = showPosterTitles.value,
|
||||
onCheckedChange = { isChecked ->
|
||||
showPosterTitles.value = isChecked
|
||||
preferences.showPosterTitles = isChecked
|
||||
|
||||
@@ -22,6 +22,7 @@ import androidx.navigation.NavController
|
||||
import com.google.accompanist.web.AccompanistWebViewClient
|
||||
import com.google.accompanist.web.WebView
|
||||
import com.google.accompanist.web.rememberWebViewState
|
||||
import com.owenlejeune.tvtime.utils.SessionManager
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
|
||||
@@ -37,7 +38,12 @@ fun WebLinkView(
|
||||
title = {},
|
||||
navigationIcon = {
|
||||
IconButton(
|
||||
onClick = { appNavController.popBackStack() }
|
||||
onClick = {
|
||||
if (url.contains("auth")) {
|
||||
SessionManager.cancelSignIn()
|
||||
}
|
||||
appNavController.popBackStack()
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Close,
|
||||
@@ -47,8 +53,8 @@ fun WebLinkView(
|
||||
}
|
||||
)
|
||||
}
|
||||
) {
|
||||
Box(modifier = Modifier.padding(it)) {
|
||||
) { paddingValues ->
|
||||
Box(modifier = Modifier.padding(paddingValues)) {
|
||||
val webViewState = rememberWebViewState(url = url)
|
||||
WebView(
|
||||
state = webViewState,
|
||||
|
||||
@@ -115,13 +115,7 @@ fun TVTimeTheme(
|
||||
content = {
|
||||
val systemUiController = rememberSystemUiController()
|
||||
systemUiController.setStatusBarColor(color = androidx.compose.material3.MaterialTheme.colorScheme.background)
|
||||
systemUiController.setNavigationBarColor(
|
||||
color = androidx.compose.material3.MaterialTheme.colorScheme.primary.copy(
|
||||
alpha = 0.08f
|
||||
).compositeOver(
|
||||
background = androidx.compose.material3.MaterialTheme.colorScheme.surface
|
||||
)
|
||||
)
|
||||
systemUiController.setNavigationBarColor(color = androidx.compose.material3.MaterialTheme.colorScheme.background)
|
||||
|
||||
content()
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ class HomeTabRecyclerAdapter: RecyclerView.Adapter<HomeTabRecyclerAdapter.TabVie
|
||||
|
||||
init {
|
||||
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 {
|
||||
addAll(visiblePages)
|
||||
add(null)
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package com.owenlejeune.tvtime.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.widget.Toast
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
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.V4AccountList
|
||||
import com.owenlejeune.tvtime.preferences.AppPreferences
|
||||
import com.owenlejeune.tvtime.ui.navigation.AccountTabNavItem
|
||||
import com.owenlejeune.tvtime.ui.screens.main.MediaViewType
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -59,6 +56,12 @@ object SessionManager: KoinComponent {
|
||||
}
|
||||
}
|
||||
|
||||
fun cancelSignIn() {
|
||||
if (currentSession.value is InProgressSession) {
|
||||
currentSession.value = null
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun initialize() {
|
||||
preferences.authorizedSessionValues?.let { values ->
|
||||
val session = AuthorizedSession(
|
||||
@@ -79,17 +82,17 @@ object SessionManager: KoinComponent {
|
||||
val requestTokenResponse = service.createRequestToken(AuthRequestBody(redirect = "app://tvtime.auth.return"))
|
||||
if (requestTokenResponse.isSuccessful) {
|
||||
requestTokenResponse.body()?.let { ctr ->
|
||||
currentSession.value = InProgressSession(ctr.requestToken)
|
||||
val url = context.getString(R.string.tmdb_auth_url, ctr.requestToken)
|
||||
val encodedUrl = URLEncoder.encode(url, StandardCharsets.UTF_8.toString())
|
||||
withContext(Dispatchers.Main) {
|
||||
onRedirect(encodedUrl)
|
||||
}
|
||||
currentSession.value = InProgressSession(ctr.requestToken)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun singInPart2(
|
||||
suspend fun signInPart2(
|
||||
context: Context = get(Context::class.java)
|
||||
) {
|
||||
if (currentSession.value is InProgressSession) {
|
||||
|
||||
@@ -114,6 +114,7 @@
|
||||
<string name="account_header_title_formatted">Hello, %1$s!</string>
|
||||
<string name="account_name_guest">Guest</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="rating_test">Rating: %1$d</string>
|
||||
|
||||
Reference in New Issue
Block a user