Add release tooling and generated launcher icon
This commit is contained in:
@@ -75,9 +75,28 @@ The project is a standard Gradle Android project:
|
|||||||
./gradlew assembleDebug
|
./gradlew assembleDebug
|
||||||
```
|
```
|
||||||
|
|
||||||
For local release builds, `build.sh` uses the Android SDK and JDK available on
|
Release builds require local signing configuration:
|
||||||
the machine, writes `local.properties`, builds a release APK, and copies it to a
|
|
||||||
local sync folder if present.
|
```bash
|
||||||
|
./gradlew assembleRelease
|
||||||
|
```
|
||||||
|
|
||||||
|
## Releases
|
||||||
|
|
||||||
|
Public releases use two-part versions such as `0.1`, `0.2`, and eventually
|
||||||
|
`1.0`.
|
||||||
|
|
||||||
|
Gitea automatically provides source archives for release tags, so the release
|
||||||
|
assets only need the APK and its checksum. A release can be created with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p local
|
||||||
|
printf '%s' 'your-gitea-token' > local/gitea-token
|
||||||
|
|
||||||
|
GITEA_BASE_URL=git.ajpanton.se \
|
||||||
|
GITEA_TOKEN_FILE=local/gitea-token \
|
||||||
|
scripts/release-gitea.sh 0.1
|
||||||
|
```
|
||||||
|
|
||||||
## Development Note
|
## Development Note
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import se.ajpanton.statusbartweak.runtime.features.clock.ClockLayoutTextFactory;
|
|||||||
import se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.AnchorSide;
|
import se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.AnchorSide;
|
||||||
import se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.Bounds;
|
import se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.Bounds;
|
||||||
import se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.LayoutPosition;
|
import se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.LayoutPosition;
|
||||||
|
import se.ajpanton.statusbartweak.shell.settings.SbtDefaults;
|
||||||
|
|
||||||
final class ClockLayout {
|
final class ClockLayout {
|
||||||
private static final int MIDDLE_COLLISION_MARGIN_PX = 2;
|
private static final int MIDDLE_COLLISION_MARGIN_PX = 2;
|
||||||
@@ -103,6 +104,7 @@ final class ClockLayout {
|
|||||||
measuredRows = positionRowsFromLogicalSteps(
|
measuredRows = positionRowsFromLogicalSteps(
|
||||||
measuredRows,
|
measuredRows,
|
||||||
Math.max(1, autoGapSteps),
|
Math.max(1, autoGapSteps),
|
||||||
|
Math.max(1, baseHeightSteps),
|
||||||
Math.max(1, statusbarHeight),
|
Math.max(1, statusbarHeight),
|
||||||
verticalOffsetSteps,
|
verticalOffsetSteps,
|
||||||
bottomRowBaselineAnchorY);
|
bottomRowBaselineAnchorY);
|
||||||
@@ -216,6 +218,7 @@ final class ClockLayout {
|
|||||||
private static ArrayList<MeasuredClockRow> positionRowsFromLogicalSteps(
|
private static ArrayList<MeasuredClockRow> positionRowsFromLogicalSteps(
|
||||||
ArrayList<MeasuredClockRow> measuredRows,
|
ArrayList<MeasuredClockRow> measuredRows,
|
||||||
int autoGapSteps,
|
int autoGapSteps,
|
||||||
|
int baseHeightSteps,
|
||||||
int statusbarHeight,
|
int statusbarHeight,
|
||||||
int verticalOffsetSteps,
|
int verticalOffsetSteps,
|
||||||
int bottomRowBaselineAnchorY
|
int bottomRowBaselineAnchorY
|
||||||
@@ -238,7 +241,10 @@ final class ClockLayout {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
MeasuredClockRow nextRow = measuredRows.get(rowIndex + 1);
|
MeasuredClockRow nextRow = measuredRows.get(rowIndex + 1);
|
||||||
int gapSteps = resolveGapSteps(nextRow.source.gapBeforePx, autoGapSteps);
|
int gapSteps = resolveGapSteps(
|
||||||
|
nextRow.source.gapBeforePx,
|
||||||
|
autoGapSteps,
|
||||||
|
baseHeightSteps);
|
||||||
topSteps = nextBottomSteps - gapSteps - Math.max(1, row.logicalHeightSteps);
|
topSteps = nextBottomSteps - gapSteps - Math.max(1, row.logicalHeightSteps);
|
||||||
bottomPx = nextBottomPx - stepsToPx(gapSteps, statusbarHeight);
|
bottomPx = nextBottomPx - stepsToPx(gapSteps, statusbarHeight);
|
||||||
}
|
}
|
||||||
@@ -657,11 +663,12 @@ final class ClockLayout {
|
|||||||
return max > 0f ? max : 1f;
|
return max > 0f ? max : 1f;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int resolveGapSteps(int gapBeforeSteps, int autoGapSteps) {
|
private static int resolveGapSteps(int gapBeforeSteps, int autoGapSteps, int baseHeightSteps) {
|
||||||
if (gapBeforeSteps == ClockLayoutTextFactory.ROW_GAP_AUTO) {
|
if (gapBeforeSteps == ClockLayoutTextFactory.ROW_GAP_AUTO) {
|
||||||
return Math.max(1, autoGapSteps);
|
return Math.max(1, autoGapSteps);
|
||||||
}
|
}
|
||||||
return gapBeforeSteps;
|
int defaultSteps = Math.max(1, SbtDefaults.CLOCK_TEXT_SIZE_STEPS_DEFAULT);
|
||||||
|
return Math.round(gapBeforeSteps * Math.max(1, baseHeightSteps) / (float) defaultSteps);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int stepsToPx(int steps, int statusbarHeight) {
|
private static int stepsToPx(int steps, int statusbarHeight) {
|
||||||
|
|||||||
@@ -5,166 +5,6 @@
|
|||||||
android:viewportWidth="108"
|
android:viewportWidth="108"
|
||||||
android:viewportHeight="108">
|
android:viewportHeight="108">
|
||||||
<path
|
<path
|
||||||
android:fillColor="#3DDC84"
|
android:fillColor="#0D0B08"
|
||||||
android:pathData="M0,0h108v108h-108z" />
|
android:pathData="M0,0 L108,0 L108,108 L0,108 Z" />
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M9,0L9,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,0L19,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M29,0L29,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M39,0L39,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M49,0L49,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M59,0L59,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M69,0L69,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M79,0L79,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M89,0L89,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M99,0L99,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,9L108,9"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,19L108,19"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,29L108,29"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,39L108,39"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,49L108,49"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,59L108,59"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,69L108,69"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,79L108,79"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,89L108,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,99L108,99"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,29L89,29"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,39L89,39"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,49L89,49"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,59L89,59"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,69L89,69"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,79L89,79"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M29,19L29,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M39,19L39,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M49,19L49,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M59,19L59,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M69,19L69,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M79,19L79,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@@ -1,30 +1,109 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:aapt="http://schemas.android.com/aapt"
|
|
||||||
android:width="108dp"
|
android:width="108dp"
|
||||||
android:height="108dp"
|
android:height="108dp"
|
||||||
android:viewportWidth="108"
|
android:viewportWidth="108"
|
||||||
android:viewportHeight="108">
|
android:viewportHeight="108">
|
||||||
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
<!-- Statusbar panel -->
|
||||||
<aapt:attr name="android:fillColor">
|
|
||||||
<gradient
|
|
||||||
android:endX="85.84757"
|
|
||||||
android:endY="92.4963"
|
|
||||||
android:startX="42.9492"
|
|
||||||
android:startY="49.59793"
|
|
||||||
android:type="linear">
|
|
||||||
<item
|
|
||||||
android:color="#44000000"
|
|
||||||
android:offset="0.0" />
|
|
||||||
<item
|
|
||||||
android:color="#00000000"
|
|
||||||
android:offset="1.0" />
|
|
||||||
</gradient>
|
|
||||||
</aapt:attr>
|
|
||||||
</path>
|
|
||||||
<path
|
<path
|
||||||
android:fillColor="#FFFFFF"
|
android:fillColor="#77746E"
|
||||||
android:fillType="nonZero"
|
android:pathData="M0,0 L108,0 L108,54 L0,54 Z" />
|
||||||
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
|
||||||
|
<!-- Camera cutout -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#050403"
|
||||||
|
android:pathData="M51.8,0 L56.2,0 L56.2,36.8 C56.2,39 51.8,39 51.8,36.8 Z" />
|
||||||
|
|
||||||
|
<!-- Envelope -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M39,22.5 L50,22.5 L50,30.5 L39,30.5 Z M39,22.5 L44.5,27.62 L50,22.5"
|
||||||
|
android:strokeColor="#FFFFFFFF"
|
||||||
|
android:strokeWidth="1.3"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round" />
|
||||||
|
|
||||||
|
<!-- Battery outline -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M59,23 L70,23 A1,1 0,0 1 71,24 L71,29.7 A1,1 0,0 1 70,30.7 L59,30.7 A1,1 0,0 1 58,29.7 L58,24 A1,1 0,0 1 59,23 Z M71,25.35 L73,25.35 L73,28.35 L71,28.35 Z"
|
||||||
|
android:strokeColor="#FFFFFFFF"
|
||||||
android:strokeWidth="1"
|
android:strokeWidth="1"
|
||||||
android:strokeColor="#00000000" />
|
android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round" />
|
||||||
|
|
||||||
|
<!-- Battery fill -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFFFF"
|
||||||
|
android:pathData="M59.4,24.4 L66.54,24.4 L66.54,29.3 L59.4,29.3 Z" />
|
||||||
|
|
||||||
|
<!-- Mon -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M31,39.6 L31,31.6 L33.48,36.24 L35.96,31.6 L35.96,39.6 M37.96,36.32 A1.9024,3.28 0,1 0 41.7648,36.32 A1.9024,3.28 0,1 0 37.96,36.32 M43.7648,39.6 L43.7648,33.04 C45.8574,33.04 47.5696,34.352 47.5696,39.6"
|
||||||
|
android:strokeColor="#FFFFFFFF"
|
||||||
|
android:strokeWidth="1.1"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round" />
|
||||||
|
|
||||||
|
<!-- Wi-Fi -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M61,36.5 C63.8,32.75 68.2,32.75 71,36.5 M63.2,39 C65,37 67,37 68.8,39"
|
||||||
|
android:strokeColor="#FFFFFFFF"
|
||||||
|
android:strokeWidth="1.3"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round" />
|
||||||
|
|
||||||
|
<!-- Wi-Fi dot -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFFFF"
|
||||||
|
android:pathData="M65.1,41.5 A0.9,0.9 0,1 0 66.9,41.5 A0.9,0.9 0,1 0 65.1,41.5" />
|
||||||
|
|
||||||
|
<!-- Cellular bars -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFFFF"
|
||||||
|
android:pathData="M73,39.7 L74.8,39.7 L74.8,42.5 L73,42.5 Z M76,36.9 L77.8,36.9 L77.8,42.5 L76,42.5 Z M79,34.1 L80.8,34.1 L80.8,42.5 L79,42.5 Z" />
|
||||||
|
|
||||||
|
<!-- Clock 12:34 -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M30,42.5 L30,47.5 M30,47.5 L30,52.5 M33,42.5 L38,42.5 M38,42.5 L38,47.5 M33,47.5 L38,47.5 M33,47.5 L33,52.5 M33,52.5 L38,52.5 M45,42.5 L50,42.5 M50,42.5 L50,47.5 M45,47.5 L50,47.5 M50,47.5 L50,52.5 M45,52.5 L50,52.5 M53,42.5 L53,47.5 M53,47.5 L58,47.5 M58,42.5 L58,47.5 M58,47.5 L58,52.5 M65,45.1 L68.6,45.1 M65,45.1 L65,48.7 M65,48.7 L68.6,48.7 M68.6,48.7 L68.6,52.3 M65,52.3 L68.6,52.3 M71,45.1 L74.6,45.1 M71,45.1 L71,48.7 M71,48.7 L74.6,48.7 M71,48.7 L71,52.3 M74.6,48.7 L74.6,52.3 M71,52.3 L74.6,52.3"
|
||||||
|
android:strokeColor="#FFFFFFFF"
|
||||||
|
android:strokeWidth="1.55"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round" />
|
||||||
|
|
||||||
|
<!-- Clock colons -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFFFF"
|
||||||
|
android:pathData="M40.4,45.5 A1.1,1.1 0,1 0 42.6,45.5 A1.1,1.1 0,1 0 40.4,45.5 M40.4,49.5 A1.1,1.1 0,1 0 42.6,49.5 A1.1,1.1 0,1 0 40.4,49.5 M60.708,47.26 A0.792,0.792 0,1 0 62.292,47.26 A0.792,0.792 0,1 0 60.708,47.26 M60.708,50.14 A0.792,0.792 0,1 0 62.292,50.14 A0.792,0.792 0,1 0 60.708,50.14" />
|
||||||
|
|
||||||
|
<!-- Battery bar track -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#2A2116"
|
||||||
|
android:pathData="M3,54 L105,54 A3,3 0,0 1 108,57 L108,57 A3,3 0,0 1 105,60 L3,60 A3,3 0,0 1 0,57 L0,57 A3,3 0,0 1 3,54 Z" />
|
||||||
|
|
||||||
|
<!-- Battery bar fill -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#2FDB72"
|
||||||
|
android:pathData="M3,55 L72.6,55 A2,2 0,0 1 74.6,57 L74.6,57 A2,2 0,0 1 72.6,59 L3,59 A2,2 0,0 1 1,57 L1,57 A2,2 0,0 1 3,55 Z" />
|
||||||
|
|
||||||
|
<!-- SBT wordmark outer -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M46,62.8 L35,62.8 L35,72.8 L46,72.8 L46,82.8 L35,82.8 M49.75,62.8 L49.75,82.8 M49.75,62.8 L58.39,62.8 L61.75,66.16 L61.75,69.44 L58.39,72.8 L49.75,72.8 M49.75,72.8 L58.39,72.8 L61.75,76.16 L61.75,79.44 L58.39,82.8 L49.75,82.8 M65.5,62.8 L78.5,62.8 M72,62.8 L72,82.8"
|
||||||
|
android:strokeColor="#FF9F1A"
|
||||||
|
android:strokeWidth="5"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round" />
|
||||||
|
|
||||||
|
<!-- SBT wordmark inner -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M46,62.8 L35,62.8 L35,72.8 L46,72.8 L46,82.8 L35,82.8 M49.75,62.8 L49.75,82.8 M49.75,62.8 L58.39,62.8 L61.75,66.16 L61.75,69.44 L58.39,72.8 L49.75,72.8 M49.75,72.8 L58.39,72.8 L61.75,76.16 L61.75,79.44 L58.39,82.8 L49.75,82.8 M65.5,62.8 L78.5,62.8 M72,62.8 L72,82.8"
|
||||||
|
android:strokeColor="#FFF5DC"
|
||||||
|
android:strokeWidth="1.8"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round" />
|
||||||
</vector>
|
</vector>
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108">
|
||||||
|
<!-- Statusbar panel -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFFFF"
|
||||||
|
android:pathData="M0,0 L108,0 L108,54 L0,54 Z" />
|
||||||
|
|
||||||
|
<!-- Camera cutout -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFFFF"
|
||||||
|
android:pathData="M51.8,0 L56.2,0 L56.2,36.8 C56.2,39 51.8,39 51.8,36.8 Z" />
|
||||||
|
|
||||||
|
<!-- Envelope -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M39,22.5 L50,22.5 L50,30.5 L39,30.5 Z M39,22.5 L44.5,27.62 L50,22.5"
|
||||||
|
android:strokeColor="#FFFFFFFF"
|
||||||
|
android:strokeWidth="1.3"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round" />
|
||||||
|
|
||||||
|
<!-- Battery outline -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M59,23 L70,23 A1,1 0,0 1 71,24 L71,29.7 A1,1 0,0 1 70,30.7 L59,30.7 A1,1 0,0 1 58,29.7 L58,24 A1,1 0,0 1 59,23 Z M71,25.35 L73,25.35 L73,28.35 L71,28.35 Z"
|
||||||
|
android:strokeColor="#FFFFFFFF"
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round" />
|
||||||
|
|
||||||
|
<!-- Battery fill -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFFFF"
|
||||||
|
android:pathData="M59.4,24.4 L66.54,24.4 L66.54,29.3 L59.4,29.3 Z" />
|
||||||
|
|
||||||
|
<!-- Mon -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M31,39.6 L31,31.6 L33.48,36.24 L35.96,31.6 L35.96,39.6 M37.96,36.32 A1.9024,3.28 0,1 0 41.7648,36.32 A1.9024,3.28 0,1 0 37.96,36.32 M43.7648,39.6 L43.7648,33.04 C45.8574,33.04 47.5696,34.352 47.5696,39.6"
|
||||||
|
android:strokeColor="#FFFFFFFF"
|
||||||
|
android:strokeWidth="1.1"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round" />
|
||||||
|
|
||||||
|
<!-- Wi-Fi -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M61,36.5 C63.8,32.75 68.2,32.75 71,36.5 M63.2,39 C65,37 67,37 68.8,39"
|
||||||
|
android:strokeColor="#FFFFFFFF"
|
||||||
|
android:strokeWidth="1.3"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round" />
|
||||||
|
|
||||||
|
<!-- Wi-Fi dot -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFFFF"
|
||||||
|
android:pathData="M65.1,41.5 A0.9,0.9 0,1 0 66.9,41.5 A0.9,0.9 0,1 0 65.1,41.5" />
|
||||||
|
|
||||||
|
<!-- Cellular bars -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFFFF"
|
||||||
|
android:pathData="M73,39.7 L74.8,39.7 L74.8,42.5 L73,42.5 Z M76,36.9 L77.8,36.9 L77.8,42.5 L76,42.5 Z M79,34.1 L80.8,34.1 L80.8,42.5 L79,42.5 Z" />
|
||||||
|
|
||||||
|
<!-- Clock 12:34 -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M30,42.5 L30,47.5 M30,47.5 L30,52.5 M33,42.5 L38,42.5 M38,42.5 L38,47.5 M33,47.5 L38,47.5 M33,47.5 L33,52.5 M33,52.5 L38,52.5 M45,42.5 L50,42.5 M50,42.5 L50,47.5 M45,47.5 L50,47.5 M50,47.5 L50,52.5 M45,52.5 L50,52.5 M53,42.5 L53,47.5 M53,47.5 L58,47.5 M58,42.5 L58,47.5 M58,47.5 L58,52.5 M65,45.1 L68.6,45.1 M65,45.1 L65,48.7 M65,48.7 L68.6,48.7 M68.6,48.7 L68.6,52.3 M65,52.3 L68.6,52.3 M71,45.1 L74.6,45.1 M71,45.1 L71,48.7 M71,48.7 L74.6,48.7 M71,48.7 L71,52.3 M74.6,48.7 L74.6,52.3 M71,52.3 L74.6,52.3"
|
||||||
|
android:strokeColor="#FFFFFFFF"
|
||||||
|
android:strokeWidth="1.55"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round" />
|
||||||
|
|
||||||
|
<!-- Clock colons -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFFFF"
|
||||||
|
android:pathData="M40.4,45.5 A1.1,1.1 0,1 0 42.6,45.5 A1.1,1.1 0,1 0 40.4,45.5 M40.4,49.5 A1.1,1.1 0,1 0 42.6,49.5 A1.1,1.1 0,1 0 40.4,49.5 M60.708,47.26 A0.792,0.792 0,1 0 62.292,47.26 A0.792,0.792 0,1 0 60.708,47.26 M60.708,50.14 A0.792,0.792 0,1 0 62.292,50.14 A0.792,0.792 0,1 0 60.708,50.14" />
|
||||||
|
|
||||||
|
<!-- Battery bar track -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFFFF"
|
||||||
|
android:pathData="M3,54 L105,54 A3,3 0,0 1 108,57 L108,57 A3,3 0,0 1 105,60 L3,60 A3,3 0,0 1 0,57 L0,57 A3,3 0,0 1 3,54 Z" />
|
||||||
|
|
||||||
|
<!-- Battery bar fill -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFFFF"
|
||||||
|
android:pathData="M3,55 L72.6,55 A2,2 0,0 1 74.6,57 L74.6,57 A2,2 0,0 1 72.6,59 L3,59 A2,2 0,0 1 1,57 L1,57 A2,2 0,0 1 3,55 Z" />
|
||||||
|
|
||||||
|
<!-- SBT wordmark -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M46,62.8 L35,62.8 L35,72.8 L46,72.8 L46,82.8 L35,82.8 M49.75,62.8 L49.75,82.8 M49.75,62.8 L58.39,62.8 L61.75,66.16 L61.75,69.44 L58.39,72.8 L49.75,72.8 M49.75,72.8 L58.39,72.8 L61.75,76.16 L61.75,79.44 L58.39,82.8 L49.75,82.8 M65.5,62.8 L78.5,62.8 M72,62.8 L72,82.8"
|
||||||
|
android:strokeColor="#FFFFFFFF"
|
||||||
|
android:strokeWidth="5"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round" />
|
||||||
|
</vector>
|
||||||
@@ -2,5 +2,5 @@
|
|||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@drawable/ic_launcher_background" />
|
<background android:drawable="@drawable/ic_launcher_background" />
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
<monochrome android:drawable="@drawable/ic_launcher_monochrome" />
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.4 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 982 B |
Binary file not shown.
|
Before Width: | Height: | Size: 1.9 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.8 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 3.8 KiB |
Executable
+505
@@ -0,0 +1,505 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Generate the launcher icon VectorDrawable files and a local SVG preview.
|
||||||
|
|
||||||
|
This keeps the icon tweakable without hand-editing dense Android vector XML.
|
||||||
|
Edit the constants below, run this script, then open local/launcher-icon-preview.svg.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from pathlib import Path
|
||||||
|
from xml.sax.saxutils import escape
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
|
||||||
|
FOREGROUND = ROOT / "app/src/main/res/drawable/ic_launcher_foreground.xml"
|
||||||
|
MONOCHROME = ROOT / "app/src/main/res/drawable/ic_launcher_monochrome.xml"
|
||||||
|
BACKGROUND = ROOT / "app/src/main/res/drawable/ic_launcher_background.xml"
|
||||||
|
PREVIEW = ROOT / "local/launcher-icon-preview.svg"
|
||||||
|
|
||||||
|
VIEWPORT = 108
|
||||||
|
|
||||||
|
# Main geometry knobs. Coordinates are in Android/SVG viewport units.
|
||||||
|
# Open local/launcher-icon-preview.svg after regenerating to compare the
|
||||||
|
# square, rounded, and circular launcher masks.
|
||||||
|
# Statusbar panel. It fills the full top width.
|
||||||
|
STATUS_BOTTOM = 54
|
||||||
|
|
||||||
|
CUTOUT_CX = 54
|
||||||
|
CUTOUT_TOP = 0
|
||||||
|
CUTOUT_BOTTOM = 39
|
||||||
|
CUTOUT_STEM_HALF_WIDTH = 2.2
|
||||||
|
|
||||||
|
# Android adaptive icons use a 108x108 viewport, but only the central 72x72
|
||||||
|
# area is guaranteed to survive launcher masks. The preview uses this safe-zone
|
||||||
|
# circle, not the full viewport circle.
|
||||||
|
ADAPTIVE_SAFE_RADIUS = 36
|
||||||
|
|
||||||
|
# Row 1.
|
||||||
|
ENVELOPE_X = 39
|
||||||
|
ENVELOPE_Y = 22.5
|
||||||
|
ENVELOPE_W = 11
|
||||||
|
ENVELOPE_H = 8
|
||||||
|
ENVELOPE_STROKE = 1.3
|
||||||
|
|
||||||
|
BATTERY_ICON_X = 58
|
||||||
|
BATTERY_ICON_Y = 23
|
||||||
|
BATTERY_ICON_W = 13
|
||||||
|
BATTERY_ICON_H = 7.7
|
||||||
|
BATTERY_ICON_RADIUS = 1
|
||||||
|
BATTERY_ICON_PROTRUSION_W = 2
|
||||||
|
BATTERY_ICON_PROTRUSION_H = 3
|
||||||
|
BATTERY_ICON_LEVEL = 0.70
|
||||||
|
BATTERY_ICON_STROKE = 1.0
|
||||||
|
|
||||||
|
# Row 2.
|
||||||
|
DAY_TEXT_X = 31
|
||||||
|
DAY_TEXT_Y = 31.6
|
||||||
|
DAY_TEXT_H = 8
|
||||||
|
DAY_TEXT_STROKE = 1.1
|
||||||
|
DAY_TEXT_LETTER_SPACING = 2.0
|
||||||
|
DAY_TEXT_LOWERCASE_SCALE = 0.82
|
||||||
|
|
||||||
|
WIFI_X = 61
|
||||||
|
WIFI_Y = 36.5
|
||||||
|
WIFI_W = 10
|
||||||
|
WIFI_H = 5
|
||||||
|
WIFI_STROKE = 1.3
|
||||||
|
WIFI_DOT_R = 0.9
|
||||||
|
|
||||||
|
SIGNAL_X = 73
|
||||||
|
SIGNAL_BOTTOM = 42.5
|
||||||
|
SIGNAL_BAR_W = 1.8
|
||||||
|
SIGNAL_BAR_SPACING = 1.2
|
||||||
|
SIGNAL_BAR_HEIGHTS = (2.8, 5.6, 8.4)
|
||||||
|
|
||||||
|
# The divider between the statusbar illustration and the SBT wordmark.
|
||||||
|
BATTERY_BAR_LEFT = 0
|
||||||
|
BATTERY_BAR_RIGHT = 108
|
||||||
|
BATTERY_BAR_Y = STATUS_BOTTOM + 0.0
|
||||||
|
BATTERY_BAR_H = 6
|
||||||
|
BATTERY_BAR_LEVEL = 0.70
|
||||||
|
|
||||||
|
WORDMARK_X = 35
|
||||||
|
WORDMARK_Y = 62.8
|
||||||
|
WORDMARK_W = 50
|
||||||
|
WORDMARK_H = 20
|
||||||
|
WORDMARK_STROKE_OUTER = 5.0
|
||||||
|
WORDMARK_STROKE_INNER = 1.8
|
||||||
|
|
||||||
|
COLOR_STATUS = "#77746E"
|
||||||
|
COLOR_CUTOUT = "#050403"
|
||||||
|
COLOR_WHITE = "#FFFFFFFF"
|
||||||
|
COLOR_WARM_WHITE = "#FFF5DC"
|
||||||
|
COLOR_ORANGE = "#FF9F1A"
|
||||||
|
COLOR_GREEN = "#2FDB72"
|
||||||
|
COLOR_BAR_TRACK = "#2A2116"
|
||||||
|
COLOR_BG = "#0D0B08"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class PathStyle:
|
||||||
|
fill: str = "#00000000"
|
||||||
|
stroke: str | None = None
|
||||||
|
stroke_width: float | None = None
|
||||||
|
line_cap: str | None = None
|
||||||
|
line_join: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class IconPath:
|
||||||
|
data: str
|
||||||
|
style: PathStyle
|
||||||
|
comment: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
def round_rect(x: float, y: float, w: float, h: float, r: float) -> str:
|
||||||
|
if r <= 0:
|
||||||
|
return rect(x, y, w, h)
|
||||||
|
r = min(r, w / 2, h / 2)
|
||||||
|
return (
|
||||||
|
f"M{x + r:g},{y:g} L{x + w - r:g},{y:g} "
|
||||||
|
f"A{r:g},{r:g} 0,0 1 {x + w:g},{y + r:g} "
|
||||||
|
f"L{x + w:g},{y + h - r:g} "
|
||||||
|
f"A{r:g},{r:g} 0,0 1 {x + w - r:g},{y + h:g} "
|
||||||
|
f"L{x + r:g},{y + h:g} "
|
||||||
|
f"A{r:g},{r:g} 0,0 1 {x:g},{y + h - r:g} "
|
||||||
|
f"L{x:g},{y + r:g} "
|
||||||
|
f"A{r:g},{r:g} 0,0 1 {x + r:g},{y:g} Z"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def rect(x: float, y: float, w: float, h: float) -> str:
|
||||||
|
return f"M{x:g},{y:g} L{x + w:g},{y:g} L{x + w:g},{y + h:g} L{x:g},{y + h:g} Z"
|
||||||
|
|
||||||
|
|
||||||
|
def circle(cx: float, cy: float, r: float) -> str:
|
||||||
|
return (
|
||||||
|
f"M{cx - r:g},{cy:g} "
|
||||||
|
f"A{r:g},{r:g} 0,1 0 {cx + r:g},{cy:g} "
|
||||||
|
f"A{r:g},{r:g} 0,1 0 {cx - r:g},{cy:g}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def path(data: str, fill: str = "#00000000", *, stroke: str | None = None,
|
||||||
|
stroke_width: float | None = None, line_cap: str | None = None,
|
||||||
|
line_join: str | None = None, comment: str | None = None) -> IconPath:
|
||||||
|
return IconPath(data, PathStyle(fill, stroke, stroke_width, line_cap, line_join), comment)
|
||||||
|
|
||||||
|
|
||||||
|
def stroke_path(data: str, color: str, width: float, *, comment: str | None = None) -> IconPath:
|
||||||
|
return path(
|
||||||
|
data,
|
||||||
|
stroke=color,
|
||||||
|
stroke_width=width,
|
||||||
|
line_cap="round",
|
||||||
|
line_join="round",
|
||||||
|
comment=comment,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def seven_segment_digit(x: float, y: float, digit: str, scale: float) -> str:
|
||||||
|
segments = {
|
||||||
|
"0": "abcfed",
|
||||||
|
"1": "bc",
|
||||||
|
"2": "abged",
|
||||||
|
"3": "abgcd",
|
||||||
|
"4": "fgbc",
|
||||||
|
"5": "afgcd",
|
||||||
|
"6": "afgecd",
|
||||||
|
"7": "abc",
|
||||||
|
"8": "abcdefg",
|
||||||
|
"9": "abfgcd",
|
||||||
|
}[digit]
|
||||||
|
w = 5 * scale
|
||||||
|
h = 10 * scale
|
||||||
|
mid = y + h / 2
|
||||||
|
left = x
|
||||||
|
right = x + w
|
||||||
|
top = y
|
||||||
|
bottom = y + h
|
||||||
|
coords = {
|
||||||
|
"a": f"M{left:g},{top:g} L{right:g},{top:g}",
|
||||||
|
"b": f"M{right:g},{top:g} L{right:g},{mid:g}",
|
||||||
|
"c": f"M{right:g},{mid:g} L{right:g},{bottom:g}",
|
||||||
|
"d": f"M{left:g},{bottom:g} L{right:g},{bottom:g}",
|
||||||
|
"e": f"M{left:g},{mid:g} L{left:g},{bottom:g}",
|
||||||
|
"f": f"M{left:g},{top:g} L{left:g},{mid:g}",
|
||||||
|
"g": f"M{left:g},{mid:g} L{right:g},{mid:g}",
|
||||||
|
}
|
||||||
|
return " ".join(coords[s] for s in segments)
|
||||||
|
|
||||||
|
|
||||||
|
def colon(x: float, y: float, scale: float) -> str:
|
||||||
|
r = 1.1 * scale
|
||||||
|
return circle(x, y + 3 * scale, r) + " " + circle(x, y + 7 * scale, r)
|
||||||
|
|
||||||
|
|
||||||
|
def cutout_path() -> str:
|
||||||
|
cx = CUTOUT_CX
|
||||||
|
top = CUTOUT_TOP
|
||||||
|
bottom = CUTOUT_BOTTOM
|
||||||
|
stem_half = CUTOUT_STEM_HALF_WIDTH
|
||||||
|
return (
|
||||||
|
f"M{cx - stem_half:g},{top:g} "
|
||||||
|
f"L{cx + stem_half:g},{top:g} "
|
||||||
|
f"L{cx + stem_half:g},{bottom - stem_half:g} "
|
||||||
|
f"C{cx + stem_half:g},{bottom:g} {cx - stem_half:g},{bottom:g} {cx - stem_half:g},{bottom - stem_half:g} "
|
||||||
|
f"Z"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def envelope_path() -> str:
|
||||||
|
x = ENVELOPE_X
|
||||||
|
y = ENVELOPE_Y
|
||||||
|
w = ENVELOPE_W
|
||||||
|
h = ENVELOPE_H
|
||||||
|
return (
|
||||||
|
f"M{x:g},{y:g} L{x + w:g},{y:g} L{x + w:g},{y + h:g} L{x:g},{y + h:g} Z "
|
||||||
|
f"M{x:g},{y:g} L{x + w / 2:g},{y + h * 0.64:g} L{x + w:g},{y:g}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def battery_icon_paths(fill: str, stroke: str) -> list[IconPath]:
|
||||||
|
x = BATTERY_ICON_X
|
||||||
|
y = BATTERY_ICON_Y
|
||||||
|
w = BATTERY_ICON_W
|
||||||
|
h = BATTERY_ICON_H
|
||||||
|
r = BATTERY_ICON_RADIUS
|
||||||
|
protrusion_w = BATTERY_ICON_PROTRUSION_W
|
||||||
|
protrusion_h = BATTERY_ICON_PROTRUSION_H
|
||||||
|
gauge_pad = max(1.0, BATTERY_ICON_STROKE + 0.4)
|
||||||
|
gauge_w = max(0.0, (w - gauge_pad * 2) * BATTERY_ICON_LEVEL)
|
||||||
|
protrusion_y = y + (h - protrusion_h) / 2
|
||||||
|
return [
|
||||||
|
stroke_path(
|
||||||
|
round_rect(x, y, w, h, r)
|
||||||
|
+ " "
|
||||||
|
+ rect(x + w, protrusion_y, protrusion_w, protrusion_h),
|
||||||
|
stroke,
|
||||||
|
BATTERY_ICON_STROKE,
|
||||||
|
comment="Battery outline",
|
||||||
|
),
|
||||||
|
path(rect(x + gauge_pad, y + gauge_pad, gauge_w, max(0.0, h - gauge_pad * 2)), fill,
|
||||||
|
comment="Battery fill"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def day_text_path() -> str:
|
||||||
|
x = DAY_TEXT_X
|
||||||
|
y = DAY_TEXT_Y
|
||||||
|
h = DAY_TEXT_H
|
||||||
|
lower_h = h * DAY_TEXT_LOWERCASE_SCALE
|
||||||
|
lower_top = y + h - lower_h
|
||||||
|
spacing = DAY_TEXT_LETTER_SPACING
|
||||||
|
|
||||||
|
m_w = h * 0.62
|
||||||
|
o_w = lower_h * 0.58
|
||||||
|
n_w = lower_h * 0.58
|
||||||
|
o_x = x + m_w + spacing
|
||||||
|
n_x = o_x + o_w + spacing
|
||||||
|
return (
|
||||||
|
# M
|
||||||
|
f"M{x:g},{y + h:g} L{x:g},{y:g} L{x + m_w / 2:g},{y + h * 0.58:g} "
|
||||||
|
f"L{x + m_w:g},{y:g} L{x + m_w:g},{y + h:g} "
|
||||||
|
# o
|
||||||
|
f"M{o_x:g},{lower_top + lower_h / 2:g} "
|
||||||
|
f"A{o_w / 2:g},{lower_h / 2:g} 0,1 0 {o_x + o_w:g},{lower_top + lower_h / 2:g} "
|
||||||
|
f"A{o_w / 2:g},{lower_h / 2:g} 0,1 0 {o_x:g},{lower_top + lower_h / 2:g} "
|
||||||
|
# n
|
||||||
|
f"M{n_x:g},{y + h:g} L{n_x:g},{lower_top:g} "
|
||||||
|
f"C{n_x + n_w * 0.55:g},{lower_top:g} {n_x + n_w:g},{lower_top + lower_h * 0.2:g} "
|
||||||
|
f"{n_x + n_w:g},{y + h:g}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def wifi_paths(color: str) -> list[IconPath]:
|
||||||
|
x = WIFI_X
|
||||||
|
y = WIFI_Y
|
||||||
|
w = WIFI_W
|
||||||
|
h = WIFI_H
|
||||||
|
return [
|
||||||
|
stroke_path(
|
||||||
|
f"M{x:g},{y:g} C{x + w * 0.28:g},{y - h * 0.75:g} "
|
||||||
|
f"{x + w * 0.72:g},{y - h * 0.75:g} {x + w:g},{y:g} "
|
||||||
|
f"M{x + w * 0.22:g},{y + h * 0.5:g} C{x + w * 0.4:g},{y + h * 0.1:g} "
|
||||||
|
f"{x + w * 0.6:g},{y + h * 0.1:g} {x + w * 0.78:g},{y + h * 0.5:g}",
|
||||||
|
color,
|
||||||
|
WIFI_STROKE,
|
||||||
|
comment="Wi-Fi",
|
||||||
|
),
|
||||||
|
path(circle(x + w / 2, y + h, WIFI_DOT_R), color, comment="Wi-Fi dot"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def signal_path() -> str:
|
||||||
|
pieces = []
|
||||||
|
for index, height in enumerate(SIGNAL_BAR_HEIGHTS):
|
||||||
|
x = SIGNAL_X + index * (SIGNAL_BAR_W + SIGNAL_BAR_SPACING)
|
||||||
|
pieces.append(rect(x, SIGNAL_BOTTOM - height, SIGNAL_BAR_W, height))
|
||||||
|
return " ".join(pieces)
|
||||||
|
|
||||||
|
|
||||||
|
def clock_paths() -> list[IconPath]:
|
||||||
|
y = 42.5
|
||||||
|
x = 25
|
||||||
|
s = 1.0
|
||||||
|
small = 0.72
|
||||||
|
pieces = [
|
||||||
|
seven_segment_digit(x, y, "1", s),
|
||||||
|
seven_segment_digit(x + 8, y, "2", s),
|
||||||
|
seven_segment_digit(x + 20, y, "3", s),
|
||||||
|
seven_segment_digit(x + 28, y, "4", s),
|
||||||
|
]
|
||||||
|
small_x = x + 40
|
||||||
|
pieces.extend([
|
||||||
|
seven_segment_digit(small_x, y + 2.6, "5", small),
|
||||||
|
seven_segment_digit(small_x + 6, y + 2.6, "6", small),
|
||||||
|
])
|
||||||
|
return [
|
||||||
|
stroke_path(" ".join(pieces), COLOR_WHITE, 1.55, comment="Clock 12:34"),
|
||||||
|
path(colon(x + 16.5, y, s) + " " + colon(small_x - 3.5, y + 2.6, small), COLOR_WHITE,
|
||||||
|
comment="Clock colons"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def statusbar_paths(monochrome: bool) -> list[IconPath]:
|
||||||
|
white = COLOR_WHITE
|
||||||
|
status_fill = COLOR_WHITE if monochrome else COLOR_STATUS
|
||||||
|
cutout_fill = COLOR_WHITE if monochrome else COLOR_CUTOUT
|
||||||
|
paths: list[IconPath] = []
|
||||||
|
paths.append(path(rect(0, 0, VIEWPORT, STATUS_BOTTOM), status_fill, comment="Statusbar panel"))
|
||||||
|
paths.append(path(cutout_path(), cutout_fill, comment="Camera cutout"))
|
||||||
|
|
||||||
|
# Row 1.
|
||||||
|
paths.append(stroke_path(envelope_path(), white, ENVELOPE_STROKE, comment="Envelope"))
|
||||||
|
paths.extend(battery_icon_paths(white, white))
|
||||||
|
|
||||||
|
# Row 2.
|
||||||
|
paths.append(stroke_path(day_text_path(), white, DAY_TEXT_STROKE, comment="Mon"))
|
||||||
|
paths.extend(wifi_paths(white))
|
||||||
|
paths.append(path(signal_path(), white, comment="Cellular bars"))
|
||||||
|
|
||||||
|
# Row 3.
|
||||||
|
paths.extend(clock_paths())
|
||||||
|
return paths
|
||||||
|
|
||||||
|
|
||||||
|
def battery_bar_paths(monochrome: bool) -> list[IconPath]:
|
||||||
|
green = COLOR_WHITE if monochrome else COLOR_GREEN
|
||||||
|
track = COLOR_WHITE if monochrome else COLOR_BAR_TRACK
|
||||||
|
track_w = max(0.0, BATTERY_BAR_RIGHT - BATTERY_BAR_LEFT)
|
||||||
|
fill_w = track_w * BATTERY_BAR_LEVEL
|
||||||
|
return [
|
||||||
|
path(round_rect(BATTERY_BAR_LEFT, BATTERY_BAR_Y, track_w, BATTERY_BAR_H, 3), track,
|
||||||
|
comment="Battery bar track"),
|
||||||
|
path(round_rect(BATTERY_BAR_LEFT + 1, BATTERY_BAR_Y + 1, max(0.0, fill_w - 2), BATTERY_BAR_H - 2, 2), green,
|
||||||
|
comment="Battery bar fill"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def wordmark_paths(monochrome: bool) -> list[IconPath]:
|
||||||
|
x = WORDMARK_X
|
||||||
|
y = WORDMARK_Y
|
||||||
|
w = WORDMARK_W
|
||||||
|
h = WORDMARK_H
|
||||||
|
gap = w * 0.075
|
||||||
|
s_w = w * 0.22
|
||||||
|
b_w = w * 0.24
|
||||||
|
t_w = w * 0.26
|
||||||
|
s_x = x
|
||||||
|
b_x = s_x + s_w + gap
|
||||||
|
t_x = b_x + b_w + gap
|
||||||
|
mid = y + h * 0.50
|
||||||
|
cut = min(b_w * 0.28, h * 0.18)
|
||||||
|
data = (
|
||||||
|
f"M{s_x + s_w:g},{y:g} L{s_x:g},{y:g} L{s_x:g},{mid:g} L{s_x + s_w:g},{mid:g} "
|
||||||
|
f"L{s_x + s_w:g},{y + h:g} L{s_x:g},{y + h:g} "
|
||||||
|
f"M{b_x:g},{y:g} L{b_x:g},{y + h:g} "
|
||||||
|
f"M{b_x:g},{y:g} L{b_x + b_w - cut:g},{y:g} L{b_x + b_w:g},{y + cut:g} "
|
||||||
|
f"L{b_x + b_w:g},{mid - cut:g} L{b_x + b_w - cut:g},{mid:g} L{b_x:g},{mid:g} "
|
||||||
|
f"M{b_x:g},{mid:g} L{b_x + b_w - cut:g},{mid:g} L{b_x + b_w:g},{mid + cut:g} "
|
||||||
|
f"L{b_x + b_w:g},{y + h - cut:g} L{b_x + b_w - cut:g},{y + h:g} L{b_x:g},{y + h:g} "
|
||||||
|
f"M{t_x:g},{y:g} L{t_x + t_w:g},{y:g} M{t_x + t_w / 2:g},{y:g} L{t_x + t_w / 2:g},{y + h:g}"
|
||||||
|
)
|
||||||
|
if monochrome:
|
||||||
|
return [stroke_path(data, COLOR_WHITE, WORDMARK_STROKE_OUTER, comment="SBT wordmark")]
|
||||||
|
return [
|
||||||
|
stroke_path(data, COLOR_ORANGE, WORDMARK_STROKE_OUTER, comment="SBT wordmark outer"),
|
||||||
|
stroke_path(data, COLOR_WARM_WHITE, WORDMARK_STROKE_INNER, comment="SBT wordmark inner"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def foreground_paths(monochrome: bool = False) -> list[IconPath]:
|
||||||
|
return statusbar_paths(monochrome) + battery_bar_paths(monochrome) + wordmark_paths(monochrome)
|
||||||
|
|
||||||
|
|
||||||
|
def background_paths() -> list[IconPath]:
|
||||||
|
return [
|
||||||
|
path(rect(0, 0, VIEWPORT, VIEWPORT), COLOR_BG),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def android_path_xml(icon_path: IconPath) -> str:
|
||||||
|
attrs = [
|
||||||
|
f'android:fillColor="{icon_path.style.fill}"',
|
||||||
|
f'android:pathData="{escape(icon_path.data)}"',
|
||||||
|
]
|
||||||
|
if icon_path.style.stroke:
|
||||||
|
attrs.append(f'android:strokeColor="{icon_path.style.stroke}"')
|
||||||
|
if icon_path.style.stroke_width is not None:
|
||||||
|
attrs.append(f'android:strokeWidth="{icon_path.style.stroke_width:g}"')
|
||||||
|
if icon_path.style.line_cap:
|
||||||
|
attrs.append(f'android:strokeLineCap="{icon_path.style.line_cap}"')
|
||||||
|
if icon_path.style.line_join:
|
||||||
|
attrs.append(f'android:strokeLineJoin="{icon_path.style.line_join}"')
|
||||||
|
return " <path\n " + "\n ".join(attrs) + " />"
|
||||||
|
|
||||||
|
|
||||||
|
def write_vector(destination: Path, paths: list[IconPath]) -> None:
|
||||||
|
body = "\n".join(
|
||||||
|
(f"\n <!-- {escape(p.comment)} -->\n" if p.comment else "\n") + android_path_xml(p)
|
||||||
|
for p in paths
|
||||||
|
)
|
||||||
|
destination.write_text(
|
||||||
|
'<?xml version="1.0" encoding="utf-8"?>\n'
|
||||||
|
'<vector xmlns:android="http://schemas.android.com/apk/res/android"\n'
|
||||||
|
' android:width="108dp"\n'
|
||||||
|
' android:height="108dp"\n'
|
||||||
|
' android:viewportWidth="108"\n'
|
||||||
|
' android:viewportHeight="108">'
|
||||||
|
f'{body}\n'
|
||||||
|
'</vector>\n',
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def svg_path_xml(icon_path: IconPath) -> str:
|
||||||
|
style = [f'fill="{icon_path.style.fill}"']
|
||||||
|
if icon_path.style.stroke:
|
||||||
|
style.append(f'stroke="{icon_path.style.stroke}"')
|
||||||
|
if icon_path.style.stroke_width is not None:
|
||||||
|
style.append(f'stroke-width="{icon_path.style.stroke_width:g}"')
|
||||||
|
if icon_path.style.line_cap:
|
||||||
|
style.append(f'stroke-linecap="{icon_path.style.line_cap}"')
|
||||||
|
if icon_path.style.line_join:
|
||||||
|
style.append(f'stroke-linejoin="{icon_path.style.line_join}"')
|
||||||
|
return f'<path d="{escape(icon_path.data)}" {" ".join(style)} />'
|
||||||
|
|
||||||
|
|
||||||
|
def write_preview() -> None:
|
||||||
|
PREVIEW.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
paths = background_paths() + foreground_paths(False)
|
||||||
|
body = "\n ".join(svg_path_xml(p) for p in paths)
|
||||||
|
panel_bg = '<rect width="108" height="108" fill="#050403" />'
|
||||||
|
square = f'<g transform="translate(0,0)">\n {panel_bg}\n {body}\n</g>'
|
||||||
|
rounded = (
|
||||||
|
'<g clip-path="url(#rounded-mask)">\n'
|
||||||
|
' <g transform="translate(126,0)">\n'
|
||||||
|
f' {panel_bg}\n {body}\n'
|
||||||
|
' </g>\n'
|
||||||
|
'</g>\n'
|
||||||
|
'<rect x="126" y="0" width="108" height="108" rx="27" fill="none" '
|
||||||
|
'stroke="#66FFFFFF" stroke-width="0.8" />'
|
||||||
|
)
|
||||||
|
circle_preview = (
|
||||||
|
'<g clip-path="url(#circle-mask)">\n'
|
||||||
|
' <g transform="translate(252,0)">\n'
|
||||||
|
f' {panel_bg}\n {body}\n'
|
||||||
|
' </g>\n'
|
||||||
|
'</g>\n'
|
||||||
|
f'<circle cx="306" cy="54" r="{ADAPTIVE_SAFE_RADIUS}" fill="none" '
|
||||||
|
'stroke="#66FFFFFF" stroke-width="0.8" />'
|
||||||
|
)
|
||||||
|
PREVIEW.write_text(
|
||||||
|
'<svg xmlns="http://www.w3.org/2000/svg" width="1368" height="432" viewBox="0 0 360 114">\n'
|
||||||
|
' <defs>\n'
|
||||||
|
' <clipPath id="rounded-mask"><rect x="126" y="0" width="108" height="108" rx="27" /></clipPath>\n'
|
||||||
|
f' <clipPath id="circle-mask"><circle cx="306" cy="54" r="{ADAPTIVE_SAFE_RADIUS}" /></clipPath>\n'
|
||||||
|
' </defs>\n'
|
||||||
|
' <rect width="360" height="114" fill="#18130C" />\n'
|
||||||
|
f' {square}\n'
|
||||||
|
f' {rounded}\n'
|
||||||
|
f' {circle_preview}\n'
|
||||||
|
' <text x="54" y="113" fill="#DDD4C2" font-size="4" text-anchor="middle">square</text>\n'
|
||||||
|
' <text x="180" y="113" fill="#DDD4C2" font-size="4" text-anchor="middle">rounded</text>\n'
|
||||||
|
' <text x="306" y="113" fill="#DDD4C2" font-size="4" text-anchor="middle">circle</text>\n'
|
||||||
|
'</svg>\n',
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
write_vector(BACKGROUND, background_paths())
|
||||||
|
write_vector(FOREGROUND, foreground_paths(False))
|
||||||
|
write_vector(MONOCHROME, foreground_paths(True))
|
||||||
|
write_preview()
|
||||||
|
print(f"Wrote {FOREGROUND.relative_to(ROOT)}")
|
||||||
|
print(f"Wrote {MONOCHROME.relative_to(ROOT)}")
|
||||||
|
print(f"Wrote {BACKGROUND.relative_to(ROOT)}")
|
||||||
|
print(f"Wrote {PREVIEW.relative_to(ROOT)}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Executable
+185
@@ -0,0 +1,185 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage:
|
||||||
|
GITEA_BASE_URL=git.example.com GITEA_TOKEN_FILE=local/gitea-token scripts/release-gitea.sh 0.1
|
||||||
|
|
||||||
|
Creates a Gitea release for the current HEAD and uploads:
|
||||||
|
- the signed release APK
|
||||||
|
- a SHA-256 checksum file for the APK
|
||||||
|
|
||||||
|
Environment:
|
||||||
|
GITEA_BASE_URL Base URL of the Gitea instance, for example git.example.com
|
||||||
|
GITEA_TOKEN Gitea access token with repository write/release permissions
|
||||||
|
GITEA_TOKEN_FILE Optional file containing the token, used when GITEA_TOKEN is unset
|
||||||
|
GITEA_OWNER Optional repository owner override. Defaults to origin remote owner.
|
||||||
|
GITEA_REPO Optional repository name override. Defaults to origin remote repo.
|
||||||
|
VERSION_CODE Optional Android versionCode override.
|
||||||
|
|
||||||
|
The script requires a clean working tree and pushes the current branch and tag.
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
version="${1:-}"
|
||||||
|
if [[ ! "$version" =~ ^[0-9]+\.[0-9]+$ ]]; then
|
||||||
|
usage >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
: "${GITEA_BASE_URL:?Set GITEA_BASE_URL, for example git.example.com}"
|
||||||
|
|
||||||
|
if [[ -z "${GITEA_TOKEN:-}" && -n "${GITEA_TOKEN_FILE:-}" ]]; then
|
||||||
|
if [[ ! -f "$GITEA_TOKEN_FILE" ]]; then
|
||||||
|
echo "GITEA_TOKEN_FILE does not exist: ${GITEA_TOKEN_FILE}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
GITEA_TOKEN="$(tr -d '\r\n' < "$GITEA_TOKEN_FILE")"
|
||||||
|
fi
|
||||||
|
: "${GITEA_TOKEN:?Set GITEA_TOKEN or GITEA_TOKEN_FILE to a Gitea access token}"
|
||||||
|
|
||||||
|
require_clean_tree() {
|
||||||
|
if [[ -n "$(git status --porcelain)" ]]; then
|
||||||
|
echo "Working tree is not clean. Commit or stash changes before releasing." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
remote_repo_path() {
|
||||||
|
local remote_url
|
||||||
|
remote_url="$(git remote get-url origin)"
|
||||||
|
remote_url="${remote_url%.git}"
|
||||||
|
case "$remote_url" in
|
||||||
|
ssh://*)
|
||||||
|
remote_url="${remote_url#ssh://}"
|
||||||
|
remote_url="${remote_url#*/}"
|
||||||
|
;;
|
||||||
|
http://*|https://*)
|
||||||
|
remote_url="${remote_url#http://}"
|
||||||
|
remote_url="${remote_url#https://}"
|
||||||
|
remote_url="${remote_url#*/}"
|
||||||
|
;;
|
||||||
|
*@*:*)
|
||||||
|
remote_url="${remote_url#*:}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
printf '%s\n' "$remote_url"
|
||||||
|
}
|
||||||
|
|
||||||
|
json_string() {
|
||||||
|
python3 -c 'import json,sys; print(json.dumps(sys.argv[1]))' "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
extract_json_id() {
|
||||||
|
python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])'
|
||||||
|
}
|
||||||
|
|
||||||
|
version_code_for() {
|
||||||
|
local major minor
|
||||||
|
IFS=. read -r major minor <<<"$1"
|
||||||
|
printf '%d\n' "$((major * 10000 + minor * 100))"
|
||||||
|
}
|
||||||
|
|
||||||
|
urlencode() {
|
||||||
|
python3 -c 'import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1], safe=""))' "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
normalize_base_url() {
|
||||||
|
local base_url
|
||||||
|
base_url="${1%/}"
|
||||||
|
case "$base_url" in
|
||||||
|
http://*|https://*) ;;
|
||||||
|
*) base_url="https://${base_url}" ;;
|
||||||
|
esac
|
||||||
|
printf '%s\n' "$base_url"
|
||||||
|
}
|
||||||
|
|
||||||
|
require_clean_tree
|
||||||
|
|
||||||
|
tag="v${version}"
|
||||||
|
release_name="StatusBarTweak ${version}"
|
||||||
|
version_code="${VERSION_CODE:-$(version_code_for "$version")}"
|
||||||
|
repo_path="$(remote_repo_path)"
|
||||||
|
owner="${GITEA_OWNER:-${repo_path%%/*}}"
|
||||||
|
repo="${GITEA_REPO:-${repo_path##*/}}"
|
||||||
|
gitea_base_url="$(normalize_base_url "$GITEA_BASE_URL")"
|
||||||
|
api_base="${gitea_base_url}/api/v1"
|
||||||
|
|
||||||
|
if [[ "$owner" == "$repo_path" || -z "$owner" || -z "$repo" ]]; then
|
||||||
|
echo "Could not infer owner/repo from origin. Set GITEA_OWNER and GITEA_REPO." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
git fetch origin --tags
|
||||||
|
if git rev-parse -q --verify "refs/tags/${tag}" >/dev/null; then
|
||||||
|
echo "Tag ${tag} already exists." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
./gradlew clean assembleRelease \
|
||||||
|
-PsbtVersionName="$version" \
|
||||||
|
-PsbtVersionCode="$version_code"
|
||||||
|
|
||||||
|
apk_source="app/build/outputs/apk/release/app-release.apk"
|
||||||
|
if [[ ! -f "$apk_source" ]]; then
|
||||||
|
echo "Release APK not found at ${apk_source}." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
release_dir="build/release/${tag}"
|
||||||
|
apk_name="StatusBarTweak-${version}.apk"
|
||||||
|
checksum_name="${apk_name}.sha256"
|
||||||
|
mkdir -p "$release_dir"
|
||||||
|
cp "$apk_source" "${release_dir}/${apk_name}"
|
||||||
|
(cd "$release_dir" && sha256sum "$apk_name" > "$checksum_name")
|
||||||
|
|
||||||
|
git tag -a "$tag" -m "$release_name"
|
||||||
|
git push origin HEAD
|
||||||
|
git push origin "$tag"
|
||||||
|
|
||||||
|
body="Tested primarily on Samsung Galaxy Fold 5 running One UI 8.0 / Android 16."
|
||||||
|
payload="$(
|
||||||
|
cat <<EOF
|
||||||
|
{
|
||||||
|
"tag_name": $(json_string "$tag"),
|
||||||
|
"target_commitish": $(json_string "$(git rev-parse HEAD)"),
|
||||||
|
"name": $(json_string "$release_name"),
|
||||||
|
"body": $(json_string "$body"),
|
||||||
|
"draft": false,
|
||||||
|
"prerelease": true
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
)"
|
||||||
|
|
||||||
|
release_id="$(
|
||||||
|
curl --fail --silent --show-error \
|
||||||
|
-X POST \
|
||||||
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
--data "$payload" \
|
||||||
|
"${api_base}/repos/$(urlencode "$owner")/$(urlencode "$repo")/releases" \
|
||||||
|
| extract_json_id
|
||||||
|
)"
|
||||||
|
|
||||||
|
upload_asset() {
|
||||||
|
local path name
|
||||||
|
path="$1"
|
||||||
|
name="$(basename "$path")"
|
||||||
|
curl --fail --silent --show-error \
|
||||||
|
-X POST \
|
||||||
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
-F "attachment=@${path}" \
|
||||||
|
"${api_base}/repos/$(urlencode "$owner")/$(urlencode "$repo")/releases/${release_id}/assets?name=$(urlencode "$name")" \
|
||||||
|
>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
upload_asset "${release_dir}/${apk_name}"
|
||||||
|
upload_asset "${release_dir}/${checksum_name}"
|
||||||
|
|
||||||
|
echo "Created release ${tag}: ${gitea_base_url}/${owner}/${repo}/releases/tag/${tag}"
|
||||||
Reference in New Issue
Block a user