diff --git a/app/src/main/java/me/ash/reader/ui/component/CurlyCornerShape.kt b/app/src/main/java/me/ash/reader/ui/component/CurlyCornerShape.kt new file mode 100644 index 0000000..39821e3 --- /dev/null +++ b/app/src/main/java/me/ash/reader/ui/component/CurlyCornerShape.kt @@ -0,0 +1,83 @@ +package me.ash.reader.ui.component + +import androidx.compose.foundation.shape.CornerBasedShape +import androidx.compose.foundation.shape.CornerSize +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.shape.ZeroCornerSize +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Outline +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.unit.LayoutDirection +import kotlin.math.cos +import kotlin.math.sin + +val curlyCornerShape = CurlyCornerShape() + +class CurlyCornerShape( + private val amp: Double = 16.0, + private val count: Int = 12, +) : CornerBasedShape( + topStart = ZeroCornerSize, + topEnd = ZeroCornerSize, + bottomEnd = ZeroCornerSize, + bottomStart = ZeroCornerSize +) { + private fun sineCircleXYatAngle( + d1: Double, + d2: Double, + d3: Double, + d4: Double, + d5: Double, + i: Int + ): List = (i.toDouble() * d5).run { + listOf( + (sin(this) * d4 + d3) * cos(d5) + d1, + (sin(this) * d4 + d3) * sin(d5) + d2 + ) + } + + override fun createOutline( + size: Size, + topStart: Float, + topEnd: Float, + bottomEnd: Float, + bottomStart: Float, + layoutDirection: LayoutDirection + ): Outline { + val d = 2.0 + val r2: Double = size.width / d + var r13: Double = size.height / d + val r18 = size.width / 2.0 - amp + val path = Path() + path.moveTo((d * r2 - amp).toFloat(), r13.toFloat()) + var i = 0 + while (true) { + val i2 = i + 1 + val d3 = r13 + val r5: List = sineCircleXYatAngle( + r2, r13, r18, amp, Math.toRadians( + i.toDouble() + ), count + ) + path.lineTo(r5[0].toFloat(), r5[1].toFloat()) + if (i2 >= 360) { + path.close() + return Outline.Generic(path) + } + i = i2 + r13 = d3 + } + } + + override fun copy( + topStart: CornerSize, + topEnd: CornerSize, + bottomEnd: CornerSize, + bottomStart: CornerSize + ) = RoundedCornerShape( + topStart = topStart, + topEnd = topEnd, + bottomEnd = bottomEnd, + bottomStart = bottomStart + ) +} \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/page/common/HomeEntry.kt b/app/src/main/java/me/ash/reader/ui/page/common/HomeEntry.kt index 8a70858..f7bd98b 100644 --- a/app/src/main/java/me/ash/reader/ui/page/common/HomeEntry.kt +++ b/app/src/main/java/me/ash/reader/ui/page/common/HomeEntry.kt @@ -15,6 +15,7 @@ import me.ash.reader.ui.ext.isFirstLaunch import me.ash.reader.ui.page.home.HomePage import me.ash.reader.ui.page.settings.ColorAndStyle import me.ash.reader.ui.page.settings.SettingsPage +import me.ash.reader.ui.page.settings.TipsAndSupport import me.ash.reader.ui.page.startup.StartupPage import me.ash.reader.ui.theme.AppTheme import me.ash.reader.ui.theme.LocalUseDarkTheme @@ -50,6 +51,9 @@ fun HomeEntry() { animatedComposable(route = RouteName.COLOR_AND_STYLE) { ColorAndStyle(navController) } + animatedComposable(route = RouteName.TIPS_AND_SUPPORT) { + TipsAndSupport(navController) + } } } } \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/page/common/RouteName.kt b/app/src/main/java/me/ash/reader/ui/page/common/RouteName.kt index ac27792..ecf7ec3 100644 --- a/app/src/main/java/me/ash/reader/ui/page/common/RouteName.kt +++ b/app/src/main/java/me/ash/reader/ui/page/common/RouteName.kt @@ -8,4 +8,5 @@ object RouteName { const val READ = "read" const val SETTINGS = "settings" const val COLOR_AND_STYLE = "color_and_style" + const val TIPS_AND_SUPPORT = "tips_and_support" } \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/page/home/HomePage.kt b/app/src/main/java/me/ash/reader/ui/page/home/HomePage.kt index 76aec0d..a22439f 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/HomePage.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/HomePage.kt @@ -13,9 +13,11 @@ import me.ash.reader.ui.component.ViewPager import me.ash.reader.ui.ext.collectAsStateValue import me.ash.reader.ui.ext.findActivity import me.ash.reader.ui.page.common.ExtraName +import me.ash.reader.ui.page.home.feeds.FeedsPage +import me.ash.reader.ui.page.home.feeds.option.feed.FeedOptionDrawer import me.ash.reader.ui.page.home.feeds.option.feed.FeedOptionViewAction import me.ash.reader.ui.page.home.feeds.option.feed.FeedOptionViewModel -import me.ash.reader.ui.page.home.feeds.FeedsPage +import me.ash.reader.ui.page.home.feeds.option.group.GroupOptionDrawer import me.ash.reader.ui.page.home.flow.FlowPage import me.ash.reader.ui.page.home.read.ReadPage import me.ash.reader.ui.page.home.read.ReadViewAction @@ -148,4 +150,7 @@ fun HomePage( ), ) } + + FeedOptionDrawer() + GroupOptionDrawer() } \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsPage.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsPage.kt index 7258899..3148f1a 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsPage.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsPage.kt @@ -38,8 +38,6 @@ import me.ash.reader.ui.ext.getName import me.ash.reader.ui.page.common.RouteName import me.ash.reader.ui.page.home.FilterBar import me.ash.reader.ui.page.home.FilterState -import me.ash.reader.ui.page.home.feeds.option.feed.FeedOptionDrawer -import me.ash.reader.ui.page.home.feeds.option.group.GroupOptionDrawer import me.ash.reader.ui.page.home.feeds.subscribe.SubscribeDialog import me.ash.reader.ui.page.home.feeds.subscribe.SubscribeViewAction import me.ash.reader.ui.page.home.feeds.subscribe.SubscribeViewModel @@ -241,8 +239,5 @@ fun FeedsPage( ) } ) - - FeedOptionDrawer() - GroupOptionDrawer() } diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/ResultView.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/ResultView.kt index 1f02321..3c7eb02 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/ResultView.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/ResultView.kt @@ -186,7 +186,9 @@ private fun AddToGroup( Spacer(modifier = Modifier.height(10.dp)) if (groups.size > 6) { - LazyRow { + LazyRow( + verticalAlignment = Alignment.CenterVertically, + ) { items(groups) { SelectionChip( modifier = Modifier.animateContentSize(), diff --git a/app/src/main/java/me/ash/reader/ui/page/settings/SettingsPage.kt b/app/src/main/java/me/ash/reader/ui/page/settings/SettingsPage.kt index a9c565d..d330d25 100644 --- a/app/src/main/java/me/ash/reader/ui/page/settings/SettingsPage.kt +++ b/app/src/main/java/me/ash/reader/ui/page/settings/SettingsPage.kt @@ -107,8 +107,9 @@ fun SettingsPage( title = stringResource(R.string.tips_and_support), desc = stringResource(R.string.tips_and_support_desc), icon = Icons.Outlined.TipsAndUpdates, - enable = false, - ) {} + ) { + navController.navigate(RouteName.TIPS_AND_SUPPORT) + } } } } diff --git a/app/src/main/java/me/ash/reader/ui/page/settings/TipsAndSupport.kt b/app/src/main/java/me/ash/reader/ui/page/settings/TipsAndSupport.kt new file mode 100644 index 0000000..d9da011 --- /dev/null +++ b/app/src/main/java/me/ash/reader/ui/page/settings/TipsAndSupport.kt @@ -0,0 +1,294 @@ +package me.ash.reader.ui.page.settings + +import android.content.Intent +import android.net.Uri +import android.view.HapticFeedbackConstants +import android.view.SoundEffectConstants +import androidx.compose.animation.animateContentSize +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.tween +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.gestures.detectTapGestures +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.rounded.ArrowBack +import androidx.compose.material.icons.rounded.Balance +import androidx.compose.material.icons.rounded.VolunteerActivism +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.navigation.NavHostController +import me.ash.reader.R +import me.ash.reader.ui.component.CurlyCornerShape +import me.ash.reader.ui.component.FeedbackIconButton +import me.ash.reader.ui.theme.palette.alwaysLight +import me.ash.reader.ui.theme.palette.onLight + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun TipsAndSupport( + navController: NavHostController, +) { + val context = LocalContext.current + val view = LocalView.current + val githubLink = stringResource(R.string.github_link) + val telegramLink = stringResource(R.string.telegram_link) + var version by remember { mutableStateOf("") } + var pressAMP by remember { mutableStateOf(16f) } + val animatedPress by animateFloatAsState( + targetValue = pressAMP, + animationSpec = tween() + ) + + LaunchedEffect(Unit) { + version = context.packageManager.getPackageInfo(context.packageName, 0).versionName + } + + Scaffold( + modifier = Modifier + .background(MaterialTheme.colorScheme.surface onLight MaterialTheme.colorScheme.inverseOnSurface) + .statusBarsPadding() + .navigationBarsPadding(), + containerColor = MaterialTheme.colorScheme.surface onLight MaterialTheme.colorScheme.inverseOnSurface, + topBar = { + SmallTopAppBar( + colors = TopAppBarDefaults.smallTopAppBarColors( + containerColor = MaterialTheme.colorScheme.surface onLight MaterialTheme.colorScheme.inverseOnSurface + ), + title = {}, + navigationIcon = { + FeedbackIconButton( + imageVector = Icons.Rounded.ArrowBack, + contentDescription = stringResource(R.string.back), + tint = MaterialTheme.colorScheme.onSurface + ) { + navController.popBackStack() + } + }, + actions = {} + ) + }, + content = { + LazyColumn( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.SpaceAround, + ) { + item { + Column( + modifier = Modifier.pointerInput(Unit) { + detectTapGestures( + onPress = { + view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP) + pressAMP = 0f + tryAwaitRelease() + view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP) + view.playSoundEffect(SoundEffectConstants.CLICK) + pressAMP = 16f + }, + ) + }, + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + ) { + Box( + modifier = Modifier + .size(240.dp) + .background( + color = MaterialTheme.colorScheme.primaryContainer alwaysLight true, + shape = CurlyCornerShape(amp = animatedPress.toDouble()), + ) + .shadow( + elevation = 10.dp, + shape = CurlyCornerShape(amp = animatedPress.toDouble()), + ambientColor = MaterialTheme.colorScheme.primaryContainer alwaysLight true, + spotColor = MaterialTheme.colorScheme.primaryContainer alwaysLight true, + ), + contentAlignment = Alignment.Center, + ) { + Image( + modifier = Modifier.size(90.dp), + painter = painterResource(R.drawable.ic_launcher_monochrome), + contentDescription = stringResource(R.string.read_you), + colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onSurface alwaysLight true), + ) + } + Spacer(modifier = Modifier.height(48.dp)) + BadgedBox( + badge = { + Badge( + modifier = Modifier.animateContentSize(tween(800)), + containerColor = MaterialTheme.colorScheme.tertiaryContainer, + contentColor = MaterialTheme.colorScheme.tertiary, + ) { + Text(text = version) + } + } + ) { + Text( + text = stringResource(R.string.read_you), + style = MaterialTheme.typography.displaySmall + ) + } + } + } + item { + Spacer(modifier = Modifier.height(48.dp)) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically, + ) { + // Sponsor + RoundIconButton(RoundIconButtonType.Sponsor( + backgroundColor = MaterialTheme.colorScheme.tertiaryContainer alwaysLight true, + ) {}) + Spacer(modifier = Modifier.width(16.dp)) + + // Telegram + RoundIconButton(RoundIconButtonType.Telegram( + backgroundColor = MaterialTheme.colorScheme.primaryContainer alwaysLight true, + ) { + view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP) + view.playSoundEffect(SoundEffectConstants.CLICK) + context.startActivity( + Intent( + Intent.ACTION_VIEW, + Uri.parse(telegramLink) + ) + ) + }) + Spacer(modifier = Modifier.width(16.dp)) + + // GitHub + RoundIconButton(RoundIconButtonType.GitHub( + backgroundColor = MaterialTheme.colorScheme.primaryContainer alwaysLight true, + ) { + view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP) + view.playSoundEffect(SoundEffectConstants.CLICK) + context.startActivity( + Intent( + Intent.ACTION_VIEW, + Uri.parse(githubLink) + ) + ) + }) + Spacer(modifier = Modifier.width(16.dp)) + + // License + RoundIconButton(RoundIconButtonType.License( + backgroundColor = MaterialTheme.colorScheme.secondaryContainer alwaysLight true, + ) {}) + } + Spacer(modifier = Modifier.height(48.dp)) + } + } + } + ) +} + +@Immutable +sealed class RoundIconButtonType( + val iconResource: Int? = null, + val iconVector: ImageVector? = null, + val descResource: Int? = null, + val descString: String? = null, + open val offset: Modifier = Modifier.offset(), + open val backgroundColor: Color = Color.Unspecified, + open val onClick: () -> Unit = {}, +) { + @Immutable + data class Sponsor( + val desc: Int = R.string.sponsor, + override val backgroundColor: Color, + override val onClick: () -> Unit = {}, + ) : RoundIconButtonType( + iconVector = Icons.Rounded.VolunteerActivism, + descResource = desc, + backgroundColor = backgroundColor, + onClick = onClick, + ) + + @Immutable + data class Telegram( + val desc: String = "Telegram", + override val offset: Modifier = Modifier.offset(x = (-1).dp), + override val backgroundColor: Color, + override val onClick: () -> Unit = {}, + ) : RoundIconButtonType( + iconResource = R.drawable.ic_telegram, + descString = desc, + backgroundColor = backgroundColor, + onClick = onClick, + ) + + @Immutable + data class GitHub( + val desc: String = "GitHub", + override val backgroundColor: Color, + override val onClick: () -> Unit = {}, + ) : RoundIconButtonType( + iconResource = R.drawable.ic_github, + descString = desc, + backgroundColor = backgroundColor, + onClick = onClick, + ) + + @Immutable + data class License( + val desc: Int = R.string.open_source_licenses, + override val backgroundColor: Color, + override val onClick: () -> Unit = {}, + ) : RoundIconButtonType( + iconVector = Icons.Rounded.Balance, + descResource = desc, + backgroundColor = backgroundColor, + onClick = onClick, + ) +} + +@Composable +private fun RoundIconButton(type: RoundIconButtonType) { + IconButton( + modifier = Modifier + .size(70.dp) + .background( + color = type.backgroundColor, + shape = CircleShape, + ), + onClick = { type.onClick() } + ) { + when (type) { + is RoundIconButtonType.Sponsor, is RoundIconButtonType.License -> { + Icon( + modifier = type.offset, + imageVector = type.iconVector!!, + contentDescription = stringResource(type.descResource!!), + tint = MaterialTheme.colorScheme.onSurface alwaysLight true, + ) + } + is RoundIconButtonType.GitHub, is RoundIconButtonType.Telegram -> { + Icon( + modifier = type.offset, + painter = painterResource(type.iconResource!!), + contentDescription = type.descString, + tint = MaterialTheme.colorScheme.onSurface alwaysLight true, + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_github.xml b/app/src/main/res/drawable/ic_github.xml new file mode 100644 index 0000000..3e66680 --- /dev/null +++ b/app/src/main/res/drawable/ic_github.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_launcher_monochrome.xml b/app/src/main/res/drawable/ic_launcher_monochrome.xml index 4640218..08cc371 100644 --- a/app/src/main/res/drawable/ic_launcher_monochrome.xml +++ b/app/src/main/res/drawable/ic_launcher_monochrome.xml @@ -2,26 +2,21 @@ android:width="24dp" android:height="24dp" android:viewportWidth="24" - android:viewportHeight="24" - android:tint="#FFFFFF"> + android:viewportHeight="24"> + android:fillAlpha="0.45"/> + android:fillAlpha="0.7"/> + android:fillColor="#191C1E"/> diff --git a/app/src/main/res/drawable/ic_telegram.xml b/app/src/main/res/drawable/ic_telegram.xml new file mode 100644 index 0000000..5e035e4 --- /dev/null +++ b/app/src/main/res/drawable/ic_telegram.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 99933ec..376ed9a 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -107,4 +107,8 @@ 订阅源页面 信息流页面 阅读页面 + 捐赠 + 开放源代码许可 + https://github.com/Ashinch/ReadYou + https://t.me/ReadYouApp \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ba9b704..a779f6e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -107,4 +107,8 @@ Feeds Page Flow Page Reading Page + Sponsor + Open Source Licenses + https://github.com/Ashinch/ReadYou + https://t.me/ReadYouApp \ No newline at end of file