Add dedicated "Support Us" screen (#3200)

This commit is contained in:
AntsyLich
2026-04-10 01:56:24 +06:00
committed by GitHub
parent 1712b9cdc5
commit 50b0ccb928
9 changed files with 248 additions and 5 deletions
+3
View File
@@ -11,6 +11,9 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co
- `Other` - for technical stuff.
## [Unreleased]
### Added
- Add dedicated "Support Us" screen ([@AntsyLich](https://github.com/AntsyLich)) ([#3200](https://github.com/mihonapp/mihon/pull/3200))
### Changed
- Don't prompt users to enable Google Play services if disabled or unavailable ([@leodyversemilla07](https://github.com/leodyversemilla07)) ([#3152](https://github.com/mihonapp/mihon/pull/3152))
- Make "Support Us" more prominent in More screen ([@AntsyLich](https://github.com/AntsyLich)) ([#3196](https://github.com/mihonapp/mihon/pull/3196))
@@ -40,6 +40,7 @@ fun MoreScreen(
onClickStats: () -> Unit,
onClickDataAndStorage: () -> Unit,
onClickSettings: () -> Unit,
onClickSupport: () -> Unit,
onClickAbout: () -> Unit,
) {
val uriHandler = LocalUriHandler.current
@@ -136,7 +137,7 @@ fun MoreScreen(
TextPreferenceWidget(
title = stringResource(MR.strings.label_support_us),
icon = Icons.Default.VolunteerActivism,
onPreferenceClick = { uriHandler.openUri(Constants.URL_DONATE) },
onPreferenceClick = onClickSupport,
)
}
item {
@@ -41,6 +41,7 @@ import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.system.updaterEnabled
import kotlinx.coroutines.launch
import logcat.LogPriority
import tachiyomi.core.common.Constants
import tachiyomi.core.common.util.lang.withIOContext
import tachiyomi.core.common.util.lang.withUIContext
import tachiyomi.core.common.util.system.logcat
@@ -179,7 +180,7 @@ object AboutScreen : Screen() {
LinkIcon(
label = "Discord",
icon = CustomIcons.Discord,
url = "https://discord.gg/mihon",
url = Constants.URL_DISCORD,
)
LinkIcon(
label = "X",
@@ -7,7 +7,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import cafe.adriel.voyager.core.model.ScreenModel
import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.core.model.screenModelScope
@@ -31,6 +30,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import mihon.feature.support.SupportUsScreen
import tachiyomi.core.common.util.lang.launchIO
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.stringResource
@@ -57,7 +57,6 @@ data object MoreTab : Tab {
@Composable
override fun Content() {
val context = LocalContext.current
val navigator = LocalNavigator.currentOrThrow
val screenModel = rememberScreenModel { MoreScreenModel() }
val downloadQueueState by screenModel.downloadQueueState.collectAsState()
@@ -72,6 +71,7 @@ data object MoreTab : Tab {
onClickStats = { navigator.push(StatsScreen()) },
onClickDataAndStorage = { navigator.push(SettingsScreen(SettingsScreen.Destination.DataAndStorage)) },
onClickSettings = { navigator.push(SettingsScreen()) },
onClickSupport = { navigator.push(SupportUsScreen()) },
onClickAbout = { navigator.push(SettingsScreen(SettingsScreen.Destination.About)) },
)
}
@@ -0,0 +1,143 @@
package mihon.feature.support
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.OpenInNew
import androidx.compose.material3.Card
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
import eu.kanade.presentation.util.Screen
import tachiyomi.core.common.Constants
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.icons.CustomIcons
import tachiyomi.presentation.core.icons.Discord
import tachiyomi.presentation.core.icons.OpenCollective
import tachiyomi.presentation.core.icons.Patreon
class SupportUsScreen : Screen() {
@Composable
override fun Content() {
val navigator = LocalNavigator.currentOrThrow
val uriHandler = LocalUriHandler.current
Scaffold(
topBar = {
AppBar(
title = stringResource(MR.strings.label_support_us),
navigateUp = navigator::pop,
)
},
) { paddingValues ->
Column(
verticalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
modifier = Modifier.verticalScroll(rememberScrollState()).padding(
remember(paddingValues) {
object : PaddingValues {
override fun calculateLeftPadding(layoutDirection: LayoutDirection): Dp {
return paddingValues.calculateLeftPadding(layoutDirection) +
MaterialTheme.padding.medium
}
override fun calculateTopPadding(): Dp {
return 0.dp
}
override fun calculateRightPadding(layoutDirection: LayoutDirection): Dp {
return paddingValues.calculateRightPadding(layoutDirection) +
MaterialTheme.padding.medium
}
override fun calculateBottomPadding(): Dp {
return 0.dp
}
}
},
),
) {
Spacer(modifier = Modifier.height(paddingValues.calculateTopPadding()))
Text(
text = stringResource(MR.strings.supportUsScreen_perks),
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.padding(horizontal = MaterialTheme.padding.medium),
)
SupportItem(
icon = CustomIcons.Patreon,
title = stringResource(MR.strings.supportUsScreen_donationPlatform_patreon),
onClick = { uriHandler.openUri(Constants.URL_DONATE_PATREON) },
)
SupportItem(
icon = CustomIcons.OpenCollective,
title = stringResource(MR.strings.supportUsScreen_donationPlatform_opencollective),
onClick = { uriHandler.openUri(Constants.URL_DONATE_OPENCOLLECTIVE) },
)
Text(
text = stringResource(MR.strings.supportUsScreen_currentlySupportedBy, 200),
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.padding(horizontal = MaterialTheme.padding.medium),
)
Text(
text = stringResource(MR.strings.supportUsScreen_contactForDetailsMessage),
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.padding(horizontal = MaterialTheme.padding.medium),
)
SupportItem(
icon = CustomIcons.Discord,
title = stringResource(MR.strings.supportUsScreen_contactPlatform),
onClick = { uriHandler.openUri(Constants.URL_DISCORD) },
)
Spacer(modifier = Modifier.height(paddingValues.calculateBottomPadding()))
}
}
}
@Composable
private fun SupportItem(
icon: ImageVector,
title: String,
onClick: () -> Unit,
) {
Card {
TextPreferenceWidget(
title = title,
icon = icon,
widget = {
Icon(
imageVector = Icons.AutoMirrored.Filled.OpenInNew,
contentDescription = null,
)
},
onPreferenceClick = onClick,
)
}
}
}
@@ -3,7 +3,9 @@ package tachiyomi.core.common
object Constants {
const val URL_HELP = "https://mihon.app/docs/guides/troubleshooting/"
const val URL_HELP_UPCOMING = "https://mihon.app/docs/faq/updates/upcoming"
const val URL_DONATE = "https://patreon.com/mihon/membership"
const val URL_DONATE_PATREON = "https://patreon.com/mihon/membership"
const val URL_DONATE_OPENCOLLECTIVE = "https://opencollective.com/mihon/contribute"
const val URL_DISCORD = "https://discord.gg/mihon"
const val MANGA_EXTRA = "manga"
@@ -1047,4 +1047,11 @@
<string name="migrationListScreen.migrateDialog.cancelLabel">Cancel</string>
<string name="migrationListScreen.progressDialog.cancelLabel">Cancel</string>
<string name="migrationListScreen.matchWithoutChapterToast">No chapters found, this entry cannot be used for migration</string>
<string name="supportUsScreen.perks">Patrons get access to exclusive channels, with more perks to come.</string>
<string name="supportUsScreen.donationPlatform.patreon">Patreon (preferred)</string>
<string name="supportUsScreen.donationPlatform.opencollective">OpenCollective</string>
<string name="supportUsScreen.currentlySupportedBy">Mihon is currently backed by %d+ patrons. We want to thank each and everyone of you. Let's make Mihon even better together.</string>
<string name="supportUsScreen.contactForDetailsMessage">For alternative options or any related questions, join our Discord server.</string>
<string name="supportUsScreen.contactPlatform">Discord</string>
</resources>
@@ -0,0 +1,47 @@
package tachiyomi.presentation.core.icons
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.path
import androidx.compose.ui.unit.dp
@Suppress("UnusedReceiverParameter")
val CustomIcons.OpenCollective: ImageVector
get() {
if (_OpenCollective != null) {
return _OpenCollective!!
}
_OpenCollective = ImageVector.Builder(
name = "OpenCollective",
defaultWidth = 24.dp,
defaultHeight = 24.dp,
viewportWidth = 24f,
viewportHeight = 24f,
).apply {
path(fill = SolidColor(Color.Black)) {
moveTo(12f, 0f)
curveTo(5.373f, 0f, 0f, 5.373f, 0f, 12f)
reflectiveCurveToRelative(5.373f, 12f, 12f, 12f)
curveToRelative(2.54f, 0f, 4.894f, -0.79f, 6.834f, -2.135f)
lineToRelative(-3.107f, -3.109f)
arcToRelative(7.715f, 7.715f, 0f, isMoreThanHalf = true, isPositiveArc = true, 0f, -13.512f)
lineToRelative(3.107f, -3.109f)
arcTo(11.943f, 11.943f, 0f, isMoreThanHalf = false, isPositiveArc = false, 12f, 0f)
close()
moveTo(21.865f, 5.166f)
lineToRelative(-3.109f, 3.107f)
arcTo(7.67f, 7.67f, 0f, isMoreThanHalf = false, isPositiveArc = true, 19.715f, 12f)
arcToRelative(7.682f, 7.682f, 0f, isMoreThanHalf = false, isPositiveArc = true, -0.959f, 3.727f)
lineToRelative(3.109f, 3.107f)
arcTo(11.943f, 11.943f, 0f, isMoreThanHalf = false, isPositiveArc = false, 24f, 12f)
curveToRelative(0f, -2.54f, -0.79f, -4.894f, -2.135f, -6.834f)
close()
}
}.build()
return _OpenCollective!!
}
@Suppress("ObjectPropertyName")
private var _OpenCollective: ImageVector? = null
@@ -0,0 +1,39 @@
package tachiyomi.presentation.core.icons
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.path
import androidx.compose.ui.unit.dp
@Suppress("UnusedReceiverParameter")
val CustomIcons.Patreon: ImageVector
get() {
if (_Patreon != null) {
return _Patreon!!
}
_Patreon = ImageVector.Builder(
name = "Patreon",
defaultWidth = 24.dp,
defaultHeight = 24.dp,
viewportWidth = 24f,
viewportHeight = 24f,
).apply {
path(fill = SolidColor(Color.Black)) {
moveTo(22.957f, 7.21f)
curveToRelative(-0.004f, -3.064f, -2.391f, -5.576f, -5.191f, -6.482f)
curveToRelative(-3.478f, -1.125f, -8.064f, -0.962f, -11.384f, 0.604f)
curveTo(2.357f, 3.231f, 1.093f, 7.391f, 1.046f, 11.54f)
curveToRelative(-0.039f, 3.411f, 0.302f, 12.396f, 5.369f, 12.46f)
curveToRelative(3.765f, 0.047f, 4.326f, -4.804f, 6.068f, -7.141f)
curveToRelative(1.24f, -1.662f, 2.836f, -2.132f, 4.801f, -2.618f)
curveToRelative(3.376f, -0.836f, 5.678f, -3.501f, 5.673f, -7.031f)
close()
}
}.build()
return _Patreon!!
}
@Suppress("ObjectPropertyName")
private var _Patreon: ImageVector? = null