mirror of
https://github.com/wgtunnel/android.git
synced 2026-06-02 00:29:08 +02:00
feat: add app licenses screen
This commit is contained in:
+2
-1
@@ -1,2 +1,3 @@
|
||||
/build
|
||||
/release
|
||||
/release
|
||||
/src/main/assets/licenses.json
|
||||
|
||||
+23
-1
@@ -6,6 +6,7 @@ plugins {
|
||||
alias(libs.plugins.ksp)
|
||||
alias(libs.plugins.compose.compiler)
|
||||
alias(libs.plugins.grgit)
|
||||
alias(libs.plugins.licensee)
|
||||
}
|
||||
|
||||
val versionFile = file("$rootDir/versionCode.txt")
|
||||
@@ -139,6 +140,14 @@ android {
|
||||
buildConfig = true
|
||||
}
|
||||
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 {
|
||||
@@ -227,7 +236,6 @@ dependencies {
|
||||
// util
|
||||
implementation(libs.qrcode.kotlin)
|
||||
implementation(libs.semver4j)
|
||||
implementation(libs.markdown.compose)
|
||||
|
||||
// Ktor
|
||||
implementation(libs.ktor.client.core)
|
||||
@@ -263,3 +271,17 @@ tasks.whenTaskAdded {
|
||||
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.logs.LogsScreen
|
||||
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.util.extensions.isRunningOnTv
|
||||
import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel
|
||||
@@ -283,6 +284,7 @@ class MainActivity : AppCompatActivity() {
|
||||
composable<Route.Support> {
|
||||
SupportScreen(appViewModel = viewModel)
|
||||
}
|
||||
composable<Route.License> { LicenseScreen() }
|
||||
composable<Route.AutoTunnelAdvanced> {
|
||||
AutoTunnelAdvancedScreen(appUiState, viewModel)
|
||||
}
|
||||
|
||||
+2
-2
@@ -47,13 +47,13 @@ class GitHubUpdateRepository(
|
||||
withContext(ioDispatcher) {
|
||||
try {
|
||||
// clean up old files
|
||||
context.cacheDir?.listFiles()?.forEach { file ->
|
||||
context.getExternalFilesDir(null)?.listFiles()?.forEach { file ->
|
||||
if (file.extension == "apk") file.delete()
|
||||
}
|
||||
|
||||
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 totalBytes: Long = response.contentLength() ?: -1L
|
||||
|
||||
@@ -31,6 +31,8 @@ sealed class Route {
|
||||
|
||||
@Serializable data object Scanner : Route()
|
||||
|
||||
@Serializable data object License : Route()
|
||||
|
||||
@Serializable data class Config(val id: Int) : Route()
|
||||
|
||||
@Serializable
|
||||
|
||||
+9
@@ -213,6 +213,15 @@ fun currentNavBackStackEntryAsNavBarState(
|
||||
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.SettingsAdvanced::class) ->
|
||||
NavBarState(
|
||||
|
||||
+8
-4
@@ -15,7 +15,7 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
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.ui.common.SectionDivider
|
||||
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.viewmodel.AppViewModel
|
||||
import com.zaneschepke.wireguardautotunnel.viewmodel.event.AppEvent
|
||||
import dev.jeziellago.compose.markdowntext.MarkdownText
|
||||
|
||||
@Composable
|
||||
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)) },
|
||||
body = {
|
||||
Column {
|
||||
MarkdownText(uiState.appUpdate?.releaseNotes ?: "")
|
||||
Column(
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Text(uiState.appUpdate?.version ?: "")
|
||||
Text(uiState.appUpdate?.releaseNotes ?: "")
|
||||
if (uiState.isLoading) {
|
||||
LinearProgressIndicator(
|
||||
progress = { uiState.downloadProgress },
|
||||
|
||||
+17
@@ -1,20 +1,24 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.support.components
|
||||
|
||||
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.Policy
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
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.surface.SelectionItem
|
||||
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.SurfaceSelectionGroupButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.navigation.LocalNavController
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl
|
||||
|
||||
@Composable
|
||||
fun GeneralSupportOptions(context: android.content.Context) {
|
||||
val navController = LocalNavController.current
|
||||
SurfaceSelectionGroupButton(
|
||||
items =
|
||||
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="install_updated_permission">This app needs permission to install updates.</string>
|
||||
<string name="allow">Allow</string>
|
||||
<string name="licenses">Licenses</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,6 +7,7 @@ plugins {
|
||||
alias(libs.plugins.androidLibrary) apply false
|
||||
alias(libs.plugins.compose.compiler) apply false
|
||||
alias(libs.plugins.ktfmt)
|
||||
alias(libs.plugins.licensee) apply false
|
||||
}
|
||||
|
||||
subprojects {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
object Constants {
|
||||
const val VERSION_NAME = "3.8.3"
|
||||
const val VERSION_NAME = "3.8.2"
|
||||
const val JVM_TARGET = "17"
|
||||
const val VERSION_CODE = 38300
|
||||
const val VERSION_CODE = 38200
|
||||
const val TARGET_SDK = 35
|
||||
const val MIN_SDK = 26
|
||||
const val APP_ID = "com.zaneschepke.wireguardautotunnel"
|
||||
@@ -16,4 +16,7 @@ object Constants {
|
||||
const val NIGHTLY = "nightly"
|
||||
const val PRERELEASE = "prerelease"
|
||||
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"
|
||||
storage = "1.5.0"
|
||||
ktfmt = "0.22.0"
|
||||
licensee = "1.12.0"
|
||||
|
||||
|
||||
[libraries]
|
||||
@@ -117,4 +118,5 @@ androidLibrary = { id = "com.android.library", version.ref = "androidGradlePlugi
|
||||
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
||||
grgit = { id = "org.ajoberstar.grgit.service", version.ref = "gradlePlugins-grgit" }
|
||||
ktfmt = { id = "com.ncorti.ktfmt.gradle", version.ref = "ktfmt" }
|
||||
licensee = { id = "app.cash.licensee", version.ref = "licensee" }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user