Add Tips and Support page UI
This commit is contained in:
@@ -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<Double> = (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<Double> = 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
|
||||
)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user