add: build and release CI
Add build and release pipeline for app Fixes bug where location permission screen was not appearing on < Android 9 Bump versions
This commit is contained in:
parent
d1e61be3ae
commit
c0e58125dd
|
@ -0,0 +1,98 @@
|
||||||
|
# name of the workflow
|
||||||
|
name: Android CI Tag Deployment
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- '*.*.*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build Signed APK
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: Set up JDK 17
|
||||||
|
uses: actions/setup-java@v3
|
||||||
|
with:
|
||||||
|
distribution: 'temurin'
|
||||||
|
java-version: '17'
|
||||||
|
cache: gradle
|
||||||
|
- name: Grant execute permission for gradlew
|
||||||
|
run: chmod +x gradlew
|
||||||
|
|
||||||
|
# Here we need to decode keystore.jks from base64 string and place it
|
||||||
|
# in the folder specified in the release signing configuration
|
||||||
|
- name: Decode Keystore
|
||||||
|
id: decode_keystore
|
||||||
|
uses: timheuer/base64-to-file@v1.2
|
||||||
|
with:
|
||||||
|
fileName: 'android_keystore.jks'
|
||||||
|
fileDir: '/home/runner/work/wgtunnel/wgtunnel/app/keystore/'
|
||||||
|
encodedString: ${{ secrets.KEYSTORE }}
|
||||||
|
|
||||||
|
# Build and sign APK ("-x test" argument is used to skip tests)
|
||||||
|
# add fdroid flavor for apk upload
|
||||||
|
- name: Build Fdroid Release APK
|
||||||
|
run: ./gradlew :app:assembleFdroidRelease -x test
|
||||||
|
env:
|
||||||
|
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
|
||||||
|
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
|
||||||
|
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
|
||||||
|
|
||||||
|
# get fdroid flavor release apk path
|
||||||
|
- name: Get apk path
|
||||||
|
id: apk-path
|
||||||
|
run: echo "path=$(find . -regex '^.*/build/outputs/apk/fdroid/release/.*\.apk$' -type f | head -1)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
# add general flavor for google play
|
||||||
|
- name: Build General Release AAB
|
||||||
|
id: buildRelease
|
||||||
|
run: ./gradlew bundleGeneralRelease
|
||||||
|
env:
|
||||||
|
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
|
||||||
|
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
|
||||||
|
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
|
||||||
|
|
||||||
|
# Save the APK after the Build job is complete to publish it as a Github release in the next job
|
||||||
|
- name: Upload APK
|
||||||
|
uses: actions/upload-artifact@v3.1.2
|
||||||
|
with:
|
||||||
|
name: wgtunnel
|
||||||
|
path: ${{ steps.apk-path.outputs.path }}
|
||||||
|
|
||||||
|
- name: Create service_account.json
|
||||||
|
id: createServiceAccount
|
||||||
|
run: echo '${{ secrets.SERVICE_ACCOUNT_JSON }}' > service_account.json
|
||||||
|
|
||||||
|
# upload general flavor release aab to beta track
|
||||||
|
- name: Publish to Play Store BETA
|
||||||
|
id: publish
|
||||||
|
uses: r0adkll/upload-google-play@v1
|
||||||
|
with:
|
||||||
|
serviceAccountJson: service_account.json
|
||||||
|
packageName: com.zaneschepke.wireguardautotunnel
|
||||||
|
releaseFile: app/build/outputs/bundle/generalRelease/app-general-release.aab
|
||||||
|
track: beta
|
||||||
|
|
||||||
|
release:
|
||||||
|
name: Release APK
|
||||||
|
needs: build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Download APK from build
|
||||||
|
uses: actions/download-artifact@v1
|
||||||
|
with:
|
||||||
|
name: wgtunnel
|
||||||
|
- name: Create Release
|
||||||
|
id: create_release
|
||||||
|
uses: softprops/action-gh-release@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
tag_name: ${{ github.ref_name }}
|
||||||
|
name: Release ${{ github.ref_name }}
|
||||||
|
draft: false
|
||||||
|
prerelease: false
|
||||||
|
files: wgtunnel/${{ steps.apk-path.outputs.path }}
|
|
@ -69,3 +69,5 @@ lint/tmp/
|
||||||
# App Specific cases
|
# App Specific cases
|
||||||
app/release/output.json
|
app/release/output.json
|
||||||
.idea/codeStyles/
|
.idea/codeStyles/
|
||||||
|
# where we keep our signing secrets locally
|
||||||
|
app/signing.properties
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import java.util.Properties
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.android.application)
|
alias(libs.plugins.android.application)
|
||||||
alias(libs.plugins.kotlin.android)
|
alias(libs.plugins.kotlin.android)
|
||||||
|
@ -15,7 +17,7 @@ android {
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
targetSdk = 34
|
targetSdk = 34
|
||||||
versionCode = 32000
|
versionCode = 32000
|
||||||
versionName = "3.2.0"
|
versionName = "3.2.1"
|
||||||
|
|
||||||
ksp {
|
ksp {
|
||||||
arg("room.schemaLocation", "$projectDir/schemas")
|
arg("room.schemaLocation", "$projectDir/schemas")
|
||||||
|
@ -29,7 +31,39 @@ android {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
signingConfigs {
|
||||||
|
create("release") {
|
||||||
|
val properties = Properties().apply {
|
||||||
|
//created local file for signing details
|
||||||
|
try {
|
||||||
|
load(file("signing.properties").reader())
|
||||||
|
} catch (_ : Exception) {
|
||||||
|
load(file("signing_template.properties").reader())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val storePassVarName = "SIGNING_STORE_PASSWORD";
|
||||||
|
val keyAliasVarName = "SIGNING_KEY_ALIAS"
|
||||||
|
val keyPassVarName = "SIGNING_KEY_PASSWORD"
|
||||||
|
val keyStorePathVarName = "KEY_STORE_PATH"
|
||||||
|
|
||||||
|
//try to get secrets from env first for pipeline build, then properties file for local build
|
||||||
|
storeFile = file(System.getenv().getOrDefault(keyStorePathVarName, properties.getProperty(keyStorePathVarName)))
|
||||||
|
storePassword = System.getenv().getOrDefault(storePassVarName, properties.getProperty(storePassVarName))
|
||||||
|
keyAlias = System.getenv().getOrDefault(keyAliasVarName, properties.getProperty(keyAliasVarName))
|
||||||
|
keyPassword = System.getenv().getOrDefault(keyPassVarName, properties.getProperty(keyPassVarName))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
applicationVariants.all {
|
||||||
|
val variant = this
|
||||||
|
variant.outputs
|
||||||
|
.map { it as com.android.build.gradle.internal.api.BaseVariantOutputImpl }
|
||||||
|
.forEach { output ->
|
||||||
|
val outputFileName = "wgtunnel-${variant.flavorName}-${variant.buildType.name}-${variant.versionName}.apk"
|
||||||
|
output.outputFileName = outputFileName
|
||||||
|
}
|
||||||
|
}
|
||||||
release {
|
release {
|
||||||
isDebuggable = false
|
isDebuggable = false
|
||||||
isMinifyEnabled = true
|
isMinifyEnabled = true
|
||||||
|
@ -38,6 +72,7 @@ android {
|
||||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||||
"proguard-rules.pro"
|
"proguard-rules.pro"
|
||||||
)
|
)
|
||||||
|
signingConfig = signingConfigs.getByName("release")
|
||||||
}
|
}
|
||||||
debug {
|
debug {
|
||||||
isDebuggable = true
|
isDebuggable = true
|
||||||
|
@ -61,6 +96,7 @@ android {
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility = JavaVersion.VERSION_17
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
targetCompatibility = JavaVersion.VERSION_17
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
|
isCoreLibraryDesugaringEnabled = true
|
||||||
}
|
}
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = "17"
|
jvmTarget = "17"
|
||||||
|
@ -105,6 +141,7 @@ dependencies {
|
||||||
|
|
||||||
//wg
|
//wg
|
||||||
implementation(libs.tunnel)
|
implementation(libs.tunnel)
|
||||||
|
coreLibraryDesugaring(libs.desugar.jdk.libs)
|
||||||
|
|
||||||
//logging
|
//logging
|
||||||
implementation(libs.timber)
|
implementation(libs.timber)
|
||||||
|
|
|
@ -99,7 +99,7 @@ fun SettingsScreen(
|
||||||
val fineLocationState = rememberPermissionState(Manifest.permission.ACCESS_FINE_LOCATION)
|
val fineLocationState = rememberPermissionState(Manifest.permission.ACCESS_FINE_LOCATION)
|
||||||
var currentText by remember { mutableStateOf("") }
|
var currentText by remember { mutableStateOf("") }
|
||||||
val scrollState = rememberScrollState()
|
val scrollState = rememberScrollState()
|
||||||
var didShowLocationDisclaimer by remember { mutableStateOf(false) }
|
var isLocationDisclaimerNeeded by remember { mutableStateOf(true) }
|
||||||
var isBackgroundLocationGranted by remember { mutableStateOf(true) }
|
var isBackgroundLocationGranted by remember { mutableStateOf(true) }
|
||||||
var showAuthPrompt by remember { mutableStateOf(false) }
|
var showAuthPrompt by remember { mutableStateOf(false) }
|
||||||
var didExportFiles by remember { mutableStateOf(false) }
|
var didExportFiles by remember { mutableStateOf(false) }
|
||||||
|
@ -141,6 +141,7 @@ fun SettingsScreen(
|
||||||
return(isBackgroundLocationGranted && fineLocationState.status.isGranted && !viewModel.isLocationServicesNeeded())
|
return(isBackgroundLocationGranted && fineLocationState.status.isGranted && !viewModel.isLocationServicesNeeded())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun openSettings() {
|
fun openSettings() {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
val intentSettings =
|
val intentSettings =
|
||||||
|
@ -156,62 +157,75 @@ fun SettingsScreen(
|
||||||
rememberPermissionState(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
|
rememberPermissionState(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
|
||||||
if(!backgroundLocationState.status.isGranted) {
|
if(!backgroundLocationState.status.isGranted) {
|
||||||
isBackgroundLocationGranted = false
|
isBackgroundLocationGranted = false
|
||||||
if(!didShowLocationDisclaimer) {
|
|
||||||
Column(
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
|
||||||
verticalArrangement = Arrangement.Top,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.verticalScroll(scrollState)
|
|
||||||
.padding(padding)
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
Icons.Rounded.LocationOff,
|
|
||||||
contentDescription = stringResource(id = R.string.map),
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(30.dp)
|
|
||||||
.size(128.dp)
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
stringResource(R.string.prominent_background_location_title),
|
|
||||||
textAlign = TextAlign.Center,
|
|
||||||
modifier = Modifier.padding(30.dp),
|
|
||||||
fontSize = 20.sp
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
stringResource(R.string.prominent_background_location_message),
|
|
||||||
textAlign = TextAlign.Center,
|
|
||||||
modifier = Modifier.padding(30.dp),
|
|
||||||
fontSize = 15.sp
|
|
||||||
)
|
|
||||||
Row(
|
|
||||||
modifier = if (WireGuardAutoTunnel.isRunningOnAndroidTv(context)) Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(10.dp) else Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(30.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
horizontalArrangement = Arrangement.SpaceEvenly
|
|
||||||
) {
|
|
||||||
TextButton(onClick = {
|
|
||||||
didShowLocationDisclaimer = true
|
|
||||||
}) {
|
|
||||||
Text(stringResource(id = R.string.no_thanks))
|
|
||||||
}
|
|
||||||
TextButton(modifier = Modifier.focusRequester(focusRequester), onClick = {
|
|
||||||
openSettings()
|
|
||||||
}) {
|
|
||||||
Text(stringResource(id = R.string.turn_on))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
|
isLocationDisclaimerNeeded = false
|
||||||
isBackgroundLocationGranted = true
|
isBackgroundLocationGranted = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
|
||||||
|
if(!fineLocationState.status.isGranted) {
|
||||||
|
isBackgroundLocationGranted = false
|
||||||
|
} else {
|
||||||
|
isLocationDisclaimerNeeded = false
|
||||||
|
isBackgroundLocationGranted = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isLocationDisclaimerNeeded) {
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.Top,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.verticalScroll(scrollState)
|
||||||
|
.padding(padding)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
Icons.Rounded.LocationOff,
|
||||||
|
contentDescription = stringResource(id = R.string.map),
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(30.dp)
|
||||||
|
.size(128.dp)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.prominent_background_location_title),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier.padding(30.dp),
|
||||||
|
fontSize = 20.sp
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.prominent_background_location_message),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier.padding(30.dp),
|
||||||
|
fontSize = 15.sp
|
||||||
|
)
|
||||||
|
Row(
|
||||||
|
modifier = if (WireGuardAutoTunnel.isRunningOnAndroidTv(context)) Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(10.dp) else Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(30.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.SpaceEvenly
|
||||||
|
) {
|
||||||
|
TextButton(onClick = {
|
||||||
|
isLocationDisclaimerNeeded = false
|
||||||
|
}) {
|
||||||
|
Text(stringResource(id = R.string.no_thanks))
|
||||||
|
}
|
||||||
|
TextButton(modifier = Modifier.focusRequester(focusRequester), onClick = {
|
||||||
|
openSettings()
|
||||||
|
}) {
|
||||||
|
Text(stringResource(id = R.string.turn_on))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if(showAuthPrompt) {
|
if(showAuthPrompt) {
|
||||||
AuthorizationPrompt(onSuccess = {
|
AuthorizationPrompt(onSuccess = {
|
||||||
showAuthPrompt = false
|
showAuthPrompt = false
|
||||||
|
|
|
@ -1,22 +1,23 @@
|
||||||
[versions]
|
[versions]
|
||||||
accompanist = "0.31.2-alpha"
|
accompanist = "0.32.0"
|
||||||
activityCompose = "1.8.0"
|
activityCompose = "1.8.0"
|
||||||
androidx-junit = "1.1.5"
|
androidx-junit = "1.1.5"
|
||||||
appcompat = "1.6.1"
|
appcompat = "1.6.1"
|
||||||
biometricKtx = "1.2.0-alpha05"
|
biometricKtx = "1.2.0-alpha05"
|
||||||
coreGoogleShortcuts = "1.1.0"
|
coreGoogleShortcuts = "1.1.0"
|
||||||
coreKtx = "1.12.0"
|
coreKtx = "1.12.0"
|
||||||
|
desugar_jdk_libs = "2.0.4"
|
||||||
espressoCore = "3.5.1"
|
espressoCore = "3.5.1"
|
||||||
firebase-crashlytics-gradle = "2.9.9"
|
firebase-crashlytics-gradle = "2.9.9"
|
||||||
google-services = "4.4.0"
|
google-services = "4.4.0"
|
||||||
hiltAndroid = "2.48"
|
hiltAndroid = "2.48.1"
|
||||||
hiltNavigationCompose = "1.0.0"
|
hiltNavigationCompose = "1.1.0"
|
||||||
junit = "4.13.2"
|
junit = "4.13.2"
|
||||||
kotlinx-serialization-json = "1.5.1"
|
kotlinx-serialization-json = "1.6.0"
|
||||||
lifecycle-runtime-compose = "2.6.2"
|
lifecycle-runtime-compose = "2.6.2"
|
||||||
material-icons-extended = "1.5.4"
|
material-icons-extended = "1.5.4"
|
||||||
material3 = "1.1.2"
|
material3 = "1.1.2"
|
||||||
navigationCompose = "2.7.4"
|
navigationCompose = "2.7.5"
|
||||||
roomVersion = "2.6.0"
|
roomVersion = "2.6.0"
|
||||||
timber = "5.0.1"
|
timber = "5.0.1"
|
||||||
tunnel = "1.0.20230706"
|
tunnel = "1.0.20230706"
|
||||||
|
@ -24,13 +25,13 @@ androidGradlePlugin = "8.3.0-alpha06"
|
||||||
kotlin="1.9.10"
|
kotlin="1.9.10"
|
||||||
ksp="1.9.10-1.0.13"
|
ksp="1.9.10-1.0.13"
|
||||||
composeBom="2023.10.01"
|
composeBom="2023.10.01"
|
||||||
firebaseBom="32.4.0"
|
firebaseBom= "32.5.0"
|
||||||
compose="1.5.4"
|
compose="1.5.4"
|
||||||
crashlytics="18.5.0"
|
crashlytics= "18.5.1"
|
||||||
analytics="21.4.0"
|
analytics="21.5.0"
|
||||||
composeCompiler="1.5.3"
|
composeCompiler="1.5.3"
|
||||||
zxingAndroidEmbedded = "4.3.0"
|
zxingAndroidEmbedded = "4.3.0"
|
||||||
zxingCore = "3.4.1"
|
zxingCore = "3.5.2"
|
||||||
|
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
|
@ -61,6 +62,7 @@ androidx-compose-ui-tooling-preview = { module="androidx.compose.ui:ui-tooling-p
|
||||||
androidx-compose-ui = { module="androidx.compose.ui:ui", version.ref="compose" }
|
androidx-compose-ui = { module="androidx.compose.ui:ui", version.ref="compose" }
|
||||||
|
|
||||||
#hilt
|
#hilt
|
||||||
|
desugar_jdk_libs = { module = "com.android.tools:desugar_jdk_libs", version.ref = "desugar_jdk_libs" }
|
||||||
hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hiltAndroid" }
|
hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hiltAndroid" }
|
||||||
hilt-android-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hiltAndroid" }
|
hilt-android-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hiltAndroid" }
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue