diff --git a/app/src/main/java/dev/anilbeesetti/nextplayer/navigation/SettingsNavGraph.kt b/app/src/main/java/dev/anilbeesetti/nextplayer/navigation/SettingsNavGraph.kt
index b6623f500..a75e1e103 100644
--- a/app/src/main/java/dev/anilbeesetti/nextplayer/navigation/SettingsNavGraph.kt
+++ b/app/src/main/java/dev/anilbeesetti/nextplayer/navigation/SettingsNavGraph.kt
@@ -9,12 +9,14 @@ import dev.anilbeesetti.nextplayer.settings.navigation.appearancePreferencesScre
import dev.anilbeesetti.nextplayer.settings.navigation.audioPreferencesScreen
import dev.anilbeesetti.nextplayer.settings.navigation.decoderPreferencesScreen
import dev.anilbeesetti.nextplayer.settings.navigation.folderPreferencesScreen
+import dev.anilbeesetti.nextplayer.settings.navigation.librariesScreen
import dev.anilbeesetti.nextplayer.settings.navigation.mediaLibraryPreferencesScreen
import dev.anilbeesetti.nextplayer.settings.navigation.navigateToAboutPreferences
import dev.anilbeesetti.nextplayer.settings.navigation.navigateToAppearancePreferences
import dev.anilbeesetti.nextplayer.settings.navigation.navigateToAudioPreferences
import dev.anilbeesetti.nextplayer.settings.navigation.navigateToDecoderPreferences
import dev.anilbeesetti.nextplayer.settings.navigation.navigateToFolderPreferencesScreen
+import dev.anilbeesetti.nextplayer.settings.navigation.navigateToLibraries
import dev.anilbeesetti.nextplayer.settings.navigation.navigateToMediaLibraryPreferencesScreen
import dev.anilbeesetti.nextplayer.settings.navigation.navigateToPlayerPreferences
import dev.anilbeesetti.nextplayer.settings.navigation.navigateToSubtitlePreferences
@@ -69,6 +71,10 @@ fun NavGraphBuilder.settingsNavGraph(
onNavigateUp = navController::navigateUp,
)
aboutPreferencesScreen(
+ onLibrariesClick = navController::navigateToLibraries,
+ onNavigateUp = navController::navigateUp,
+ )
+ librariesScreen(
onNavigateUp = navController::navigateUp,
)
}
diff --git a/core/ui/src/main/res/drawable/ic_github.xml b/core/ui/src/main/res/drawable/ic_github.xml
new file mode 100644
index 000000000..6a3cad3e8
--- /dev/null
+++ b/core/ui/src/main/res/drawable/ic_github.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
diff --git a/core/ui/src/main/res/drawable/ic_kofi.xml b/core/ui/src/main/res/drawable/ic_kofi.xml
new file mode 100644
index 000000000..4eeaedde5
--- /dev/null
+++ b/core/ui/src/main/res/drawable/ic_kofi.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/core/ui/src/main/res/drawable/ic_paypal.xml b/core/ui/src/main/res/drawable/ic_paypal.xml
new file mode 100644
index 000000000..64e14e57d
--- /dev/null
+++ b/core/ui/src/main/res/drawable/ic_paypal.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/core/ui/src/main/res/drawable/ic_upi.xml b/core/ui/src/main/res/drawable/ic_upi.xml
new file mode 100644
index 000000000..0a4eac690
--- /dev/null
+++ b/core/ui/src/main/res/drawable/ic_upi.xml
@@ -0,0 +1,14 @@
+
+
+
+
diff --git a/core/ui/src/main/res/values/strings.xml b/core/ui/src/main/res/values/strings.xml
index fc0733651..52e2b8b70 100644
--- a/core/ui/src/main/res/values/strings.xml
+++ b/core/ui/src/main/res/values/strings.xml
@@ -216,4 +216,14 @@
Loop: Off
Loop: One
Loop: All
+ anilbeesetti
+ by %1$s
+ Libraries
+ Github
+ Donate
+ No app found to handle this link
+ Support the developer on %1$s
+ Ko-fi
+ PayPal
+ UPI
diff --git a/feature/settings/src/main/java/dev/anilbeesetti/nextplayer/settings/navigation/AboutPreferencesNavigation.kt b/feature/settings/src/main/java/dev/anilbeesetti/nextplayer/settings/navigation/AboutPreferencesNavigation.kt
index fe5bdd670..fea1e555c 100644
--- a/feature/settings/src/main/java/dev/anilbeesetti/nextplayer/settings/navigation/AboutPreferencesNavigation.kt
+++ b/feature/settings/src/main/java/dev/anilbeesetti/nextplayer/settings/navigation/AboutPreferencesNavigation.kt
@@ -6,16 +6,36 @@ import androidx.navigation.NavOptions
import androidx.navigation.navOptions
import dev.anilbeesetti.nextplayer.core.ui.designsystem.animatedComposable
import dev.anilbeesetti.nextplayer.settings.screens.about.AboutPreferencesScreen
+import dev.anilbeesetti.nextplayer.settings.screens.about.LibrariesScreen
const val aboutPreferencesNavigationRoute = "about_preferences_route"
+const val librariesNavigationRoute = "libraries_route"
fun NavController.navigateToAboutPreferences(navOptions: NavOptions? = navOptions { launchSingleTop = true }) {
this.navigate(aboutPreferencesNavigationRoute, navOptions)
}
-fun NavGraphBuilder.aboutPreferencesScreen(onNavigateUp: () -> Unit) {
+fun NavController.navigateToLibraries(navOptions: NavOptions? = navOptions { launchSingleTop = true }) {
+ this.navigate(librariesNavigationRoute, navOptions)
+}
+
+fun NavGraphBuilder.aboutPreferencesScreen(
+ onLibrariesClick: () -> Unit,
+ onNavigateUp: () -> Unit,
+) {
animatedComposable(route = aboutPreferencesNavigationRoute) {
AboutPreferencesScreen(
+ onLibrariesClick = onLibrariesClick,
+ onNavigateUp = onNavigateUp,
+ )
+ }
+}
+
+fun NavGraphBuilder.librariesScreen(
+ onNavigateUp: () -> Unit,
+) {
+ animatedComposable(route = librariesNavigationRoute) {
+ LibrariesScreen(
onNavigateUp = onNavigateUp,
)
}
diff --git a/feature/settings/src/main/java/dev/anilbeesetti/nextplayer/settings/screens/about/AboutPreferencesScreen.kt b/feature/settings/src/main/java/dev/anilbeesetti/nextplayer/settings/screens/about/AboutPreferencesScreen.kt
index 72db63e81..6889688f6 100644
--- a/feature/settings/src/main/java/dev/anilbeesetti/nextplayer/settings/screens/about/AboutPreferencesScreen.kt
+++ b/feature/settings/src/main/java/dev/anilbeesetti/nextplayer/settings/screens/about/AboutPreferencesScreen.kt
@@ -1,36 +1,85 @@
package dev.anilbeesetti.nextplayer.settings.screens.about
-import androidx.compose.foundation.layout.PaddingValues
+import android.content.Context
+import android.os.Build
+import android.widget.Toast
+import androidx.compose.animation.core.RepeatMode
+import androidx.compose.animation.core.animateFloat
+import androidx.compose.animation.core.infiniteRepeatable
+import androidx.compose.animation.core.rememberInfiniteTransition
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.only
+import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.windowInsetsPadding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+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.drawWithCache
+import androidx.compose.ui.geometry.CornerRadius
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.graphics.asImageBitmap
+import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalUriHandler
+import androidx.compose.ui.platform.UriHandler
+import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.res.vectorResource
+import androidx.compose.ui.text.intl.Locale
import androidx.compose.ui.unit.dp
-import com.mikepenz.aboutlibraries.ui.compose.LibrariesContainer
-import com.mikepenz.aboutlibraries.ui.compose.LibraryDefaults
+import androidx.compose.ui.unit.sp
+import androidx.core.graphics.drawable.toBitmapOrNull
import dev.anilbeesetti.nextplayer.core.ui.R
+import dev.anilbeesetti.nextplayer.core.ui.components.ClickablePreferenceItem
import dev.anilbeesetti.nextplayer.core.ui.components.NextTopAppBar
import dev.anilbeesetti.nextplayer.core.ui.designsystem.NextIcons
+import dev.anilbeesetti.nextplayer.settings.composables.PreferenceSubtitle
+
+private const val GITHUB_URL = "https://github.com/anilbeesetti/nextplayer"
+private const val KOFI_URL = "https://ko-fi.com/anilbeesetti"
+private const val PAYPAL_URL = "https://paypal.me/AnilBeesetti"
+private const val UPI_URL = "upi://pay?pa=anilbeesetti811@ybl&pn=Next%20Player&cu=INR"
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AboutPreferencesScreen(
+ onLibrariesClick: () -> Unit,
onNavigateUp: () -> Unit,
) {
val scrollBehaviour = TopAppBarDefaults.pinnedScrollBehavior()
+ val context = LocalContext.current
val uriHandler = LocalUriHandler.current
Scaffold(
@@ -54,28 +103,220 @@ fun AboutPreferencesScreen(
)
},
) { innerPadding ->
- LibrariesContainer(
- contentPadding = innerPadding,
+ Column(
modifier = Modifier
.fillMaxSize()
- .windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal)),
- colors = LibraryDefaults.libraryColors(
- backgroundColor = MaterialTheme.colorScheme.background,
- contentColor = MaterialTheme.colorScheme.onBackground,
- badgeBackgroundColor = MaterialTheme.colorScheme.secondaryContainer,
- badgeContentColor = MaterialTheme.colorScheme.onSecondaryContainer,
- ),
- padding = LibraryDefaults.libraryPadding(
- namePadding = PaddingValues(bottom = 4.dp),
- badgeContentPadding = PaddingValues(4.dp),
- ),
- onLibraryClick = { library ->
- library.website?.let {
- if (it.isNotEmpty()) {
- uriHandler.openUri(it)
- }
+ .padding(innerPadding)
+ .windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal))
+ .verticalScroll(rememberScrollState()),
+ ) {
+ AboutApp(
+ onGithubClick = {
+ uriHandler.openUriOrShowToast(
+ uri = GITHUB_URL,
+ context = context,
+ )
+ },
+ onLibrariesClick = onLibrariesClick,
+ )
+ PreferenceSubtitle(text = stringResource(id = R.string.donate))
+ ClickablePreferenceItem(
+ title = stringResource(R.string.kofi),
+ description = stringResource(R.string.support_the_developer_on, stringResource(R.string.kofi)),
+ icon = ImageVector.vectorResource(R.drawable.ic_kofi),
+ onClick = {
+ uriHandler.openUriOrShowToast(
+ uri = KOFI_URL,
+ context = context,
+ )
+ },
+ )
+
+ ClickablePreferenceItem(
+ title = stringResource(R.string.paypal),
+ description = stringResource(R.string.support_the_developer_on, stringResource(R.string.paypal)),
+ icon = ImageVector.vectorResource(R.drawable.ic_paypal),
+ onClick = {
+ uriHandler.openUriOrShowToast(
+ uri = PAYPAL_URL,
+ context = context,
+ )
+ },
+ )
+
+ if (Locale.current.region == "IN") {
+ ClickablePreferenceItem(
+ title = stringResource(R.string.upi),
+ description = stringResource(R.string.support_the_developer_on, stringResource(R.string.upi)),
+ icon = ImageVector.vectorResource(R.drawable.ic_upi),
+ onClick = {
+ uriHandler.openUriOrShowToast(
+ uri = UPI_URL,
+ context = context,
+ )
+ },
+ )
+ }
+ }
+ }
+}
+
+@Composable
+fun AboutApp(
+ modifier: Modifier = Modifier,
+ onGithubClick: () -> Unit,
+ onLibrariesClick: () -> Unit,
+) {
+ val context = LocalContext.current
+ val appVersion = remember { context.appVersion() }
+ val appIcon = remember { context.appIcon() }
+
+ val colorPrimary = MaterialTheme.colorScheme.primaryContainer
+ val colorTertiary = MaterialTheme.colorScheme.tertiaryContainer
+
+ val transition = rememberInfiniteTransition()
+ val fraction by transition.animateFloat(
+ initialValue = 0f,
+ targetValue = 1f,
+ animationSpec = infiniteRepeatable(
+ animation = tween(durationMillis = 5000),
+ repeatMode = RepeatMode.Reverse,
+ ),
+ )
+ val cornerRadius = 24.dp
+
+ Column(
+ modifier = modifier
+ .padding(all = 16.dp)
+ .drawWithCache {
+ val cx = size.width - size.width * fraction
+ val cy = size.height * fraction
+
+ val gradient = Brush.radialGradient(
+ colors = listOf(colorPrimary, colorTertiary),
+ center = Offset(cx, cy),
+ radius = 800f,
+ )
+
+ onDrawBehind {
+ drawRoundRect(
+ brush = gradient,
+ cornerRadius = CornerRadius(
+ cornerRadius.toPx(),
+ cornerRadius.toPx(),
+ ),
+ )
}
- },
- )
+ }
+ .padding(all = 24.dp)
+ .fillMaxWidth(),
+ verticalArrangement = Arrangement.spacedBy(24.dp),
+ ) {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(16.dp),
+ ) {
+ appIcon?.let {
+ Image(
+ bitmap = it,
+ contentDescription = "App Logo",
+ modifier = Modifier.size(48.dp).clip(CircleShape),
+ )
+ }
+ Column {
+ Text(
+ text = stringResource(id = R.string.app_name),
+ fontSize = 22.sp,
+ color = MaterialTheme.colorScheme.onSurface,
+ )
+
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(4.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Text(
+ text = appVersion,
+ style = MaterialTheme.typography.titleSmall,
+ color = MaterialTheme.colorScheme.onSurfaceVariant,
+ fontSize = 12.sp,
+ )
+ Text(
+ text = stringResource(R.string.by, stringResource(R.string.app_developer)),
+ style = MaterialTheme.typography.bodyMedium,
+ color = MaterialTheme.colorScheme.onSurfaceVariant,
+ )
+ }
+ }
+ }
+
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ ) {
+ Button(
+ onClick = onLibrariesClick,
+ colors = ButtonDefaults.buttonColors(
+ contentColor = MaterialTheme.colorScheme.onPrimary,
+ disabledContentColor = MaterialTheme.colorScheme.onPrimary.copy(alpha = .12f),
+ containerColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.8f),
+ disabledContainerColor = MaterialTheme.colorScheme.primary.copy(alpha = .12f),
+ ),
+ shape = RoundedCornerShape(8.dp),
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(52.dp)
+ .weight(1f),
+ ) {
+ Text(text = stringResource(R.string.libraries))
+ }
+ Button(
+ onClick = onGithubClick,
+ colors = ButtonDefaults.buttonColors(
+ contentColor = MaterialTheme.colorScheme.onTertiary,
+ disabledContentColor = MaterialTheme.colorScheme.onTertiary.copy(alpha = .12f),
+ containerColor = MaterialTheme.colorScheme.tertiary.copy(alpha = 0.8f),
+ disabledContainerColor = MaterialTheme.colorScheme.tertiary.copy(alpha = .12f),
+ ),
+ shape = RoundedCornerShape(8.dp),
+ modifier = Modifier
+ .fillMaxWidth()
+ .weight(1f)
+ .height(52.dp),
+ ) {
+ Icon(
+ painter = painterResource(R.drawable.ic_github),
+ contentDescription = null,
+ modifier = Modifier.size(24.dp),
+ )
+ Spacer(modifier = Modifier.width(8.dp))
+ Text(text = stringResource(R.string.github))
+ }
+ }
+ }
+}
+
+private fun Context.appVersion(): String {
+ val packageInfo = packageManager.getPackageInfo(packageName, 0)
+
+ @Suppress("DEPRECATION")
+ val versionCode = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ packageInfo.longVersionCode
+ } else {
+ packageInfo.versionCode
+ }
+
+ return "${packageInfo.versionName} ($versionCode)"
+}
+
+private fun Context.appIcon(): ImageBitmap? {
+ return packageManager.getApplicationInfo(packageName, 0).loadIcon(packageManager)?.toBitmapOrNull()?.asImageBitmap()
+}
+
+internal fun UriHandler.openUriOrShowToast(uri: String, context: Context) {
+ try {
+ openUri(uri = uri)
+ } catch (e: Exception) {
+ Toast.makeText(context, context.getString(R.string.error_opening_link), Toast.LENGTH_SHORT).show()
}
}
diff --git a/feature/settings/src/main/java/dev/anilbeesetti/nextplayer/settings/screens/about/LibrariesScreen.kt b/feature/settings/src/main/java/dev/anilbeesetti/nextplayer/settings/screens/about/LibrariesScreen.kt
new file mode 100644
index 000000000..100fb0094
--- /dev/null
+++ b/feature/settings/src/main/java/dev/anilbeesetti/nextplayer/settings/screens/about/LibrariesScreen.kt
@@ -0,0 +1,83 @@
+package dev.anilbeesetti.nextplayer.settings.screens.about
+
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.WindowInsetsSides
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.only
+import androidx.compose.foundation.layout.safeDrawing
+import androidx.compose.foundation.layout.windowInsetsPadding
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalUriHandler
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import com.mikepenz.aboutlibraries.ui.compose.LibrariesContainer
+import com.mikepenz.aboutlibraries.ui.compose.LibraryDefaults
+import dev.anilbeesetti.nextplayer.core.ui.R
+import dev.anilbeesetti.nextplayer.core.ui.components.NextTopAppBar
+import dev.anilbeesetti.nextplayer.core.ui.designsystem.NextIcons
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun LibrariesScreen(
+ onNavigateUp: () -> Unit,
+) {
+ val scrollBehaviour = TopAppBarDefaults.pinnedScrollBehavior()
+ val context = LocalContext.current
+ val uriHandler = LocalUriHandler.current
+
+ Scaffold(
+ modifier = Modifier
+ .nestedScroll(scrollBehaviour.nestedScrollConnection),
+ topBar = {
+ NextTopAppBar(
+ title = stringResource(id = R.string.libraries),
+ scrollBehavior = scrollBehaviour,
+ navigationIcon = {
+ IconButton(
+ onClick = onNavigateUp,
+ modifier = Modifier.windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Start)),
+ ) {
+ Icon(
+ imageVector = NextIcons.ArrowBack,
+ contentDescription = stringResource(id = R.string.navigate_up),
+ )
+ }
+ },
+ )
+ },
+ ) { innerPadding ->
+ LibrariesContainer(
+ contentPadding = innerPadding,
+ modifier = Modifier
+ .fillMaxSize()
+ .windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal)),
+ colors = LibraryDefaults.libraryColors(
+ backgroundColor = MaterialTheme.colorScheme.background,
+ contentColor = MaterialTheme.colorScheme.onBackground,
+ badgeBackgroundColor = MaterialTheme.colorScheme.secondaryContainer,
+ badgeContentColor = MaterialTheme.colorScheme.onSecondaryContainer,
+ ),
+ padding = LibraryDefaults.libraryPadding(
+ namePadding = PaddingValues(bottom = 4.dp),
+ badgeContentPadding = PaddingValues(4.dp),
+ ),
+ onLibraryClick = { library ->
+ library.website?.let {
+ if (it.isNotEmpty()) {
+ uriHandler.openUriOrShowToast(uri = it, context = context)
+ }
+ }
+ },
+ )
+ }
+}