diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 7578d6bea9..af695f04f0 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -142,6 +142,7 @@ dependencies { implementation(compose.foundation) implementation(compose.material3.core) implementation(compose.material3.adapter) + implementation(compose.material.icons) implementation(compose.animation) implementation(compose.ui.tooling) diff --git a/app/src/main/java/eu/kanade/presentation/components/LinkIcon.kt b/app/src/main/java/eu/kanade/presentation/components/LinkIcon.kt new file mode 100644 index 0000000000..8d9adcf8ec --- /dev/null +++ b/app/src/main/java/eu/kanade/presentation/components/LinkIcon.kt @@ -0,0 +1,39 @@ +package eu.kanade.presentation.components + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.unit.dp + +@Composable +fun LinkIcon( + modifier: Modifier = Modifier, + label: String, + painter: Painter, + url: String, +) { + val uriHandler = LocalUriHandler.current + LinkIcon(modifier, label, painter) { uriHandler.openUri(url) } +} + +@Composable +fun LinkIcon( + modifier: Modifier = Modifier, + label: String, + painter: Painter, + onClick: () -> Unit, +) { + Icon( + modifier = modifier + .clickable(onClick = onClick) + .padding(16.dp), + painter = painter, + tint = MaterialTheme.colorScheme.primary, + contentDescription = label, + ) +} diff --git a/app/src/main/java/eu/kanade/presentation/components/Preferences.kt b/app/src/main/java/eu/kanade/presentation/components/Preferences.kt new file mode 100644 index 0000000000..a604c513cc --- /dev/null +++ b/app/src/main/java/eu/kanade/presentation/components/Preferences.kt @@ -0,0 +1,89 @@ +package eu.kanade.presentation.components + +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.requiredHeight +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.widthIn +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.contentColorFor +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import eu.kanade.presentation.util.horizontalPadding + +@Composable +fun PreferenceRow( + title: String, + icon: ImageVector? = null, + onClick: () -> Unit = {}, + onLongClick: () -> Unit = {}, + subtitle: String? = null, + action: @Composable (() -> Unit)? = null, +) { + val height = if (subtitle != null) 72.dp else 56.dp + + // TODO: adjust text styles, especially subtitles + val textStyle = MaterialTheme.typography.titleMedium.copy( + color = contentColorFor(MaterialTheme.colorScheme.background), + ) + + Row( + modifier = Modifier + .fillMaxWidth() + .requiredHeight(height) + .combinedClickable( + onLongClick = onLongClick, + onClick = onClick + ), + verticalAlignment = Alignment.CenterVertically + ) { + if (icon != null) { + Icon( + imageVector = icon, + modifier = Modifier + .padding(horizontal = horizontalPadding) + .size(24.dp), + tint = MaterialTheme.colorScheme.primary, + contentDescription = null + ) + } + Column( + Modifier + .padding(horizontal = horizontalPadding) + .weight(1f) + ) { + Text( + text = title, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + style = textStyle, + ) + if (subtitle != null) { + Text( + text = subtitle, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + style = textStyle.copy( + fontWeight = FontWeight.Normal, + ), + ) + } + } + if (action != null) { + Box(Modifier.widthIn(min = 56.dp)) { + action() + } + } + } +} diff --git a/app/src/main/java/eu/kanade/presentation/more/AboutScreen.kt b/app/src/main/java/eu/kanade/presentation/more/AboutScreen.kt new file mode 100644 index 0000000000..2647d89325 --- /dev/null +++ b/app/src/main/java/eu/kanade/presentation/more/AboutScreen.kt @@ -0,0 +1,144 @@ +package eu.kanade.presentation.more + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Public +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.rememberVectorPainter +import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import eu.kanade.presentation.components.LinkIcon +import eu.kanade.presentation.components.PreferenceRow +import eu.kanade.tachiyomi.BuildConfig +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.updater.RELEASE_URL +import eu.kanade.tachiyomi.util.CrashLogUtil +import eu.kanade.tachiyomi.util.system.copyToClipboard + +@Composable +fun AboutScreen( + nestedScrollInterop: NestedScrollConnection, + checkVersion: () -> Unit, + getFormattedBuildTime: () -> String, + onClickLicenses: () -> Unit, +) { + val context = LocalContext.current + val uriHandler = LocalUriHandler.current + + LazyColumn( + modifier = Modifier.nestedScroll(nestedScrollInterop), + ) { + item { + LogoHeader() + } + + item { + PreferenceRow( + title = stringResource(R.string.version), + subtitle = when { + BuildConfig.DEBUG -> { + "Debug ${BuildConfig.COMMIT_SHA} (${getFormattedBuildTime()})" + } + BuildConfig.PREVIEW -> { + "Preview r${BuildConfig.COMMIT_COUNT} (${BuildConfig.COMMIT_SHA}, ${getFormattedBuildTime()})" + } + else -> { + "Stable ${BuildConfig.VERSION_NAME} (${getFormattedBuildTime()})" + } + }, + onClick = { + val deviceInfo = CrashLogUtil(context).getDebugInfo() + context.copyToClipboard("Debug information", deviceInfo) + }, + ) + } + + if (BuildConfig.INCLUDE_UPDATER) { + item { + PreferenceRow( + title = stringResource(R.string.check_for_updates), + onClick = { + checkVersion() + }, + ) + } + } + if (!BuildConfig.DEBUG) { + item { + PreferenceRow( + title = stringResource(R.string.whats_new), + onClick = { + uriHandler.openUri(RELEASE_URL) + }, + ) + } + } + + item { + PreferenceRow( + title = stringResource(R.string.help_translate), + onClick = { uriHandler.openUri("https://tachiyomi.org/help/contribution/#translation") }, + ) + } + + item { + PreferenceRow( + title = stringResource(R.string.licenses), + onClick = onClickLicenses, + ) + } + + item { + PreferenceRow( + title = stringResource(R.string.privacy_policy), + onClick = { uriHandler.openUri("https://tachiyomi.org/privacy") }, + ) + } + + item { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center, + ) { + LinkIcon( + label = stringResource(R.string.website), + painter = rememberVectorPainter(Icons.Outlined.Public), + url = "https://tachiyomi.org", + ) + LinkIcon( + label = "Discord", + painter = painterResource(R.drawable.ic_discord_24dp), + url = "https://discord.gg/tachiyomi", + ) + LinkIcon( + label = "Twitter", + painter = painterResource(R.drawable.ic_twitter_24dp), + url = "https://twitter.com/tachiyomiorg", + ) + LinkIcon( + label = "Facebook", + painter = painterResource(R.drawable.ic_facebook_24dp), + url = "https://facebook.com/tachiyomiorg", + ) + LinkIcon( + label = "Reddit", + painter = painterResource(R.drawable.ic_reddit_24dp), + url = "https://www.reddit.com/r/Tachiyomi", + ) + LinkIcon( + label = "GitHub", + painter = painterResource(R.drawable.ic_github_24dp), + url = "https://github.com/tachiyomiorg", + ) + } + } + } +} diff --git a/app/src/main/java/eu/kanade/presentation/more/LicensesScreen.kt b/app/src/main/java/eu/kanade/presentation/more/LicensesScreen.kt new file mode 100644 index 0000000000..11460813ed --- /dev/null +++ b/app/src/main/java/eu/kanade/presentation/more/LicensesScreen.kt @@ -0,0 +1,28 @@ +package eu.kanade.presentation.more + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.contentColorFor +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import androidx.compose.ui.input.nestedscroll.nestedScroll +import com.mikepenz.aboutlibraries.ui.compose.LibrariesContainer +import com.mikepenz.aboutlibraries.ui.compose.LibraryDefaults + +@Composable +fun LicensesScreen( + nestedScrollInterop: NestedScrollConnection, +) { + LibrariesContainer( + modifier = Modifier + .fillMaxSize() + .nestedScroll(nestedScrollInterop), + colors = LibraryDefaults.libraryColors( + backgroundColor = MaterialTheme.colorScheme.background, + contentColor = contentColorFor(MaterialTheme.colorScheme.background), + badgeBackgroundColor = MaterialTheme.colorScheme.primary, + badgeContentColor = contentColorFor(MaterialTheme.colorScheme.primary), + ), + ) +} diff --git a/app/src/main/java/eu/kanade/presentation/more/LogoHeader.kt b/app/src/main/java/eu/kanade/presentation/more/LogoHeader.kt new file mode 100644 index 0000000000..4d23f17023 --- /dev/null +++ b/app/src/main/java/eu/kanade/presentation/more/LogoHeader.kt @@ -0,0 +1,36 @@ +package eu.kanade.presentation.more + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Divider +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp +import eu.kanade.tachiyomi.R + +@Composable +fun LogoHeader() { + Column { + Surface( + modifier = Modifier.fillMaxWidth() + ) { + Icon( + painter = painterResource(R.drawable.ic_tachi), + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurface, + modifier = Modifier + .padding(32.dp) + .size(56.dp), + ) + } + + // TODO: proper color + Divider() + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutController.kt index f9b96a0e02..3fb5762738 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutController.kt @@ -1,106 +1,44 @@ package eu.kanade.tachiyomi.ui.more -import androidx.preference.PreferenceScreen +import androidx.compose.runtime.Composable +import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import eu.kanade.presentation.more.AboutScreen import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.updater.AppUpdateChecker import eu.kanade.tachiyomi.data.updater.AppUpdateResult -import eu.kanade.tachiyomi.data.updater.RELEASE_URL +import eu.kanade.tachiyomi.ui.base.controller.BasicComposeController import eu.kanade.tachiyomi.ui.base.controller.NoAppBarElevationController -import eu.kanade.tachiyomi.ui.base.controller.openInBrowser import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction -import eu.kanade.tachiyomi.ui.setting.SettingsController -import eu.kanade.tachiyomi.util.CrashLogUtil import eu.kanade.tachiyomi.util.lang.launchNow import eu.kanade.tachiyomi.util.lang.toDateTimestampString -import eu.kanade.tachiyomi.util.preference.add -import eu.kanade.tachiyomi.util.preference.onClick -import eu.kanade.tachiyomi.util.preference.preference -import eu.kanade.tachiyomi.util.preference.titleRes -import eu.kanade.tachiyomi.util.system.copyToClipboard import eu.kanade.tachiyomi.util.system.logcat import eu.kanade.tachiyomi.util.system.toast import logcat.LogPriority +import uy.kohesive.injekt.injectLazy import java.text.DateFormat import java.text.SimpleDateFormat import java.util.Locale import java.util.TimeZone -class AboutController : SettingsController(), NoAppBarElevationController { +class AboutController : BasicComposeController(), NoAppBarElevationController { + private val preferences: PreferencesHelper by injectLazy() private val updateChecker by lazy { AppUpdateChecker() } - private val dateFormat: DateFormat = preferences.dateFormat() + override fun getTitle() = resources?.getString(R.string.pref_category_about) - override fun setupPreferenceScreen(screen: PreferenceScreen) = screen.apply { - titleRes = R.string.pref_category_about - - add(MoreHeaderPreference(context)) - - preference { - key = "pref_about_version" - titleRes = R.string.version - summary = when { - BuildConfig.DEBUG -> { - "Debug ${BuildConfig.COMMIT_SHA} (${getFormattedBuildTime()})" - } - BuildConfig.PREVIEW -> { - "Preview r${BuildConfig.COMMIT_COUNT} (${BuildConfig.COMMIT_SHA}, ${getFormattedBuildTime()})" - } - else -> { - "Stable ${BuildConfig.VERSION_NAME} (${getFormattedBuildTime()})" - } - } - - onClick { - activity?.let { - val deviceInfo = CrashLogUtil(it).getDebugInfo() - it.copyToClipboard("Debug information", deviceInfo) - } - } - } - if (BuildConfig.INCLUDE_UPDATER) { - preference { - key = "pref_about_check_for_updates" - titleRes = R.string.check_for_updates - - onClick { checkVersion() } - } - } - if (!BuildConfig.DEBUG) { - preference { - key = "pref_about_whats_new" - titleRes = R.string.whats_new - - onClick { - openInBrowser(RELEASE_URL) - } - } - } - preference { - key = "pref_about_help_translate" - titleRes = R.string.help_translate - - onClick { - openInBrowser("https://tachiyomi.org/help/contribution/#translation") - } - } - preference { - key = "pref_about_licenses" - titleRes = R.string.licenses - onClick { + @Composable + override fun ComposeContent(nestedScrollInterop: NestedScrollConnection) { + AboutScreen( + nestedScrollInterop = nestedScrollInterop, + checkVersion = this::checkVersion, + getFormattedBuildTime = this::getFormattedBuildTime, + onClickLicenses = { router.pushController(LicensesController().withFadeTransaction()) - } - } - preference { - key = "pref_about_privacy_policy" - titleRes = R.string.privacy_policy - onClick { - openInBrowser("https://tachiyomi.org/privacy") - } - } - - add(AboutLinksPreference(context)) + }, + ) } /** @@ -142,7 +80,7 @@ class AboutController : SettingsController(), NoAppBarElevationController { ) outputDf.timeZone = TimeZone.getDefault() - buildTime!!.toDateTimestampString(dateFormat) + buildTime!!.toDateTimestampString(preferences.dateFormat()) } catch (e: Exception) { BuildConfig.BUILD_TIME } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/more/LicensesController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/more/LicensesController.kt index 9f8c9ef0fb..d9a1127f47 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/more/LicensesController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/more/LicensesController.kt @@ -1,14 +1,8 @@ package eu.kanade.tachiyomi.ui.more -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.contentColorFor import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.NestedScrollConnection -import androidx.compose.ui.input.nestedscroll.nestedScroll -import com.mikepenz.aboutlibraries.ui.compose.LibrariesContainer -import com.mikepenz.aboutlibraries.ui.compose.LibraryDefaults.libraryColors +import eu.kanade.presentation.more.LicensesScreen import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.ui.base.controller.BasicComposeController @@ -18,16 +12,6 @@ class LicensesController : BasicComposeController() { @Composable override fun ComposeContent(nestedScrollInterop: NestedScrollConnection) { - LibrariesContainer( - modifier = Modifier - .fillMaxSize() - .nestedScroll(nestedScrollInterop), - colors = libraryColors( - backgroundColor = MaterialTheme.colorScheme.background, - contentColor = contentColorFor(MaterialTheme.colorScheme.background), - badgeBackgroundColor = MaterialTheme.colorScheme.primary, - badgeContentColor = contentColorFor(MaterialTheme.colorScheme.primary), - ), - ) + LicensesScreen(nestedScrollInterop = nestedScrollInterop) } } diff --git a/gradle/compose.versions.toml b/gradle/compose.versions.toml index 6b271fc9ba..26a314221a 100644 --- a/gradle/compose.versions.toml +++ b/gradle/compose.versions.toml @@ -5,5 +5,6 @@ compose = "1.2.0-alpha07" foundation = { module = "androidx.compose.foundation:foundation", version.ref="compose" } material3-core = "androidx.compose.material3:material3:1.0.0-alpha09" material3-adapter = "com.google.android.material:compose-theme-adapter-3:1.0.6" +material-icons = { module = "androidx.compose.material:material-icons-extended", version.ref="compose" } animation = { module = "androidx.compose.animation:animation", version.ref="compose" } ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref="compose" }