feat: add social links (#1294)

Co-authored-by: Pun Butrach <pun.butrach@gmail.com>
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
Co-authored-by: Ax333l <main@axelen.xyz>
This commit is contained in:
Benjamin 2024-03-12 17:09:39 -07:00 committed by GitHub
parent 6709505e9e
commit 8d5d86fea8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 208 additions and 43 deletions

View file

@ -167,4 +167,7 @@ dependencies {
// Scrollbars // Scrollbars
implementation(libs.scrollbars) implementation(libs.scrollbars)
// Compose Icons
implementation(libs.compose.icons.fontawesome)
} }

View file

@ -17,6 +17,7 @@ val viewModelModule = module {
viewModelOf(::UpdateViewModel) viewModelOf(::UpdateViewModel)
viewModelOf(::ChangelogsViewModel) viewModelOf(::ChangelogsViewModel)
viewModelOf(::ImportExportViewModel) viewModelOf(::ImportExportViewModel)
viewModelOf(::AboutViewModel)
viewModelOf(::ContributorViewModel) viewModelOf(::ContributorViewModel)
viewModelOf(::DownloadsViewModel) viewModelOf(::DownloadsViewModel)
viewModelOf(::InstalledAppsViewModel) viewModelOf(::InstalledAppsViewModel)

View file

@ -26,6 +26,9 @@ class ReVancedAPI(
.getOrThrow() .getOrThrow()
.takeIf { it.version != Build.VERSION.RELEASE } .takeIf { it.version != Build.VERSION.RELEASE }
suspend fun getInfo(api: String? = null) = service.getInfo(api ?: apiUrl()).transform { it.info }
companion object Extensions { companion object Extensions {
fun ReVancedRelease.findAssetByType(mime: String) = fun ReVancedRelease.findAssetByType(mime: String) =
assets.singleOrNull { it.contentType == mime } ?: throw MissingAssetException(mime) assets.singleOrNull { it.contentType == mime } ?: throw MissingAssetException(mime)

View file

@ -0,0 +1,56 @@
package app.revanced.manager.network.dto
import kotlinx.serialization.Serializable
@Serializable
data class ReVancedInfoParent(
val info: ReVancedInfo,
)
@Serializable
data class ReVancedInfo(
val name: String,
val about: String,
val branding: ReVancedBranding,
val contact: ReVancedContact,
val socials: List<ReVancedSocial>,
val donations: ReVancedDonation,
)
@Serializable
data class ReVancedBranding(
val logo: String,
)
@Serializable
data class ReVancedContact(
val email: String,
)
@Serializable
data class ReVancedSocial(
val name: String,
val url: String,
val preferred: Boolean,
)
@Serializable
data class ReVancedDonation(
val wallets: List<ReVancedWallet>,
val links: List<ReVancedDonationLink>,
)
@Serializable
data class ReVancedWallet(
val network: String,
val currency_code: String,
val address: String,
val preferred: Boolean
)
@Serializable
data class ReVancedDonationLink(
val name: String,
val url: String,
val preferred: Boolean,
)

View file

@ -1,10 +1,12 @@
package app.revanced.manager.network.service package app.revanced.manager.network.service
import app.revanced.manager.network.dto.ReVancedLatestRelease
import app.revanced.manager.network.dto.ReVancedGitRepositories import app.revanced.manager.network.dto.ReVancedGitRepositories
import app.revanced.manager.network.dto.ReVancedInfo
import app.revanced.manager.network.dto.ReVancedInfoParent
import app.revanced.manager.network.dto.ReVancedLatestRelease
import app.revanced.manager.network.dto.ReVancedReleases import app.revanced.manager.network.dto.ReVancedReleases
import app.revanced.manager.network.utils.APIResponse import app.revanced.manager.network.utils.APIResponse
import io.ktor.client.request.* import io.ktor.client.request.url
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -31,4 +33,11 @@ class ReVancedService(
url("$api/contributors") url("$api/contributors")
} }
} }
suspend fun getInfo(api: String): APIResponse<ReVancedInfoParent> =
withContext(Dispatchers.IO) {
client.request {
url("$api/v2/info")
}
}
} }

View file

@ -14,15 +14,13 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Code
import androidx.compose.material.icons.outlined.FavoriteBorder import androidx.compose.material.icons.outlined.FavoriteBorder
import androidx.compose.material.icons.outlined.Language
import androidx.compose.material.icons.outlined.MailOutline import androidx.compose.material.icons.outlined.MailOutline
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.FilledTonalButton
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedCard import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
@ -35,12 +33,16 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import app.revanced.manager.BuildConfig import app.revanced.manager.BuildConfig
import app.revanced.manager.R import app.revanced.manager.R
import app.revanced.manager.network.dto.ReVancedSocial
import app.revanced.manager.ui.component.AppTopBar import app.revanced.manager.ui.component.AppTopBar
import app.revanced.manager.ui.component.ColumnWithScrollbar import app.revanced.manager.ui.component.ColumnWithScrollbar
import app.revanced.manager.ui.component.settings.SettingsListItem import app.revanced.manager.ui.component.settings.SettingsListItem
import app.revanced.manager.ui.viewmodel.AboutViewModel
import app.revanced.manager.ui.viewmodel.AboutViewModel.Companion.getSocialIcon
import app.revanced.manager.util.isDebuggable import app.revanced.manager.util.isDebuggable
import app.revanced.manager.util.openUrl import app.revanced.manager.util.openUrl
import com.google.accompanist.drawablepainter.rememberDrawablePainter import com.google.accompanist.drawablepainter.rememberDrawablePainter
import org.koin.androidx.compose.getViewModel
@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class) @OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
@Composable @Composable
@ -48,6 +50,7 @@ fun AboutSettingsScreen(
onBackClick: () -> Unit, onBackClick: () -> Unit,
onContributorsClick: () -> Unit, onContributorsClick: () -> Unit,
onLicensesClick: () -> Unit, onLicensesClick: () -> Unit,
viewModel: AboutViewModel = getViewModel()
) { ) {
val context = LocalContext.current val context = LocalContext.current
// painterResource() is broken on release builds for some reason. // painterResource() is broken on release builds for some reason.
@ -55,23 +58,52 @@ fun AboutSettingsScreen(
AppCompatResources.getDrawable(context, R.drawable.ic_logo_ring) AppCompatResources.getDrawable(context, R.drawable.ic_logo_ring)
}) })
val filledButton = listOf( val (preferredSocials, socials) = remember(viewModel.socials) {
Triple(Icons.Outlined.FavoriteBorder, stringResource(R.string.donate)) { viewModel.socials.partition(ReVancedSocial::preferred)
context.openUrl("https://revanced.app/donate") }
},
Triple(Icons.Outlined.Language, stringResource(R.string.website), third = {
context.openUrl("https://revanced.app")
}),
)
val outlinedButton = listOf( val preferredSocialButtons = remember(preferredSocials, viewModel.donate, viewModel.contact) {
Triple(Icons.Outlined.Code, stringResource(R.string.github), third = { preferredSocials.map {
context.openUrl("https://revanced.app/github") Triple(
}), getSocialIcon(it.name),
Triple(Icons.Outlined.MailOutline, stringResource(R.string.contact), third = { it.name,
context.openUrl("mailto:nosupport@revanced.app") third = {
}), context.openUrl(it.url)
) }
)
} + listOfNotNull(
viewModel.donate?.let {
Triple(
Icons.Outlined.FavoriteBorder,
context.getString(R.string.donate),
third = {
context.openUrl(it)
}
)
},
viewModel.contact?.let {
Triple(
Icons.Outlined.MailOutline,
context.getString(R.string.contact),
third = {
context.openUrl("mailto:$it")
}
)
}
)
}
val socialButtons = remember(socials) {
socials.map {
Triple(
getSocialIcon(it.name),
it.name,
third = {
context.openUrl(it.url)
}
)
}
}
val listItems = listOfNotNull( val listItems = listOfNotNull(
Triple(stringResource(R.string.submit_feedback), Triple(stringResource(R.string.submit_feedback),
@ -130,11 +162,13 @@ fun AboutSettingsScreen(
} }
FlowRow( FlowRow(
maxItemsInEachRow = 2, maxItemsInEachRow = 2,
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally) horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally),
modifier = Modifier.padding(horizontal = 16.dp)
) { ) {
filledButton.forEach { (icon, text, onClick) -> preferredSocialButtons.forEach { (icon, text, onClick) ->
FilledTonalButton( FilledTonalButton(
onClick = onClick onClick = onClick,
modifier = Modifier.weight(1f),
) { ) {
Row( Row(
horizontalArrangement = Arrangement.spacedBy(8.dp), horizontalArrangement = Arrangement.spacedBy(8.dp),
@ -152,29 +186,24 @@ fun AboutSettingsScreen(
} }
} }
} }
outlinedButton.forEach { (icon, text, onClick) -> }
OutlinedButton( FlowRow(
onClick = onClick horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally)
) {
socialButtons.forEach { (icon, text, onClick) ->
IconButton(
onClick = onClick,
modifier = Modifier.padding(end = 8.dp),
) { ) {
Row( Icon(
horizontalArrangement = Arrangement.spacedBy(8.dp), icon,
verticalAlignment = Alignment.CenterVertically contentDescription = text,
) { modifier = Modifier.size(28.dp),
Icon( tint = MaterialTheme.colorScheme.secondary
icon, )
contentDescription = null,
modifier = Modifier.size(18.dp)
)
Text(
text,
style = MaterialTheme.typography.labelLarge,
color = MaterialTheme.colorScheme.primary
)
}
} }
} }
} }
OutlinedCard( OutlinedCard(
modifier = Modifier.padding(horizontal = 16.dp), modifier = Modifier.padding(horizontal = 16.dp),
border = BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant) border = BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant)

View file

@ -0,0 +1,59 @@
package app.revanced.manager.ui.viewmodel
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Language
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.revanced.manager.network.api.ReVancedAPI
import app.revanced.manager.network.dto.ReVancedDonationLink
import app.revanced.manager.network.dto.ReVancedSocial
import app.revanced.manager.network.utils.getOrNull
import compose.icons.FontAwesomeIcons
import compose.icons.fontawesomeicons.Brands
import compose.icons.fontawesomeicons.brands.Discord
import compose.icons.fontawesomeicons.brands.Github
import compose.icons.fontawesomeicons.brands.Reddit
import compose.icons.fontawesomeicons.brands.Telegram
import compose.icons.fontawesomeicons.brands.XTwitter
import compose.icons.fontawesomeicons.brands.Youtube
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class AboutViewModel(private val reVancedAPI: ReVancedAPI) : ViewModel() {
var socials by mutableStateOf(emptyList<ReVancedSocial>())
private set
var contact by mutableStateOf<String?>(null)
private set
var donate by mutableStateOf<String?>(null)
private set
init {
viewModelScope.launch {
withContext(Dispatchers.IO) {
reVancedAPI.getInfo("https://api.revanced.app").getOrNull()
}?.let {
socials = it.socials
contact = it.contact.email
donate = it.donations.links.find(ReVancedDonationLink::preferred)?.url
}
}
}
companion object {
private val socialIcons = mapOf(
"Discord" to FontAwesomeIcons.Brands.Discord,
"GitHub" to FontAwesomeIcons.Brands.Github,
"Reddit" to FontAwesomeIcons.Brands.Reddit,
"Telegram" to FontAwesomeIcons.Brands.Telegram,
"Twitter" to FontAwesomeIcons.Brands.XTwitter,
"X" to FontAwesomeIcons.Brands.XTwitter,
"YouTube" to FontAwesomeIcons.Brands.Youtube,
)
fun getSocialIcon(name: String) = socialIcons[name] ?: Icons.Outlined.Language
}
}

View file

@ -30,6 +30,7 @@ app-icon-loader-coil = "1.5.0"
skrapeit = "1.2.2" skrapeit = "1.2.2"
libsu = "5.2.1" libsu = "5.2.1"
scrollbars = "1.0.4" scrollbars = "1.0.4"
compose-icons = "1.2.4"
[libraries] [libraries]
# AndroidX Core # AndroidX Core
@ -109,6 +110,10 @@ libsu-nio = { group = "com.github.topjohnwu.libsu", name = "nio", version.ref =
# Scrollbars # Scrollbars
scrollbars = { group = "com.github.GIGAMOLE", name = "ComposeScrollbars", version.ref = "scrollbars" } scrollbars = { group = "com.github.GIGAMOLE", name = "ComposeScrollbars", version.ref = "scrollbars" }
# Compose Icons
# switch to br.com.devsrsouza.compose.icons after DevSrSouza/compose-icons#30 is merged
compose-icons-fontawesome = { group = "com.github.BenjaminHalko.compose-icons", name = "font-awesome", version.ref = "compose-icons" }
[plugins] [plugins]
android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" } android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlinGradlePlugin" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlinGradlePlugin" }