Skip to content

Commit 8e2f68c

Browse files
committed
feat(android): TWA 工程生成(com.entropydecrease.twa)+ CI 签名 alias/APK 重命名对齐
1 parent 002b992 commit 8e2f68c

44 files changed

Lines changed: 1173 additions & 20 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/mobile-release.yml‎

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -146,15 +146,11 @@ jobs:
146146
# 生成 TWA 工程并构建签名 APK,上传 GitHub Release + 服务器下载目录
147147
# (官网「下载 Android APK」直链 = entropydecrease.com/downloads/entropydecrease.apk)。
148148
#
149-
# ⚠️ 首次设置(一次性,需 Android/Java 环境,完成后 CI 全自动):
150-
# 1. 生成 keystore:keytool -genkey -v -keystore entropydecrease.keystore
151-
# -alias entropydecrease -keyalg RSA -keysize 2048 -validity 10000
152-
# 2. 生成 TWA 工程(读 android/twa-manifest.json 配置):
153-
# npx @bubblewrap/cli init --manifest
154-
# https://entropydecrease.com/pwa/manifest.webmanifest --directory android/twa
155-
# 并把生成的 android/twa/ 提交到仓库(CI 只做非交互 update + build)
156-
# 3. GitHub Secrets:ANDROID_KEYSTORE_BASE64(keystore base64)、
157-
# ANDROID_KEYSTORE_PASS(keystore 密码)、ANDROID_KEY_PASS(key 密码)
149+
# ⚠️ 首次设置(2026-08-16 已完成:bubblewrap init 生成 android/twa/ + keystore):
150+
# keystore = android/twa/android.keystore(alias=android,packageId=com.entropydecrease.twa)
151+
# 1. 将 android/twa/android.keystore 转 base64 存入 Secret ANDROID_KEYSTORE_BASE64
152+
# 2. Secret ANDROID_KEYSTORE_PASS / ANDROID_KEY_PASS = keystore 密码(init 时 keytool 输入值)
153+
# CI 只做非交互 update + build;android/twa/ 提交仓库(*.keystore 已 gitignore)
158154
# ---------------------------------------------------------------------------
159155
build-apk:
160156
needs: deploy
@@ -196,7 +192,7 @@ jobs:
196192
bubblewrap build \
197193
--keystorePath /tmp/entropydecrease.keystore \
198194
--keystorePass "$KEYSTORE_PASS" \
199-
--keyAlias entropydecrease \
195+
--keyAlias android \
200196
--keyPass "$KEY_PASS"
201197
- name: Verify APK artifact
202198
run: test -f android/twa/app-release-signed.apk && echo "APK OK"
@@ -212,3 +208,14 @@ jobs:
212208
key: ${{ secrets.SSH_PRIVATE_KEY }}
213209
source: android/twa/app-release-signed.apk
214210
target: /opt/Entropydecrease/downloads
211+
strip_components: 2
212+
- name: Rename APK to canonical name
213+
uses: appleboy/ssh-action@v1
214+
with:
215+
host: ${{ secrets.SERVER_HOST }}
216+
username: ${{ secrets.SERVER_USER }}
217+
key: ${{ secrets.SSH_PRIVATE_KEY }}
218+
script: |
219+
mv -f /opt/Entropydecrease/downloads/app-release-signed.apk \
220+
/opt/Entropydecrease/downloads/entropydecrease.apk
221+
ls -la /opt/Entropydecrease/downloads/entropydecrease.apk

‎.gitignore‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,3 +76,6 @@ client/public/sounds/_backup_original/
7676

7777
# Android 签名密钥(严禁提交)
7878
android/keystore/
79+
80+
# TWA 签名密钥(bubblewrap 生成,严禁提交)
81+
android/twa/*.keystore

‎android/twa/app/build.gradle‎

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
/*
2+
* Copyright 2019 Google Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import groovy.xml.MarkupBuilder
18+
19+
plugins {
20+
id 'com.android.application'
21+
}
22+
23+
def twaManifest = [
24+
applicationId: 'com.entropydecrease.twa',
25+
hostName: 'entropydecrease.com', // The domain being opened in the TWA.
26+
launchUrl: '/pwa/', // The start path for the TWA. Must be relative to the domain.
27+
name: '熵减 - 学习伴侣', // The application name.
28+
launcherName: '熵减', // The name shown on the Android Launcher.
29+
themeColor: '#3B82F6', // The color used for the status bar.
30+
themeColorDark: '#000000', // The color used for the dark status bar.
31+
navigationColor: '#000000', // The color used for the navigation bar.
32+
navigationColorDark: '#000000', // The color used for the dark navbar.
33+
navigationDividerColor: '#000000', // The navbar divider color.
34+
navigationDividerColorDark: '#000000', // The dark navbar divider color.
35+
backgroundColor: '#111827', // The color used for the splash screen background.
36+
enableNotifications: true, // Set to true to enable notification delegation.
37+
// Every shortcut must include the following fields:
38+
// - name: String that will show up in the shortcut.
39+
// - short_name: Shorter string used if |name| is too long.
40+
// - url: Absolute path of the URL to launch the app with (e.g '/create').
41+
// - icon: Name of the resource in the drawable folder to use as an icon.
42+
shortcuts: [],
43+
// The duration of fade out animation in milliseconds to be played when removing splash screen.
44+
splashScreenFadeOutDuration: 300,
45+
generatorApp: 'bubblewrap-cli', // Application that generated the Android Project
46+
// The fallback strategy for when Trusted Web Activity is not available. Possible values are
47+
// 'customtabs' and 'webview'.
48+
fallbackType: 'customtabs',
49+
enableSiteSettingsShortcut: 'true',
50+
orientation: 'portrait-primary',
51+
]
52+
53+
android {
54+
compileSdkVersion 36
55+
namespace "com.entropydecrease.twa"
56+
defaultConfig {
57+
applicationId "com.entropydecrease.twa"
58+
minSdkVersion 21
59+
targetSdkVersion 36
60+
versionCode 1
61+
versionName "1"
62+
63+
// The name for the application
64+
resValue "string", "appName", twaManifest.name
65+
66+
// The name for the application on the Android Launcher
67+
resValue "string", "launcherName", twaManifest.launcherName
68+
69+
// The URL that will be used when launching the TWA from the Android Launcher
70+
def launchUrl = "https://" + twaManifest.hostName + twaManifest.launchUrl
71+
resValue "string", "launchUrl", launchUrl
72+
73+
74+
75+
76+
// The URL the Web Manifest for the Progressive Web App that the TWA points to. This
77+
// is used by Chrome OS and Meta Quest to open the Web version of the PWA instead of
78+
// the TWA, as it will probably give a better user experience for non-mobile devices.
79+
resValue "string", "webManifestUrl", 'https://entropydecrease.com/pwa/manifest.webmanifest'
80+
81+
82+
83+
// This is used by Meta Quest.
84+
resValue "string", "fullScopeUrl", 'https://entropydecrease.com/pwa/'
85+
86+
87+
88+
89+
// The hostname is used when building the intent-filter, so the TWA is able to
90+
// handle Intents to open host url of the application.
91+
resValue "string", "hostName", twaManifest.hostName
92+
93+
// This attribute sets the status bar color for the TWA. It can be either set here or in
94+
// `res/values/colors.xml`. Setting in both places is an error and the app will not
95+
// compile. If not set, the status bar color defaults to #FFFFFF - white.
96+
resValue "color", "colorPrimary", twaManifest.themeColor
97+
98+
// This attribute sets the dark status bar color for the TWA. It can be either set here or in
99+
// `res/values/colors.xml`. Setting in both places is an error and the app will not
100+
// compile. If not set, the status bar color defaults to #000000 - white.
101+
resValue "color", "colorPrimaryDark", twaManifest.themeColorDark
102+
103+
// This attribute sets the navigation bar color for the TWA. It can be either set here or
104+
// in `res/values/colors.xml`. Setting in both places is an error and the app will not
105+
// compile. If not set, the navigation bar color defaults to #FFFFFF - white.
106+
resValue "color", "navigationColor", twaManifest.navigationColor
107+
108+
// This attribute sets the dark navigation bar color for the TWA. It can be either set here
109+
// or in `res/values/colors.xml`. Setting in both places is an error and the app will not
110+
// compile. If not set, the navigation bar color defaults to #000000 - black.
111+
resValue "color", "navigationColorDark", twaManifest.navigationColorDark
112+
113+
// This attribute sets the navbar divider color for the TWA. It can be either
114+
// set here or in `res/values/colors.xml`. Setting in both places is an error and the app
115+
// will not compile. If not set, the divider color defaults to #00000000 - transparent.
116+
resValue "color", "navigationDividerColor", twaManifest.navigationDividerColor
117+
118+
// This attribute sets the dark navbar divider color for the TWA. It can be either
119+
// set here or in `res/values/colors.xml`. Setting in both places is an error and the
120+
//app will not compile. If not set, the divider color defaults to #000000 - black.
121+
resValue "color", "navigationDividerColorDark", twaManifest.navigationDividerColorDark
122+
123+
// Sets the color for the background used for the splash screen when launching the
124+
// Trusted Web Activity.
125+
resValue "color", "backgroundColor", twaManifest.backgroundColor
126+
127+
// Defines a provider authority for the Splash Screen
128+
resValue "string", "providerAuthority", twaManifest.applicationId + '.fileprovider'
129+
130+
// The enableNotification resource is used to enable or disable the
131+
// TrustedWebActivityService, by changing the android:enabled and android:exported
132+
// attributes
133+
resValue "bool", "enableNotification", twaManifest.enableNotifications.toString()
134+
135+
twaManifest.shortcuts.eachWithIndex { shortcut, index ->
136+
resValue "string", "shortcut_name_$index", "$shortcut.name"
137+
resValue "string", "shortcut_short_name_$index", "$shortcut.short_name"
138+
}
139+
140+
// The splashScreenFadeOutDuration resource is used to set the duration of fade out animation in milliseconds
141+
// to be played when removing splash screen. The default is 0 (no animation).
142+
resValue "integer", "splashScreenFadeOutDuration", twaManifest.splashScreenFadeOutDuration.toString()
143+
144+
resValue "string", "generatorApp", twaManifest.generatorApp
145+
146+
resValue "string", "fallbackType", twaManifest.fallbackType
147+
148+
resValue "bool", "enableSiteSettingsShortcut", twaManifest.enableSiteSettingsShortcut
149+
resValue "string", "orientation", twaManifest.orientation
150+
151+
152+
}
153+
buildTypes {
154+
release {
155+
minifyEnabled true
156+
}
157+
}
158+
compileOptions {
159+
sourceCompatibility JavaVersion.VERSION_1_8
160+
targetCompatibility JavaVersion.VERSION_1_8
161+
}
162+
lintOptions {
163+
checkReleaseBuilds false
164+
}
165+
}
166+
167+
task generateShorcutsFile {
168+
assert twaManifest.shortcuts.size() < 5, "You can have at most 4 shortcuts."
169+
twaManifest.shortcuts.eachWithIndex { s, i ->
170+
assert s.name != null, 'Missing `name` in shortcut #' + i
171+
assert s.short_name != null, 'Missing `short_name` in shortcut #' + i
172+
assert s.url != null, 'Missing `icon` in shortcut #' + i
173+
assert s.icon != null, 'Missing `url` in shortcut #' + i
174+
}
175+
176+
def shortcutsFile = new File("$projectDir/src/main/res/xml", "shortcuts.xml")
177+
178+
def xmlWriter = new StringWriter()
179+
def xmlMarkup = new MarkupBuilder(new IndentPrinter(xmlWriter, " ", true))
180+
181+
xmlMarkup
182+
.'shortcuts'('xmlns:android': 'http://schemas.android.com/apk/res/android') {
183+
twaManifest.shortcuts.eachWithIndex { s, i ->
184+
'shortcut'(
185+
'android:shortcutId': 'shortcut' + i,
186+
'android:enabled': 'true',
187+
'android:icon': '@drawable/' + s.icon,
188+
'android:shortcutShortLabel': '@string/shortcut_short_name_' + i,
189+
'android:shortcutLongLabel': '@string/shortcut_name_' + i) {
190+
'intent'(
191+
'android:action': 'android.intent.action.MAIN',
192+
'android:targetPackage': twaManifest.applicationId,
193+
'android:targetClass': twaManifest.applicationId + '.LauncherActivity',
194+
'android:data': s.url)
195+
'categories'('android:name': 'android.intent.category.LAUNCHER')
196+
}
197+
}
198+
}
199+
shortcutsFile.text = xmlWriter.toString() + '\n'
200+
}
201+
202+
preBuild.dependsOn(generateShorcutsFile)
203+
204+
repositories {
205+
206+
}
207+
208+
dependencies {
209+
implementation fileTree(include: ['*.jar'], dir: 'libs')
210+
211+
implementation 'com.google.androidbrowserhelper:androidbrowserhelper:2.6.2'
212+
213+
}

0 commit comments

Comments
 (0)