aboutsummaryrefslogtreecommitdiffstats
path: root/android/app/build.gradle
blob: 8dcfc0495c4020a66dfc76a872ec6f40e0565aee (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
apply plugin: 'com.android.application'

def keystoreProps = new Properties()
def keystoreFile = rootProject.file('keystore.properties')
if (keystoreFile.exists()) keystoreProps.load(new FileInputStream(keystoreFile))
// COINOS_UNSIGNED=1 builds without the keystore even when it is present —
// the reproducible-build check compares two unsigned builds byte for byte,
// and F-Droid builds unsigned and compares against our signed release.
def signRelease = keystoreFile.exists() && System.getenv('COINOS_UNSIGNED') == null

// The bundled shell carries the built web app as assets. It is copied from
// ../dist, which the web build produces first: COINOS_NO_SW=1 bun run build
// (the service worker is left out — inside the WebView the app is served
// from these assets, and a worker would fetch the live site over it).
tasks.register('copyWeb', Copy) {
  def dist = rootProject.file('../dist') // the repo root's dist, not the module's
  doFirst {
    if (!new File(dist, 'index.html').exists())
      throw new GradleException('build the web app first: COINOS_NO_SW=1 bun run build (in the repo root)')
  }
  from(dist) { exclude '*.apk', 'standalone.html', 'sw.js', 'zmq-watcher.js' }
  into layout.buildDirectory.dir('www')
}

// Firebase settings for the fcm build, read from app/google-services.json
// (downloaded from the Firebase console; gitignored). Without it the fcm
// build still builds, and pushes through UnifiedPush like the webview one.
def firebase = [appId: '', apiKey: '', projectId: '', senderId: '']
def gsFile = file('google-services.json')
if (gsFile.exists()) {
  def gs = new groovy.json.JsonSlurper().parse(gsFile)
  def client = gs.client.find { it.client_info?.android_client_info?.package_name == 'io.coinos.beta' }
  if (client) {
    firebase = [
      appId: client.client_info.mobilesdk_app_id,
      apiKey: client.api_key[0].current_key,
      projectId: gs.project_info.project_id,
      senderId: gs.project_info.project_number,
    ]
  }
}

android {
  namespace 'io.coinos.app'
  compileSdk 35
  defaultConfig {
    applicationId 'io.coinos.app'
    minSdk 24
    targetSdk 35
    versionCode 15
    versionName '1.14'
  }
  // Two builds of the same wallet.
  //
  //  twa      — Chrome renders the site (the better browser, and the one
  //             people already have installed). Its storage is Chrome's, so
  //             nothing outside Chrome can wake the wallet, and on a phone
  //             without Google Play Services nothing inside it can either.
  //  webview  — the wallet runs in the app, so a UnifiedPush message can
  //             start it off-screen and let it answer. A separate app id,
  //             because it is a separate wallet: its storage is its own.
  //  fdroid   — the webview shell with the web app BUNDLED as assets: what
  //             runs is what was built from this source tree, reproducibly,
  //             which is what F-Droid publishes. No service worker, no
  //             remote code; the live site is only reached for its APIs.
  // Google's dependency-metadata block (a Play-only extra in the APK
  // signing block, encrypted to Google) is refused by F-Droid's scanner.
  dependenciesInfo {
    includeInApk = false
    includeInBundle = false
  }
  flavorDimensions += 'shell'
  buildFeatures { buildConfig true }
  productFlavors {
    twa {
      dimension 'shell'
      isDefault = true
      buildConfigField 'boolean', 'BUNDLED', 'false'
    }
    webview {
      dimension 'shell'
      applicationId 'io.coinos.webview'
      versionNameSuffix '-webview'
      resValue 'string', 'app_name', 'Coinos (UnifiedPush)'
      buildConfigField 'boolean', 'BUNDLED', 'false'
    }
    // fcm — the webview shell plus Firebase Cloud Messaging: push without a
    //          distributor app on phones with Google Play Services (see
    //          src/fcm/java/io/coinos/app/Fcm.java). Its own app id, so it
    //          installs beside the TWA for a side-by-side trial.
    fcm {
      dimension 'shell'
      applicationId 'io.coinos.beta'
      versionNameSuffix '-beta'
      resValue 'string', 'app_name', 'Coinos Beta'
      buildConfigField 'boolean', 'BUNDLED', 'false'
      buildConfigField 'String', 'FCM_APP_ID', "\"${firebase.appId}\""
      buildConfigField 'String', 'FCM_API_KEY', "\"${firebase.apiKey}\""
      buildConfigField 'String', 'FCM_PROJECT_ID', "\"${firebase.projectId}\""
      buildConfigField 'String', 'FCM_SENDER_ID', "\"${firebase.senderId}\""
    }
    fdroid {
      dimension 'shell'
      applicationId 'io.coinos.fdroid'
      resValue 'string', 'app_name', 'Coinos'
      buildConfigField 'boolean', 'BUNDLED', 'true'
    }
  }
  sourceSets {
    // the webview shell has no Firebase: its Fcm is a stub
    webview {
      java.srcDirs += 'src/nofcm/java'
    }
    // the webview shell with Firebase (the manifest is webview's plus FCM)
    fcm {
      java.srcDirs = ['src/webview/java', 'src/fcm/java']
      res.srcDirs = ['src/webview/res']
      manifest.srcFile 'src/fcm/AndroidManifest.xml'
    }
    // the same shell as webview, plus the bundled web app
    fdroid {
      java.srcDirs = ['src/webview/java', 'src/nofcm/java']
      res.srcDirs = ['src/webview/res']
      manifest.srcFile 'src/webview/AndroidManifest.xml'
      assets.srcDirs += layout.buildDirectory.dir('www').get().asFile
    }
  }
  signingConfigs {
    release {
      if (signRelease) {
        storeFile rootProject.file(keystoreProps['storeFile'])
        storePassword keystoreProps['storePassword']
        keyAlias keystoreProps['keyAlias']
        keyPassword keystoreProps['keyPassword']
      }
    }
  }
  buildTypes {
    release {
      minifyEnabled false
      if (signRelease) signingConfig signingConfigs.release
    }
  }
}
dependencies {
  // only the TWA shell needs Chrome's helper; the WebView build carries none
  twaImplementation 'com.google.androidbrowserhelper:androidbrowserhelper:2.5.0'
  // WebAuthn (passkeys) inside the WebView builds — androidx, no Google services
  webviewImplementation 'androidx.webkit:webkit:1.14.0'
  fcmImplementation 'androidx.webkit:webkit:1.14.0'
  fdroidImplementation 'androidx.webkit:webkit:1.14.0'
  // only the fcm build carries Google's push client
  fcmImplementation 'com.google.firebase:firebase-messaging:24.1.0'
}

preBuild.dependsOn copyWeb