Skip to content

Commit cf2a9c7

Browse files
committed
CI release keystore signing, TileService, adaptive icons, signing template
Made-with: Cursor
1 parent a503349 commit cf2a9c7

14 files changed

Lines changed: 241 additions & 19 deletions

.github/workflows/build.yml

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
# Release signing (optional): set repository secrets below. If ANDROID_KEYSTORE_BASE64 is unset, publish uses the debug keystore (e.g. PRs from forks).
2+
# ANDROID_KEYSTORE_BASE64 — Base64 of release.keystore (PowerShell: [Convert]::ToBase64String([IO.File]::ReadAllBytes("release.keystore")))
3+
# ANDROID_SIGNING_STORE_PASS — keystore password
4+
# ANDROID_SIGNING_KEY_PASS — key password (often same as store)
5+
# ANDROID_SIGNING_KEY_ALIAS — optional; defaults to ringcontroller if empty
6+
17
name: build
28

39
on:
@@ -30,10 +36,39 @@ jobs:
3036

3137
# Native AOT Release: publish with RID (plain publish without -r can fail MSB3030 on RingController.so).
3238
- name: Publish (Release, android-arm64)
33-
run: dotnet publish src/RingController/RingController.csproj -c Release -f net10.0-android -r android-arm64
39+
shell: pwsh
40+
env:
41+
ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
42+
ANDROID_SIGNING_STORE_PASS: ${{ secrets.ANDROID_SIGNING_STORE_PASS }}
43+
ANDROID_SIGNING_KEY_PASS: ${{ secrets.ANDROID_SIGNING_KEY_PASS }}
44+
ANDROID_SIGNING_KEY_ALIAS: ${{ secrets.ANDROID_SIGNING_KEY_ALIAS }}
45+
run: |
46+
$proj = "src/RingController/RingController.csproj"
47+
$args = @(
48+
"publish", $proj,
49+
"-c", "Release",
50+
"-f", "net10.0-android",
51+
"-r", "android-arm64"
52+
)
53+
if ($env:ANDROID_KEYSTORE_BASE64) {
54+
$ksPath = Join-Path $env:GITHUB_WORKSPACE "src/RingController/release.keystore"
55+
$bytes = [Convert]::FromBase64String($env:ANDROID_KEYSTORE_BASE64)
56+
[IO.File]::WriteAllBytes($ksPath, $bytes)
57+
$alias = $env:ANDROID_SIGNING_KEY_ALIAS
58+
if ([string]::IsNullOrWhiteSpace($alias)) { $alias = "ringcontroller" }
59+
$args += @(
60+
"-p:AndroidKeyStore=true",
61+
"-p:AndroidSigningKeyStore=$ksPath",
62+
"-p:AndroidSigningStorePass=env:ANDROID_SIGNING_STORE_PASS",
63+
"-p:AndroidSigningKeyPass=env:ANDROID_SIGNING_KEY_PASS",
64+
"-p:AndroidSigningKeyAlias=$alias"
65+
)
66+
Write-Host "Publishing with release keystore (alias: $alias)"
67+
} else {
68+
Write-Warning "ANDROID_KEYSTORE_BASE64 not set — publishing with default debug keystore"
69+
}
70+
dotnet @args
3471
35-
# RID 付き publish では APK は net10.0-android 直下ではなく android-arm64/ や publish/ に出ることがある
36-
# upload-artifact v7 の archive:false は単一ファイルのみ → 先に 1 パスに確定する
3772
- name: Resolve signed APK path
3873
id: signed_apk
3974
shell: pwsh
@@ -46,7 +81,6 @@ jobs:
4681
if ($found.Count -gt 1) { Write-Warning "Multiple APKs ($($found.Count)); uploading: $($pick.FullName)" }
4782
"apk_path=$($pick.FullName)" >> $env:GITHUB_OUTPUT
4883
49-
# archive:false で ZIP せず APK をそのままアップロード(ブラウザでそのまま取得可)。artifact 名はファイル名になる
5084
- name: Upload signed APK
5185
uses: actions/upload-artifact@v7
5286
with:

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,8 @@ MultiTimer/build.log
1717

1818
# Local logs
1919
LeitzphoneRingController/logs/
20+
21+
# Android release signing (never commit keystores or local props)
22+
*.jks
23+
*.keystore
24+
src/RingController/Signing.Local.props
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<Project>
2+
<!-- Local release signing: copy Signing.Local.props.template → Signing.Local.props (gitignored). -->
3+
<Import Project="$(MSBuildThisFileDirectory)Signing.Local.props" Condition="Exists('$(MSBuildThisFileDirectory)Signing.Local.props')" />
4+
</Project>

src/RingController/MainActivity.cs

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Reflection;
12
using Android;
23
using Android.App;
34
using Android.Content;
@@ -132,7 +133,11 @@ protected override void OnCreate(Bundle? savedInstanceState)
132133
statusStrip.AddView(brightnessPermRow, permLp);
133134
UpdateBrightnessPermissionRowVisibility();
134135

135-
ringPanelHost = new RingSettingsPanelHost(this, cfg => RingConfigStore.Save(this, cfg));
136+
ringPanelHost = new RingSettingsPanelHost(this, cfg =>
137+
{
138+
RingConfigStore.Save(this, cfg);
139+
UpdateBrightnessPermissionRowVisibility();
140+
});
136141
ringPanelHost.BuildInto(container, config);
137142

138143
SetContentView(outer);
@@ -193,9 +198,10 @@ void UpdateStatus()
193198
void UpdateBrightnessPermissionRowVisibility()
194199
{
195200
if (brightnessPermRow == null) return;
196-
brightnessPermRow.Visibility = Android.Provider.Settings.System.CanWrite(this)
197-
? ViewStates.Gone
198-
: ViewStates.Visible;
201+
var cfg = RingConfigStore.LoadOrCreate(this);
202+
var wantBrightness = RingConfig.ConfigUsesBrightnessControl(cfg);
203+
var needGrant = wantBrightness && !Android.Provider.Settings.System.CanWrite(this);
204+
brightnessPermRow.Visibility = needGrant ? ViewStates.Visible : ViewStates.Gone;
199205
}
200206

201207
protected override void OnResume()
@@ -224,6 +230,22 @@ public void OnSensorChanged(SensorEvent? e)
224230

225231
public void OnAccuracyChanged(Sensor? sensor, [GeneratedEnum] SensorStatus accuracy) { }
226232

233+
/// <summary>
234+
/// Version string from embedded assembly metadata (SDK sets from &lt;Version&gt; / InformationalVersion; CI can override).
235+
/// </summary>
236+
static string GetDisplayVersionFromAssembly()
237+
{
238+
var asm = Assembly.GetExecutingAssembly();
239+
var info = asm.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
240+
if (info?.InformationalVersion is { Length: > 0 } iv)
241+
{
242+
var plus = iv.IndexOf('+');
243+
return plus >= 0 ? iv[..plus] : iv;
244+
}
245+
246+
return asm.GetName().Version?.ToString() ?? "?";
247+
}
248+
227249
void ShowVersionDialog()
228250
{
229251
var pm = PackageManager;
@@ -240,21 +262,44 @@ void ShowVersionDialog()
240262
return;
241263
}
242264
var label = ApplicationInfo?.LoadLabel(pm)?.ToString() ?? GetString(Resource.String.app_name);
243-
var verName = info.VersionName ?? "";
265+
// Display version: MSBuild / assembly (InformationalVersion or AssemblyVersion); matches CI -p:Version=...
266+
var verDisplay = GetDisplayVersionFromAssembly();
244267
var verCode = Build.VERSION.SdkInt >= BuildVersionCodes.P
245268
? (long)info.LongVersionCode
246269
: info.VersionCode;
247270
var url = GetString(Resource.String.app_info_url);
248-
var msg = $"{label}\n\n{GetString(Resource.String.version_label)} {verName} ({verCode})\n\n{url}";
249271

250272
var root = new LinearLayout(this) { Orientation = Orientation.Vertical };
251273
var dlgPad = (int)(22 * dp);
252274
root.SetPadding(dlgPad, dlgPad, dlgPad, dlgPad);
253275

254-
var msgTv = new TextView(this) { Text = msg, TextSize = 15f };
276+
var titleBlock = $"{label}\n\n{GetString(Resource.String.version_label)} {verDisplay} ({verCode})";
277+
var msgTv = new TextView(this) { Text = titleBlock, TextSize = 15f };
255278
msgTv.SetTextColor(Clr(Resource.Color.md_theme_onSurface));
256279
root.AddView(msgTv);
257280

281+
var urlTv = new TextView(this) { Text = url, TextSize = 15f };
282+
urlTv.SetTextColor(Clr(Resource.Color.md_theme_primary));
283+
urlTv.SetPadding(0, (int)(12 * dp), 0, 0);
284+
urlTv.PaintFlags |= Android.Graphics.PaintFlags.UnderlineText;
285+
urlTv.Clickable = true;
286+
urlTv.Focusable = true;
287+
var urlTa = ObtainStyledAttributes(new[] { Android.Resource.Attribute.SelectableItemBackgroundBorderless });
288+
urlTv.Background = urlTa.GetDrawable(0);
289+
urlTa.Recycle();
290+
urlTv.Click += (_, _) =>
291+
{
292+
try
293+
{
294+
StartActivity(new Intent(Intent.ActionView, Android.Net.Uri.Parse(url!)));
295+
}
296+
catch
297+
{
298+
// ignore
299+
}
300+
};
301+
root.AddView(urlTv);
302+
258303
var btnRow = new LinearLayout(this) { Orientation = Orientation.Horizontal };
259304
btnRow.SetGravity(GravityFlags.CenterHorizontal);
260305
var btnRowLp = new LinearLayout.LayoutParams(
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!-- Leitz-style dark "smoked glass" base for adaptive icon (full-bleed; launcher applies squircle mask). -->
3+
<shape xmlns:android="http://schemas.android.com/apk/res/android"
4+
android:shape="rectangle">
5+
<gradient
6+
android:angle="135"
7+
android:centerColor="#FF1E1E22"
8+
android:endColor="#FF060608"
9+
android:startColor="#FF3A3A40"
10+
android:type="linear" />
11+
</shape>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!-- 12 radial ticks (outer r=31, inner r=25 — shorter spokes, larger center void). -->
3+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
4+
android:width="108dp"
5+
android:height="108dp"
6+
android:viewportWidth="108"
7+
android:viewportHeight="108">
8+
<path
9+
android:fillColor="#00000000"
10+
android:pathData="M 54,23 L 54,29 M 69.5,27.15 L 66.5,32.35 M 80.85,38.5 L 75.65,41.5 M 85,54 L 79,54 M 80.85,69.5 L 75.65,66.5 M 69.5,80.85 L 66.5,75.65 M 54,85 L 54,79 M 38.5,80.85 L 41.5,75.65 M 27.15,69.5 L 32.35,66.5 M 23,54 L 29,54 M 27.15,38.5 L 32.35,41.5 M 38.5,27.15 L 41.5,32.35"
11+
android:strokeWidth="3.5"
12+
android:strokeColor="#FFFFFFFF"
13+
android:strokeLineCap="round" />
14+
</vector>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!-- QS tile: 10 filled ticks (36° spacing) for ring readout. Outer r=9.65 (~2.35dp inset) to avoid circular-tile clipping; was 10.4 and looked “eaten” at edge. -->
3+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
4+
android:width="24dp"
5+
android:height="24dp"
6+
android:viewportWidth="24"
7+
android:viewportHeight="24">
8+
<path
9+
android:fillColor="#FFFFFF"
10+
android:pathData="M 11.092,2.393 L 12.908,2.393 L 12.616,5.479 L 11.384,5.479 Z M 16.912,3.694 L 18.382,4.761 L 16.332,7.087 L 15.334,6.362 Z M 20.856,8.168 L 21.418,9.895 L 18.392,10.571 L 18.011,9.399 Z M 21.418,14.105 L 20.856,15.832 L 18.011,14.601 L 18.392,13.429 Z M 18.382,19.239 L 16.912,20.306 L 15.334,17.638 L 16.332,16.913 Z M 12.908,21.607 L 11.092,21.607 L 11.384,18.521 L 12.616,18.521 Z M 7.088,20.306 L 5.618,19.239 L 7.668,16.913 L 8.666,17.638 Z M 3.144,15.832 L 2.582,14.105 L 5.608,13.429 L 5.989,14.601 Z M 2.582,9.895 L 3.144,8.168 L 5.989,9.399 L 5.608,10.571 Z M 5.618,4.761 L 7.088,3.694 L 8.666,6.362 L 7.668,7.087 Z" />
11+
</vector>
Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
<?xml version="1.0" encoding="utf-8"?>
2-
<!-- Simple monochrome icon for notifications (adaptive mipmap is not valid for SetSmallIcon). -->
2+
<!-- Notification / status: same ring motif as launcher (12 ticks for legibility at 24dp). -->
33
<vector xmlns:android="http://schemas.android.com/apk/res/android"
44
android:width="24dp"
55
android:height="24dp"
66
android:viewportWidth="24"
77
android:viewportHeight="24">
88
<path
9-
android:fillColor="#FFFFFFFF"
10-
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z" />
9+
android:fillColor="#00000000"
10+
android:pathData="M 12,4.5 L 12,7 M 15.75,5.5 L 14.5,7.67 M 18.5,8.25 L 16.33,9.5 M 19.5,12 L 17,12 M 18.5,15.75 L 16.33,14.5 M 15.75,18.5 L 14.5,16.33 M 12,19.5 L 12,17 M 8.25,18.5 L 9.5,16.33 M 5.5,15.75 L 7.67,14.5 M 4.5,12 L 7,12 M 5.5,8.25 L 7.67,9.5 M 8.25,5.5 L 9.5,7.67"
11+
android:strokeWidth="1.35"
12+
android:strokeColor="#FFFFFFFF"
13+
android:strokeLineCap="round" />
1114
</vector>
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
2-
<background android:drawable="@mipmap/appicon_background" />
3-
<foreground android:drawable="@mipmap/appicon_foreground" />
2+
<background android:drawable="@drawable/appicon_background" />
3+
<foreground android:drawable="@drawable/appicon_foreground" />
4+
<!-- API 33+: Material You monochrome / themed icon (same line-art glyph). -->
5+
<monochrome android:drawable="@drawable/appicon_foreground" />
46
</adaptive-icon>
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
2-
<background android:drawable="@mipmap/appicon_background" />
3-
<foreground android:drawable="@mipmap/appicon_foreground" />
2+
<background android:drawable="@drawable/appicon_background" />
3+
<foreground android:drawable="@drawable/appicon_foreground" />
4+
<monochrome android:drawable="@drawable/appicon_foreground" />
45
</adaptive-icon>

0 commit comments

Comments
 (0)