Skip to content

Commit

Permalink
Merge pull request #28 from opatry/tasks-app-android
Browse files Browse the repository at this point in the history
:tasks-app-android
  • Loading branch information
opatry authored Sep 30, 2024
2 parents 7706938 + 9da2e26 commit 7dc3b32
Show file tree
Hide file tree
Showing 56 changed files with 1,100 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ jobs:
with:
add-job-summary-as-pr-comment: on-failure

- name: 🔨 Build Android App 📱
env:
PLAYSTORE_SECRET_PASSPHRASE: ${{ secrets.PLAYSTORE_SECRET_PASSPHRASE }}
run: |
./_ci/decrypt_secrets.sh
./gradlew --no-daemon :tasks-app-android:assembleStoreRelease
- name: 🔨 Build Desktop App 🖥️
run: ./gradlew --no-daemon :tasks-app-desktop:assemble

Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,8 @@ local.properties

.kotlin/errors/errors-*.log
.kotlin/sessions/

tasks-app-android/google-services.json

*token_cache.*
client_secret_*.apps.googleusercontent.com.json
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,24 @@ A basic TODO list application based on [Google Tasks REST API](https://developer
- [Jetpack Compose](https://developer.android.com/jetpack/compose)
- [Coil](https://coil-kt.github.io/coil/)

## Local development

<details>
<summary>See details…</summary>
Decrypt `*.gpg` files needed for development, and copy decrypted versions in proper places.

```bash
PLAYSTORE_SECRET_PASSPHRASE=MY_SECRET ./_ci/decrypt_secrets.sh
```

### Updating `google-services.json`

The production `google-services.json` file is ignored by SCM to avoid exposing API keys in public repository.
To update it, download the new version, encrypt it using `gpg --symmetric --cipher-algo AES256 google-services.json`
and store this in `_ci/google-services.json.gpg`.
The `decrypt_secrets.sh` will take it into account.
</details>

## License

```
Expand Down
Binary file not shown.
35 changes: 35 additions & 0 deletions _ci/decrypt_file.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/usr/bin/env bash

# Script to decrypt signing keystore
# see https://help.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets

set -euo pipefail

if [ $# -ne 1 ] && [ $# -ne 2 ] ; then
echo "Usage: $0 input_file.gpg [output_file]"
echo " Decrypts in-place if no output_file is provided"
exit 1
fi

input_file=${1}
output_file=${2:-""}

if [ -z "${output_file}" ]; then
output_file="$(dirname "${input_file}")/$(basename "${input_file}" .gpg)"
fi

if [ ! -f "${input_file}" ]; then
echo "${input_file} doesn't exist"
exit 1
fi

output_dir=$(dirname "${output_file}")
mkdir -p "${output_dir}"

# convert potentially relative path to absolute
output_file="$(cd "$(dirname "${output_file}")"; pwd)/$(basename "${output_file}")"

# --batch to prevent interactive command --yes to assume "yes" for questions
gpg --quiet --batch --yes --decrypt \
--passphrase="${PLAYSTORE_SECRET_PASSPHRASE}" \
--output "${output_file}" "${input_file}"
19 changes: 19 additions & 0 deletions _ci/decrypt_secrets.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/usr/bin/env bash

set -euo pipefail

origin=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) || exit

cd "${origin}"
./decrypt_file.sh "${origin}/google-services.json.gpg" \
"${origin}/../tasks-app-android/google-services.json"

mkdir -p "${origin}/../tasks-app-shared/src/jvmMain/composeResources/files"
./decrypt_file.sh "${origin}/client_secret_191682949161-esokhlfh7uugqptqnu3su9vgqmvltv95.apps.googleusercontent.com.json.gpg" \
"${origin}/../tasks-app-shared/src/jvmMain/composeResources/files/client_secret_191682949161-esokhlfh7uugqptqnu3su9vgqmvltv95.apps.googleusercontent.com.json"

# for now Android & desktop apps use the same GCP Web app credentials, kept split/duplicated in their own source set to ease changing strategy
# it's the same for `store` & `dev` flavors for now, keep in `src/main/assets` but could be dup again in `src/store/assets` & `src/dev/assets` respectively
mkdir -p "${origin}/../tasks-app-android/src/main/assets"
cp "${origin}/../tasks-app-shared/src/jvmMain/composeResources/files/client_secret_191682949161-esokhlfh7uugqptqnu3su9vgqmvltv95.apps.googleusercontent.com.json" \
"${origin}/../tasks-app-android/src/main/assets"
Binary file added _ci/google-services.json.gpg
Binary file not shown.
5 changes: 5 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,14 @@
plugins {
alias(libs.plugins.jetbrains.kotlin.multiplatform) apply false
alias(libs.plugins.jetbrains.kotlin.jvm) apply false
alias(libs.plugins.jetbrains.kotlin.android) apply false
alias(libs.plugins.jetbrains.kotlin.serialization) apply false
alias(libs.plugins.jetbrains.kotlin.compose.compiler) apply false
alias(libs.plugins.jetbrains.compose) apply false
alias(libs.plugins.ksp) apply false
alias(libs.plugins.androidx.room) apply false
alias(libs.plugins.android.application) apply false
alias(libs.plugins.android.library) apply false
alias(libs.plugins.google.services) apply false
alias(libs.plugins.firebase.crashlytics) apply false
}
24 changes: 24 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
[versions]
tasksApp-name = "0.1.0"
compileSdk = "34"
minSdk = "24"
targetSdk = "34"
agp = "8.6.1"
kotlin = "2.0.20"
ksp = "2.0.20-1.0.24"
coroutines = "1.9.0"
Expand All @@ -12,8 +17,10 @@ coil = "3.0.0-alpha10"
[libraries]
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
kotlinx-coroutines-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "coroutines" }
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" }

kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version = "0.6.1" }
kotlinx-serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version = "1.7.2" }

ktor-server-core = { module = "io.ktor:ktor-server-core-jvm", version.ref = "ktor" }
ktor-server-cio = { module = "io.ktor:ktor-server-cio", version.ref = "ktor" }
Expand Down Expand Up @@ -41,9 +48,21 @@ koin-androidx-startup = { module = "io.insert-koin:koin-androidx-startup" }
koin-compose = { module = "io.insert-koin:koin-compose" }
koin-compose-viewmodel = { module = "io.insert-koin:koin-compose-viewmodel" }

androidx-compose-m3-adaptive = { module = "androidx.compose.material3.adaptive:adaptive", version = "1.0.0" }
androidx-compose-m3-adaptive-layout = { module = "androidx.compose.material3.adaptive:adaptive-layout", version = "1.0.0" }
androidx-compose-m3-adaptive-navigation = { module = "androidx.compose.material3.adaptive:adaptive-navigation", version = "1.0.0" }

coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" }
coil-network = { module = "io.coil-kt.coil3:coil-network-ktor2", version.ref = "coil" }

androidx-appcompat = { module = "androidx.appcompat:appcompat", version = "1.7.0" }
androidx-activity-compose = { module = "androidx.activity:activity-compose", version = "1.9.2" }

firebase-bom = { module = "com.google.firebase:firebase-bom", version = "33.3.0" }
firebase-crashlytics = { module = "com.google.firebase:firebase-crashlytics" }

play-services-auth = { module = "com.google.android.gms:play-services-auth", version = "21.2.0" }

[bundles]
ktor-server = ["ktor-server-core", "ktor-server-cio"]
ktor-client = [
Expand All @@ -58,10 +77,15 @@ ktor-client = [
coil = ["coil-compose", "coil-network"]

[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
android-library = { id = "com.android.library", version.ref = "agp" }
jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
jetbrains-kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
jetbrains-kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
jetbrains-kotlin-compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
jetbrains-compose = { id = "org.jetbrains.compose", version.ref = "compose-plugin" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
androidx-room = { id = "androidx.room", version.ref = "room" }
google-services = { id = "com.google.gms.google-services", version = "4.4.2" }
firebase-crashlytics = { id = "com.google.firebase.crashlytics", version = "3.0.2" }
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,4 @@ include(":lucide-icons")
include(":tasks-core")
include(":tasks-app-shared")
include(":tasks-app-desktop")
include(":tasks-app-android")
1 change: 1 addition & 0 deletions tasks-app-android/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
147 changes: 147 additions & 0 deletions tasks-app-android/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/*
* Copyright (c) 2024 Olivier Patry
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software
* is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.jetbrains.kotlin.android)
alias(libs.plugins.jetbrains.kotlin.compose.compiler)
alias(libs.plugins.jetbrains.compose)
alias(libs.plugins.google.services)
alias(libs.plugins.firebase.crashlytics)
}

val versionCodeValue = System.getenv("CI_BUILD_NUMBER")?.toIntOrNull() ?: 1

android {
namespace = "net.opatry.tasks.app"
compileSdk = libs.versions.compileSdk.get().toInt()

defaultConfig {
applicationId = "net.opatry.tasks.app"
minSdk = libs.versions.minSdk.get().toInt()
targetSdk = libs.versions.targetSdk.get().toInt()
versionCode = versionCodeValue
versionName = libs.versions.tasksApp.name.get()

resourceConfigurations += listOf("en", "fr")
}

buildTypes {
release {
isMinifyEnabled = false
}
}

compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

kotlin {
jvmToolchain(17)
}

flavorDimensions += "target"
productFlavors {
create("dev") {
isDefault = true
applicationIdSuffix = ".dev"
dimension = "target"

manifestPlaceholders["crashlyticsEnabled"] = false
}

create("store") {
dimension = "target"
}
}

signingConfigs {
create("dev") {
storeFile = file("dev.keystore")
storePassword = "devdev"
keyAlias = "dev"
keyPassword = "devdev"
}
}

buildTypes {
getByName("debug") {
manifestPlaceholders["crashlyticsEnabled"] = false

signingConfig = signingConfigs.getByName("dev")
}
getByName("release") {
manifestPlaceholders["crashlyticsEnabled"] = true

signingConfig = signingConfigs.getByName("dev")

isMinifyEnabled = false
isShrinkResources = false
}
}

lint {
checkDependencies = true
}

buildFeatures {
compose = true
}

packaging {
resources.pickFirsts += listOf(
"META-INF/INDEX.LIST",
"META-INF/io.netty.versions.properties",
)
}
}

dependencies {
implementation(libs.kotlinx.coroutines.android) {
because("requires Dispatchers.Main & co at runtime for Jvm")
// java.lang.IllegalStateException: Module with the Main dispatcher is missing. Add dependency providing the Main dispatcher, e.g. 'kotlinx-coroutines-android' and ensure it has the same version as 'kotlinx-coroutines-core'
// see also https://github.com/JetBrains/compose-jb/releases/tag/v1.1.1
}

implementation(project.dependencies.platform(libs.koin.bom))
implementation(libs.koin.androidx.startup)
implementation(libs.koin.android)
implementation(libs.koin.compose)
implementation(libs.koin.compose.viewmodel)

implementation(compose.material3)
implementation(libs.androidx.activity.compose)

implementation(libs.androidx.appcompat)

implementation(libs.kotlinx.serialization)

implementation(platform(libs.firebase.bom))
implementation(libs.firebase.crashlytics)

implementation(libs.play.services.auth)

implementation(project(":google:oauth"))
implementation(project(":google:tasks"))
implementation(project(":tasks-app-shared"))
}
Binary file added tasks-app-android/dev.keystore
Binary file not shown.
23 changes: 23 additions & 0 deletions tasks-app-android/src/dev/google-services.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"project_info": {
"project_number": "44853535682",
"project_id": "tasks-app-c632e",
"storage_bucket": "tasks-app-c632e.appspot.com"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:44853535682:android:912604c7085bbeb11e2c5b",
"android_client_info": {
"package_name": "net.opatry.tasks.app.dev"
}
},
"api_key": [
{
"current_key": ""
}
]
}
],
"configuration_version": "1"
}
15 changes: 15 additions & 0 deletions tasks-app-android/src/dev/res/drawable/ic_launcher_foreground.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#023232">
<group android:scaleX="0.4466"
android:scaleY="0.4466"
android:translateX="6.6408"
android:translateY="6.6408">
<path
android:fillColor="@android:color/white"
android:pathData="M22,5.18L10.59,16.6l-4.24,-4.24l1.41,-1.41l2.83,2.83l10,-10L22,5.18zM19.79,10.22C19.92,10.79 20,11.39 20,12c0,4.42 -3.58,8 -8,8s-8,-3.58 -8,-8c0,-4.42 3.58,-8 8,-8c1.58,0 3.04,0.46 4.28,1.25l1.44,-1.44C16.1,2.67 14.13,2 12,2C6.48,2 2,6.48 2,12c0,5.52 4.48,10 10,10s10,-4.48 10,-10c0,-1.19 -0.22,-2.33 -0.6,-3.39L19.79,10.22z"/>
</group>
</vector>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#81FFDE</color>
</resources>
Loading

0 comments on commit 7dc3b32

Please sign in to comment.