mirror of
https://github.com/wgtunnel/android.git
synced 2026-06-02 08:33:40 +02:00
feat: add app licenses screen
This commit is contained in:
+2
-1
@@ -1,2 +1,3 @@
|
|||||||
/build
|
/build
|
||||||
/release
|
/release
|
||||||
|
/src/main/assets/licenses.json
|
||||||
|
|||||||
+23
-1
@@ -6,6 +6,7 @@ plugins {
|
|||||||
alias(libs.plugins.ksp)
|
alias(libs.plugins.ksp)
|
||||||
alias(libs.plugins.compose.compiler)
|
alias(libs.plugins.compose.compiler)
|
||||||
alias(libs.plugins.grgit)
|
alias(libs.plugins.grgit)
|
||||||
|
alias(libs.plugins.licensee)
|
||||||
}
|
}
|
||||||
|
|
||||||
val versionFile = file("$rootDir/versionCode.txt")
|
val versionFile = file("$rootDir/versionCode.txt")
|
||||||
@@ -139,6 +140,14 @@ android {
|
|||||||
buildConfig = true
|
buildConfig = true
|
||||||
}
|
}
|
||||||
packaging { resources { excludes += "/META-INF/{AL2.0,LGPL2.1}" } }
|
packaging { resources { excludes += "/META-INF/{AL2.0,LGPL2.1}" } }
|
||||||
|
|
||||||
|
licensee {
|
||||||
|
Constants.allowedLicenses.forEach { allow(it) }
|
||||||
|
allowUrl(Constants.XZING_LICENSE_URL)
|
||||||
|
|
||||||
|
// Fix for qrcode-kotlin (MIT, custom URL)
|
||||||
|
allowUrl("https://rafaellins.mit-license.org/2021/")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
@@ -227,7 +236,6 @@ dependencies {
|
|||||||
// util
|
// util
|
||||||
implementation(libs.qrcode.kotlin)
|
implementation(libs.qrcode.kotlin)
|
||||||
implementation(libs.semver4j)
|
implementation(libs.semver4j)
|
||||||
implementation(libs.markdown.compose)
|
|
||||||
|
|
||||||
// Ktor
|
// Ktor
|
||||||
implementation(libs.ktor.client.core)
|
implementation(libs.ktor.client.core)
|
||||||
@@ -263,3 +271,17 @@ tasks.whenTaskAdded {
|
|||||||
dependsOn(incrementVersionCode)
|
dependsOn(incrementVersionCode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tasks.register<Copy>("copyLicenseeJsonToAssets") {
|
||||||
|
dependsOn("licensee")
|
||||||
|
|
||||||
|
val outputAssets = layout.projectDirectory.dir("src/main/assets")
|
||||||
|
|
||||||
|
from(layout.buildDirectory.file("reports/licensee/androidFdroidRelease/artifacts.json")) {
|
||||||
|
rename("artifacts.json", "licenses.json")
|
||||||
|
}
|
||||||
|
|
||||||
|
into(outputAssets)
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.named("preBuild") { dependsOn("copyLicenseeJsonToAssets") }
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ import com.zaneschepke.wireguardautotunnel.ui.screens.settings.appearance.langua
|
|||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.killswitch.KillSwitchScreen
|
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.killswitch.KillSwitchScreen
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.logs.LogsScreen
|
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.logs.LogsScreen
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.support.SupportScreen
|
import com.zaneschepke.wireguardautotunnel.ui.screens.support.SupportScreen
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.screens.support.license.LicenseScreen
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.theme.WireguardAutoTunnelTheme
|
import com.zaneschepke.wireguardautotunnel.ui.theme.WireguardAutoTunnelTheme
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv
|
import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv
|
||||||
import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel
|
import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel
|
||||||
@@ -283,6 +284,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
composable<Route.Support> {
|
composable<Route.Support> {
|
||||||
SupportScreen(appViewModel = viewModel)
|
SupportScreen(appViewModel = viewModel)
|
||||||
}
|
}
|
||||||
|
composable<Route.License> { LicenseScreen() }
|
||||||
composable<Route.AutoTunnelAdvanced> {
|
composable<Route.AutoTunnelAdvanced> {
|
||||||
AutoTunnelAdvancedScreen(appUiState, viewModel)
|
AutoTunnelAdvancedScreen(appUiState, viewModel)
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -47,13 +47,13 @@ class GitHubUpdateRepository(
|
|||||||
withContext(ioDispatcher) {
|
withContext(ioDispatcher) {
|
||||||
try {
|
try {
|
||||||
// clean up old files
|
// clean up old files
|
||||||
context.cacheDir?.listFiles()?.forEach { file ->
|
context.getExternalFilesDir(null)?.listFiles()?.forEach { file ->
|
||||||
if (file.extension == "apk") file.delete()
|
if (file.extension == "apk") file.delete()
|
||||||
}
|
}
|
||||||
|
|
||||||
val response: HttpResponse = httpClient.get(apkUrl)
|
val response: HttpResponse = httpClient.get(apkUrl)
|
||||||
|
|
||||||
val apkFile = File(context.cacheDir, fileName)
|
val apkFile = File(context.getExternalFilesDir(null), fileName)
|
||||||
|
|
||||||
val channel: ByteReadChannel = response.bodyAsChannel()
|
val channel: ByteReadChannel = response.bodyAsChannel()
|
||||||
val totalBytes: Long = response.contentLength() ?: -1L
|
val totalBytes: Long = response.contentLength() ?: -1L
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ sealed class Route {
|
|||||||
|
|
||||||
@Serializable data object Scanner : Route()
|
@Serializable data object Scanner : Route()
|
||||||
|
|
||||||
|
@Serializable data object License : Route()
|
||||||
|
|
||||||
@Serializable data class Config(val id: Int) : Route()
|
@Serializable data class Config(val id: Int) : Route()
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|||||||
+9
@@ -213,6 +213,15 @@ fun currentNavBackStackEntryAsNavBarState(
|
|||||||
route = Route.Support,
|
route = Route.Support,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
backStackEntry.isCurrentRoute(Route.License::class) -> {
|
||||||
|
NavBarState(
|
||||||
|
showTop = true,
|
||||||
|
showBottom = true,
|
||||||
|
topTitle = { Text(stringResource(R.string.licenses)) },
|
||||||
|
route = Route.License,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
backStackEntry.isCurrentRoute(Route.AutoTunnelAdvanced::class) ||
|
backStackEntry.isCurrentRoute(Route.AutoTunnelAdvanced::class) ||
|
||||||
backStackEntry.isCurrentRoute(Route.SettingsAdvanced::class) ->
|
backStackEntry.isCurrentRoute(Route.SettingsAdvanced::class) ->
|
||||||
NavBarState(
|
NavBarState(
|
||||||
|
|||||||
+8
-4
@@ -15,7 +15,7 @@ import androidx.compose.ui.res.stringResource
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.zaneschepke.wireguardautotunnel.BuildConfig
|
import com.google.zxing.client.android.BuildConfig
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.SectionDivider
|
import com.zaneschepke.wireguardautotunnel.ui.common.SectionDivider
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.dialog.InfoDialog
|
import com.zaneschepke.wireguardautotunnel.ui.common.dialog.InfoDialog
|
||||||
@@ -29,7 +29,6 @@ import com.zaneschepke.wireguardautotunnel.util.extensions.requestInstallPackage
|
|||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.showToast
|
import com.zaneschepke.wireguardautotunnel.util.extensions.showToast
|
||||||
import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel
|
import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel
|
||||||
import com.zaneschepke.wireguardautotunnel.viewmodel.event.AppEvent
|
import com.zaneschepke.wireguardautotunnel.viewmodel.event.AppEvent
|
||||||
import dev.jeziellago.compose.markdowntext.MarkdownText
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SupportScreen(viewModel: SupportViewModel = hiltViewModel(), appViewModel: AppViewModel) {
|
fun SupportScreen(viewModel: SupportViewModel = hiltViewModel(), appViewModel: AppViewModel) {
|
||||||
@@ -63,8 +62,13 @@ fun SupportScreen(viewModel: SupportViewModel = hiltViewModel(), appViewModel: A
|
|||||||
},
|
},
|
||||||
title = { Text(stringResource(R.string.update_available)) },
|
title = { Text(stringResource(R.string.update_available)) },
|
||||||
body = {
|
body = {
|
||||||
Column {
|
Column(
|
||||||
MarkdownText(uiState.appUpdate?.releaseNotes ?: "")
|
horizontalAlignment = Alignment.Start,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top),
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
) {
|
||||||
|
Text(uiState.appUpdate?.version ?: "")
|
||||||
|
Text(uiState.appUpdate?.releaseNotes ?: "")
|
||||||
if (uiState.isLoading) {
|
if (uiState.isLoading) {
|
||||||
LinearProgressIndicator(
|
LinearProgressIndicator(
|
||||||
progress = { uiState.downloadProgress },
|
progress = { uiState.downloadProgress },
|
||||||
|
|||||||
+17
@@ -1,20 +1,24 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.ui.screens.support.components
|
package com.zaneschepke.wireguardautotunnel.ui.screens.support.components
|
||||||
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Balance
|
||||||
import androidx.compose.material.icons.filled.Book
|
import androidx.compose.material.icons.filled.Book
|
||||||
import androidx.compose.material.icons.filled.Policy
|
import androidx.compose.material.icons.filled.Policy
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.Route
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ForwardButton
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.ForwardButton
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItemLabel
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItemLabel
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionLabelType
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionLabelType
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.navigation.LocalNavController
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl
|
import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun GeneralSupportOptions(context: android.content.Context) {
|
fun GeneralSupportOptions(context: android.content.Context) {
|
||||||
|
val navController = LocalNavController.current
|
||||||
SurfaceSelectionGroupButton(
|
SurfaceSelectionGroupButton(
|
||||||
items =
|
items =
|
||||||
buildList {
|
buildList {
|
||||||
@@ -54,6 +58,19 @@ fun GeneralSupportOptions(context: android.content.Context) {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
add(
|
||||||
|
SelectionItem(
|
||||||
|
leadingIcon = Icons.Filled.Balance,
|
||||||
|
title = {
|
||||||
|
SelectionItemLabel(
|
||||||
|
stringResource(R.string.licenses),
|
||||||
|
SelectionLabelType.TITLE,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
trailing = { ForwardButton { navController.navigate(Route.License) } },
|
||||||
|
onClick = { navController.navigate(Route.License) },
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class LicenseFileEntry(
|
||||||
|
val groupId: String,
|
||||||
|
val artifactId: String,
|
||||||
|
val version: String,
|
||||||
|
val name: String,
|
||||||
|
val spdxLicenses: List<SpdxLicense> = emptyList(),
|
||||||
|
val scm: Scm? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable data class SpdxLicense(val identifier: String, val name: String, val url: String)
|
||||||
|
|
||||||
|
@Serializable data class Scm(val url: String)
|
||||||
+45
@@ -0,0 +1,45 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.ui.screens.support.license
|
||||||
|
|
||||||
|
import LicenseFileEntry
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.screens.support.license.components.LicenseList
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LicenseScreen() {
|
||||||
|
val context = LocalContext.current
|
||||||
|
var licenses by remember { mutableStateOf<List<LicenseFileEntry>>(emptyList()) }
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) { licenses = loadLicenseeJson(context) }
|
||||||
|
|
||||||
|
if (licenses.isEmpty()) {
|
||||||
|
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||||
|
CircularProgressIndicator()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LicenseList(licenses)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun loadLicenseeJson(context: Context): List<LicenseFileEntry> {
|
||||||
|
return withContext(Dispatchers.IO) {
|
||||||
|
val json = Json { ignoreUnknownKeys = true }
|
||||||
|
|
||||||
|
val jsonResult = context.assets.open("licenses.json").bufferedReader().use { it.readText() }
|
||||||
|
json.decodeFromString(jsonResult)
|
||||||
|
}
|
||||||
|
}
|
||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.ui.screens.support.license.components
|
||||||
|
|
||||||
|
import LicenseFileEntry
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LicenseList(licenses: List<LicenseFileEntry>) {
|
||||||
|
LazyColumn(modifier = Modifier.fillMaxSize().padding(16.dp)) {
|
||||||
|
items(licenses) { entry ->
|
||||||
|
Column(modifier = Modifier.padding(bottom = 12.dp)) {
|
||||||
|
Text(
|
||||||
|
text = "${entry.name} (${entry.version})",
|
||||||
|
style = MaterialTheme.typography.titleSmall,
|
||||||
|
)
|
||||||
|
|
||||||
|
entry.spdxLicenses.forEach { license ->
|
||||||
|
Text(
|
||||||
|
text = license.name,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.scm?.url?.let { scmUrl ->
|
||||||
|
Text(
|
||||||
|
text = scmUrl,
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
modifier = Modifier.padding(top = 4.dp),
|
||||||
|
color = MaterialTheme.colorScheme.secondary,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -252,4 +252,5 @@
|
|||||||
<string name="permission_required">Permission Required</string>
|
<string name="permission_required">Permission Required</string>
|
||||||
<string name="install_updated_permission">This app needs permission to install updates.</string>
|
<string name="install_updated_permission">This app needs permission to install updates.</string>
|
||||||
<string name="allow">Allow</string>
|
<string name="allow">Allow</string>
|
||||||
|
<string name="licenses">Licenses</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ plugins {
|
|||||||
alias(libs.plugins.androidLibrary) apply false
|
alias(libs.plugins.androidLibrary) apply false
|
||||||
alias(libs.plugins.compose.compiler) apply false
|
alias(libs.plugins.compose.compiler) apply false
|
||||||
alias(libs.plugins.ktfmt)
|
alias(libs.plugins.ktfmt)
|
||||||
|
alias(libs.plugins.licensee) apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
subprojects {
|
subprojects {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
object Constants {
|
object Constants {
|
||||||
const val VERSION_NAME = "3.8.3"
|
const val VERSION_NAME = "3.8.2"
|
||||||
const val JVM_TARGET = "17"
|
const val JVM_TARGET = "17"
|
||||||
const val VERSION_CODE = 38300
|
const val VERSION_CODE = 38200
|
||||||
const val TARGET_SDK = 35
|
const val TARGET_SDK = 35
|
||||||
const val MIN_SDK = 26
|
const val MIN_SDK = 26
|
||||||
const val APP_ID = "com.zaneschepke.wireguardautotunnel"
|
const val APP_ID = "com.zaneschepke.wireguardautotunnel"
|
||||||
@@ -16,4 +16,7 @@ object Constants {
|
|||||||
const val NIGHTLY = "nightly"
|
const val NIGHTLY = "nightly"
|
||||||
const val PRERELEASE = "prerelease"
|
const val PRERELEASE = "prerelease"
|
||||||
const val TYPE = "type"
|
const val TYPE = "type"
|
||||||
|
|
||||||
|
val allowedLicenses = listOf("MIT", "Apache-2.0", "BSD-3-Clause")
|
||||||
|
const val XZING_LICENSE_URL: String = "https://github.com/journeyapps/zxing-android-embedded/blob/master/COPYING"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ gradlePlugins-grgit = "5.3.0"
|
|||||||
material = "1.12.0"
|
material = "1.12.0"
|
||||||
storage = "1.5.0"
|
storage = "1.5.0"
|
||||||
ktfmt = "0.22.0"
|
ktfmt = "0.22.0"
|
||||||
|
licensee = "1.12.0"
|
||||||
|
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
@@ -117,4 +118,5 @@ androidLibrary = { id = "com.android.library", version.ref = "androidGradlePlugi
|
|||||||
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
||||||
grgit = { id = "org.ajoberstar.grgit.service", version.ref = "gradlePlugins-grgit" }
|
grgit = { id = "org.ajoberstar.grgit.service", version.ref = "gradlePlugins-grgit" }
|
||||||
ktfmt = { id = "com.ncorti.ktfmt.gradle", version.ref = "ktfmt" }
|
ktfmt = { id = "com.ncorti.ktfmt.gradle", version.ref = "ktfmt" }
|
||||||
|
licensee = { id = "app.cash.licensee", version.ref = "licensee" }
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user