mirror of
https://github.com/owenlejeune/TVTime.git
synced 2025-11-24 04:30:54 -05:00
add onboarding pages
This commit is contained in:
@@ -13,7 +13,7 @@ class AppRoutingActivity: AppCompatActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
if (preferences.firstLaunch) {
|
||||
if (preferences.firstLaunchTesting || preferences.firstLaunch) {
|
||||
launchActivity(OnboardingActivity::class.java)
|
||||
} else {
|
||||
launchActivity(MainActivity::class.java)
|
||||
|
||||
@@ -7,15 +7,13 @@ import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.ArrowForward
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
@@ -63,16 +61,38 @@ class OnboardingActivity: MonetCompatActivity() {
|
||||
Column(
|
||||
modifier = Modifier.background(color = MaterialTheme.colorScheme.background)
|
||||
) {
|
||||
val isFirstPage = pagerState.currentPage == 0
|
||||
val isLastPage = pagerState.currentPage == OnboardingPage.values().size - 1
|
||||
|
||||
if (!isFirstPage) {
|
||||
IconButton(
|
||||
modifier = Modifier.padding(12.dp),
|
||||
onClick = {
|
||||
coroutineScope.launch {
|
||||
pagerState.animateScrollToPage(pagerState.currentPage - 1)
|
||||
}
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.ArrowBack,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onBackground
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val nextButtonEnabled = remember { mutableStateOf(true) }
|
||||
HorizontalPager(
|
||||
state = pagerState,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f),
|
||||
// .background(MaterialTheme.colorScheme.primary),
|
||||
count = OnboardingPage.values().size,
|
||||
userScrollEnabled = false
|
||||
) { page ->
|
||||
OnboardingPageUi(page = OnboardingPage[page])
|
||||
Box(modifier = Modifier.padding(start = 12.dp, end = 24.dp)) {
|
||||
OnboardingPageUi(page = OnboardingPage[page], nextButtonEnabled)
|
||||
}
|
||||
}
|
||||
|
||||
Box(
|
||||
@@ -80,6 +100,17 @@ class OnboardingActivity: MonetCompatActivity() {
|
||||
.fillMaxWidth()
|
||||
.padding(all = 12.dp)
|
||||
) {
|
||||
Button(
|
||||
modifier = Modifier.align(Alignment.CenterStart),
|
||||
enabled = true,
|
||||
onClick = {
|
||||
preferences.firstLaunch = false
|
||||
launchActivity(MainActivity::class.java)
|
||||
}
|
||||
) {
|
||||
Text(text = "Skip")
|
||||
}
|
||||
|
||||
HorizontalPagerIndicator(
|
||||
pagerState = pagerState,
|
||||
modifier = Modifier
|
||||
@@ -88,10 +119,10 @@ class OnboardingActivity: MonetCompatActivity() {
|
||||
activeColor = MaterialTheme.colorScheme.secondary
|
||||
)
|
||||
|
||||
val isLastPage = pagerState.currentPage == OnboardingPage.values().size - 1
|
||||
Button(
|
||||
modifier = Modifier.align(Alignment.CenterEnd),
|
||||
shape = CircleShape,
|
||||
enabled = nextButtonEnabled.value,
|
||||
onClick = {
|
||||
if (isLastPage) {
|
||||
preferences.firstLaunch = false
|
||||
@@ -103,7 +134,7 @@ class OnboardingActivity: MonetCompatActivity() {
|
||||
}
|
||||
}
|
||||
) {
|
||||
if (isLastPage) {
|
||||
if (!isLastPage) {
|
||||
Text(stringResource(id = R.string.get_started))
|
||||
} else {
|
||||
Icon(imageVector = Icons.Filled.ArrowForward, contentDescription = null)
|
||||
@@ -114,7 +145,7 @@ class OnboardingActivity: MonetCompatActivity() {
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun OnboardingPageUi(page: OnboardingPage) {
|
||||
fun OnboardingPageUi(page: OnboardingPage, nextButtonEnabled: MutableState<Boolean>) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
@@ -122,8 +153,8 @@ class OnboardingActivity: MonetCompatActivity() {
|
||||
Image(
|
||||
painter = painterResource(id = it),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(200.dp),
|
||||
colorFilter = ColorFilter.tint(color = MaterialTheme.colorScheme.secondary)
|
||||
modifier = Modifier.size(100.dp),
|
||||
colorFilter = ColorFilter.tint(color = MaterialTheme.colorScheme.primary)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
}
|
||||
@@ -140,8 +171,8 @@ class OnboardingActivity: MonetCompatActivity() {
|
||||
fontSize = 14.sp,
|
||||
color = MaterialTheme.colorScheme.onBackground
|
||||
)
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
page.additionalContent(this@OnboardingActivity)
|
||||
Spacer(modifier = Modifier.height(50.dp))
|
||||
page.additionalContent(this@OnboardingActivity, nextButtonEnabled)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -126,7 +126,7 @@ class AppPreferences(context: Context) {
|
||||
set(value) { preferences.put(FIRST_LAUNCH_TESTING, value) }
|
||||
|
||||
var firstLaunch: Boolean
|
||||
get() = if (BuildConfig.DEBUG) firstLaunchTesting else preferences.getBoolean(FIRST_LAUNCH, true)
|
||||
get() = preferences.getBoolean(FIRST_LAUNCH, true)
|
||||
set(value) { preferences.put(FIRST_LAUNCH, value) }
|
||||
|
||||
val useV4ApiDefault: Boolean = false
|
||||
|
||||
@@ -119,6 +119,7 @@ fun RadioButtonPreference(
|
||||
selected: Boolean,
|
||||
onClick: () -> Unit,
|
||||
title: String,
|
||||
description: String? = null,
|
||||
icon: ImageVector,
|
||||
titleTextColor: Color = MaterialTheme.colorScheme.onBackground,
|
||||
disabledTextColor: Color = MaterialTheme.colorScheme.outline
|
||||
@@ -142,19 +143,31 @@ fun RadioButtonPreference(
|
||||
)
|
||||
|
||||
val titleColor = if (enabled) titleTextColor else disabledTextColor
|
||||
Text(
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterVertically),
|
||||
.align(Alignment.CenterVertically)
|
||||
.padding(end = 36.dp)
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
color = titleColor,
|
||||
fontSize = 20.sp
|
||||
)
|
||||
description?.let {
|
||||
Text(
|
||||
text = description,
|
||||
color = titleColor,
|
||||
fontSize = 14.sp
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
RadioButton(
|
||||
modifier = Modifier.align(Alignment.CenterVertically),
|
||||
modifier = Modifier.align(Alignment.CenterVertically).weight(1f),
|
||||
selected = selected,
|
||||
onClick = null
|
||||
)
|
||||
|
||||
@@ -173,10 +173,7 @@ private fun BackdropGallery(
|
||||
with (pagerState) {
|
||||
val target = if (currentPage < pageCount - 1) currentPage + 1 else 0
|
||||
|
||||
animateScrollToPage(
|
||||
page = target,
|
||||
pageOffset = 0f
|
||||
)
|
||||
pagerState.animateScrollToPage(target)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +1,197 @@
|
||||
package com.owenlejeune.tvtime.ui.screens.onboarding
|
||||
|
||||
import android.os.Build
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Brightness6
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.material.icons.outlined.DarkMode
|
||||
import androidx.compose.material.icons.outlined.LightMode
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.kieronquinn.monetcompat.core.MonetCompat
|
||||
import com.owenlejeune.tvtime.R
|
||||
import com.owenlejeune.tvtime.preferences.AppPreferences
|
||||
import com.owenlejeune.tvtime.ui.components.RadioButtonPreference
|
||||
import com.owenlejeune.tvtime.ui.components.SearchBar
|
||||
import com.owenlejeune.tvtime.ui.components.SwitchPreference
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
|
||||
sealed class OnboardingPage(
|
||||
val order: Int,
|
||||
@StringRes val title: Int,
|
||||
@StringRes val description: Int,
|
||||
@DrawableRes val image: Int? = null,
|
||||
val additionalContent: @Composable (AppCompatActivity) -> Unit = {}
|
||||
val additionalContent: @Composable (AppCompatActivity, MutableState<Boolean>) -> Unit = { a, e -> }
|
||||
) {
|
||||
|
||||
companion object {
|
||||
fun values() = listOf(ExamplePage)
|
||||
companion object: KoinComponent {
|
||||
private val preferences: AppPreferences by inject()
|
||||
|
||||
fun values() = listOf(IntroPage, SearchViewSettings, SearchTypeSettings, DarkModePage, SystemColorPage).sortedBy { it.order }
|
||||
|
||||
operator fun get(i: Int) = values()[i]
|
||||
}
|
||||
|
||||
object ExamplePage: OnboardingPage(
|
||||
R.string.app_name,
|
||||
R.string.example_page_desc,
|
||||
R.drawable.ic_launcher_foreground
|
||||
object IntroPage: OnboardingPage(
|
||||
order = 0,
|
||||
title = R.string.app_name,
|
||||
description = R.string.intro_page_desc,
|
||||
image = R.drawable.ic_launcher_foreground
|
||||
)
|
||||
|
||||
object SignInPage: OnboardingPage(
|
||||
order = 1,
|
||||
title = R.string.sign_in_page_title,
|
||||
description = R.string.sign_in_page_description,
|
||||
image = null,
|
||||
additionalContent = @Composable { activity, nextButtonEnabled ->
|
||||
|
||||
}
|
||||
)
|
||||
|
||||
object SearchViewSettings: OnboardingPage(
|
||||
order = 2,
|
||||
title = R.string.search_page_title,
|
||||
description = R.string.search_page_description,
|
||||
image = R.drawable.ic_search,
|
||||
additionalContent = @Composable { _, _ ->
|
||||
val persistentSearch = remember { mutableStateOf(preferences.showSearchBar) }
|
||||
|
||||
if (persistentSearch.value) {
|
||||
SearchBar(placeholder = "") {}
|
||||
} else {
|
||||
FloatingActionButton(onClick = {}) {
|
||||
Icon(
|
||||
Icons.Filled.Search,
|
||||
stringResource(id = R.string.preference_heading_search)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
|
||||
SwitchPreference(
|
||||
titleText = stringResource(R.string.preferences_persistent_search_title),
|
||||
subtitleText = stringResource(R.string.preferences_persistent_search_subtitle),
|
||||
checkState = persistentSearch.value,
|
||||
onCheckedChange = { isChecked ->
|
||||
persistentSearch.value = isChecked
|
||||
preferences.showSearchBar = isChecked
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
object SearchTypeSettings: OnboardingPage(
|
||||
order = 3,
|
||||
title = R.string.search_type_title,
|
||||
description = R.string.search_type_description,
|
||||
image = R.drawable.ic_search,
|
||||
additionalContent = @Composable { _, _ ->
|
||||
val multiSearch = remember { mutableStateOf(preferences.multiSearch) }
|
||||
SwitchPreference(
|
||||
titleText = stringResource(R.string.preference_multi_search_title),
|
||||
subtitleText = stringResource(R.string.preference_multi_search_subtitle),
|
||||
checkState = multiSearch.value,
|
||||
onCheckedChange = { isChecked ->
|
||||
multiSearch.value = isChecked
|
||||
preferences.multiSearch = isChecked
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
object DarkModePage: OnboardingPage(
|
||||
order = 4,
|
||||
title = R.string.dark_mode_page_title,
|
||||
description = R.string.dark_mode_page_description,
|
||||
image = R.drawable.ic_baseline_dark_mode,
|
||||
additionalContent = @Composable { activity, _ ->
|
||||
val selectedValue = remember { mutableStateOf(preferences.darkTheme) }
|
||||
|
||||
val isSelected: (Int) -> Boolean = { selectedValue.value == it }
|
||||
val onChangeState: (Int) -> Unit = {
|
||||
selectedValue.value = it
|
||||
preferences.darkTheme = it
|
||||
activity.recreate()
|
||||
}
|
||||
|
||||
RadioButtonPreference(
|
||||
selected = isSelected(AppPreferences.DarkMode.Automatic.ordinal),
|
||||
title = stringResource(R.string.preference_dark_mode_follow_system_label),
|
||||
description = "Automatically change between dark and light mode",
|
||||
icon = Icons.Filled.Brightness6,
|
||||
onClick = {
|
||||
onChangeState(AppPreferences.DarkMode.Automatic.ordinal)
|
||||
}
|
||||
)
|
||||
RadioButtonPreference(
|
||||
selected = isSelected(AppPreferences.DarkMode.Light.ordinal),
|
||||
title = stringResource(R.string.preference_dark_mode_light_mode_label),
|
||||
icon = Icons.Outlined.LightMode,
|
||||
onClick = {
|
||||
onChangeState(AppPreferences.DarkMode.Light.ordinal)
|
||||
}
|
||||
)
|
||||
RadioButtonPreference(
|
||||
selected = isSelected(AppPreferences.DarkMode.Dark.ordinal),
|
||||
title = stringResource(R.string.preference_dark_mode_dark_mode_label),
|
||||
icon = Icons.Outlined.DarkMode,
|
||||
onClick = {
|
||||
onChangeState(AppPreferences.DarkMode.Dark.ordinal)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
object SystemColorPage: OnboardingPage(
|
||||
order = 4,
|
||||
title = R.string.system_color_page_title,
|
||||
description = R.string.system_color_page_description,
|
||||
image = R.drawable.ic_baseline_palette,
|
||||
additionalContent = @Composable { activity, _ ->
|
||||
val useWallpaperColors = remember { mutableStateOf(preferences.useWallpaperColors) }
|
||||
SwitchPreference(
|
||||
titleText = stringResource(R.string.preferences_use_wallpaper_colors_title),
|
||||
subtitleText = stringResource(R.string.preferences_use_wallpaper_colors_subtitle),
|
||||
checkState = useWallpaperColors.value,
|
||||
onCheckedChange = { isChecked ->
|
||||
useWallpaperColors.value = isChecked
|
||||
preferences.useWallpaperColors = isChecked
|
||||
activity.recreate()
|
||||
}
|
||||
)
|
||||
|
||||
val isSystemColorsSupported = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
|
||||
if (isSystemColorsSupported) {
|
||||
val useSystemColors = remember { mutableStateOf(preferences.useSystemColors) }
|
||||
SwitchPreference(
|
||||
titleText = stringResource(id = R.string.preference_system_colors_heading),
|
||||
subtitleText = stringResource(R.string.preference_system_colors_subtitle),
|
||||
checkState = useSystemColors.value,
|
||||
onCheckedChange = { isChecked ->
|
||||
useSystemColors.value = isChecked
|
||||
preferences.useSystemColors = isChecked
|
||||
MonetCompat.useSystemColorsOnAndroid12 = isChecked
|
||||
MonetCompat.getInstance().updateMonetColors()
|
||||
},
|
||||
enabled = useWallpaperColors.value
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
5
app/src/main/res/drawable/ic_baseline_dark_mode.xml
Normal file
5
app/src/main/res/drawable/ic_baseline_dark_mode.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#000000"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M12,3c-4.97,0 -9,4.03 -9,9s4.03,9 9,9s9,-4.03 9,-9c0,-0.46 -0.04,-0.92 -0.1,-1.36c-0.98,1.37 -2.58,2.26 -4.4,2.26c-2.98,0 -5.4,-2.42 -5.4,-5.4c0,-1.81 0.89,-3.42 2.26,-4.4C12.92,3.04 12.46,3 12,3L12,3z"/>
|
||||
</vector>
|
||||
5
app/src/main/res/drawable/ic_baseline_palette.xml
Normal file
5
app/src/main/res/drawable/ic_baseline_palette.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#000000"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M12,3c-4.97,0 -9,4.03 -9,9s4.03,9 9,9c0.83,0 1.5,-0.67 1.5,-1.5 0,-0.39 -0.15,-0.74 -0.39,-1.01 -0.23,-0.26 -0.38,-0.61 -0.38,-0.99 0,-0.83 0.67,-1.5 1.5,-1.5L16,16c2.76,0 5,-2.24 5,-5 0,-4.42 -4.03,-8 -9,-8zM6.5,12c-0.83,0 -1.5,-0.67 -1.5,-1.5S5.67,9 6.5,9 8,9.67 8,10.5 7.33,12 6.5,12zM9.5,8C8.67,8 8,7.33 8,6.5S8.67,5 9.5,5s1.5,0.67 1.5,1.5S10.33,8 9.5,8zM14.5,8c-0.83,0 -1.5,-0.67 -1.5,-1.5S13.67,5 14.5,5s1.5,0.67 1.5,1.5S15.33,8 14.5,8zM17.5,12c-0.83,0 -1.5,-0.67 -1.5,-1.5S16.67,9 17.5,9s1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5z"/>
|
||||
</vector>
|
||||
@@ -132,8 +132,18 @@
|
||||
<string name="intent_route_auth_return">tvtime.auth.return</string>
|
||||
|
||||
<!-- onboarding -->
|
||||
<string name="get_started">Get started</string>
|
||||
<string name="example_page_desc">This is an example</string>
|
||||
<string name="get_started">Start</string>
|
||||
<string name="intro_page_desc">This is an example</string>
|
||||
<string name="sign_in_page_title">Sign into TMDb</string>
|
||||
<string name="sign_in_page_description">Sign into TMDb to add content to your favourites or watchlist and leave reviews</string>
|
||||
<string name="search_page_title">Search type</string>
|
||||
<string name="search_page_description">Show search view with a search bar or fab</string>
|
||||
<string name="search_type_title">Search mode</string>
|
||||
<string name="search_type_description">Search across individual media types, or across all at once</string>
|
||||
<string name="dark_mode_page_title">Dark mode</string>
|
||||
<string name="dark_mode_page_description"> </string>
|
||||
<string name="system_color_page_title">Theme management</string>
|
||||
<string name="system_color_page_description">Theme the app using colors pulled from your wallpaper or the color theme built into the app</string>
|
||||
|
||||
<!-- search results -->
|
||||
<string name="search_result_tv_series">TV Series</string>
|
||||
|
||||
Reference in New Issue
Block a user