Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ jobs:
fi
"$sdkmanager" "platforms;android-36" "build-tools;36.0.0"
- run: bundle exec fastlane android checks
- name: Check branding vector conversion
env:
MESSAGE487_TEST_ANDROID_VECTORS: "1"
run: python3 -m unittest discover -s scripts/tests -p test_generate_branding.py
- name: Publish test and lint reports
if: always()
uses: actions/upload-artifact@v7
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,4 @@ __pycache__/
dist/
.playwright-cli/
test-results/
**/*.tmp
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,9 @@ unsigned release APK links are kept in the PR description. See [testing](docs/en
For signed APK releases, see [Releases](docs/en/releases.md) and [release automation](docs/en/release-automation.md), and [release testing](docs/en/release-testing.md).
English and Russian store descriptions for F-Droid and Google Play share the canonical
[Fastlane metadata](fastlane/metadata/android). Store graphics and their provenance are documented
in [Branding](assets/branding/README.md).
in [Branding](assets/branding/README.md). Edit `assets/branding/branding.ai` and run
`./scripts/generate-branding.py` on macOS with Illustrator to regenerate SVG, PNG and Android
icon resources. Ordinary builds use the committed resources and do not require Illustrator.

See the [project context](docs/en/project-context.md) for remaining product decisions.
This project succeeds [sms487](https://github.com/andre487/sms487).
Expand Down
15 changes: 4 additions & 11 deletions app/src/main/res/drawable/ic_launcher_background.xml
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
<vector xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android" android:width="108dp" android:height="108dp" android:viewportWidth="108" android:viewportHeight="108">
<path android:pathData="M0,0H108V108H0Z">
<aapt:attr name="android:fillColor">
<gradient android:type="linear" android:startX="0" android:startY="0" android:endX="108" android:endY="108">
<item android:offset="0" android:color="#FF00C9EF" />
<item android:offset=".35" android:color="#FF0754F5" />
<item android:offset=".65" android:color="#FF6500ED" />
<item android:offset="1" android:color="#FFFF1AB9" />
</gradient>
</aapt:attr>
</path>
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="108dp" android:height="108dp" android:viewportWidth="768" android:viewportHeight="768">
<group android:translateX="128" android:translateY="128">
<path android:pathData="M-0.28,-0.06h512v512h-512V-0.06z" android:fillColor="#302F5E" />
</group>
</vector>
12 changes: 6 additions & 6 deletions app/src/main/res/drawable/ic_launcher_foreground.xml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="108dp" android:height="108dp" android:viewportWidth="1254" android:viewportHeight="1254">
<group android:translateX="169.29" android:translateY="169.29" android:scaleX="0.73" android:scaleY="0.73">
<path android:fillColor="#FFffffff" android:pathData="M313,298H877Q942,298 942,363V563H878V377Q878,361 862,361H329Q313,361 313,377V711Q313,727 329,727H764V790H313Q248,790 248,725V363Q248,298 313,298Z M544,790H642V857H698Q730,857 730,887Q730,917 698,917H488Q456,917 456,887Q456,857 488,857H544Z" />
<path android:fillColor="#FFffffff" android:pathData="M819,563H960Q1015,563 1015,618V887Q1015,942 960,942H819Q764,942 764,887V618Q764,563 819,563Z M831,616Q813,616 813,634V870Q813,888 831,888H948Q966,888 966,870V634Q966,616 948,616Z" android:fillType="evenOdd" />
<path android:fillColor="#00000000" android:pathData="M508,522L590,465L672,522V611H508Z M508,522L546,547H634L672,522" android:strokeColor="#FFffffff" android:strokeWidth="17" android:strokeLineJoin="round" android:strokeLineCap="round" />
<path android:fillColor="#00000000" android:pathData="M855,725H923Q929,725 929,731V783Q929,789 923,789H855Q849,789 849,783V731Q849,725 855,725Z M850,731L889,761L928,731" android:strokeColor="#FFffffff" android:strokeWidth="11" android:strokeLineJoin="round" android:strokeLineCap="round" />
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="108dp" android:height="108dp" android:viewportWidth="768" android:viewportHeight="768">
<group android:translateX="128" android:translateY="128">
<path android:pathData="M113.15,105.94l256.15,0c16.25,0 29.43,13.17 29.43,29.43L398.72,225.94l-30,0L368.72,142.18c0,-4 -3.24,-7.24 -7.24,-7.24l-241.51,0c-4,0 -7.24,3.24 -7.24,7.24l0,151.51c0,4 3.24,7.24 7.24,7.24l197.76,0l0,28L113.15,328.94c-16.25,0 -29.43,-13.17 -29.43,-29.43L83.72,135.37C83.72,119.11 96.9,105.94 113.15,105.94zM217.72,328.94l45,0l0,30l25.2,0c9.6,0 14.4,4.5 14.4,13.5s-4.8,13.5 -14.4,13.5L193.42,385.94c-9.6,0 -14.4,-4.5 -14.4,-13.5s4.8,-13.5 14.4,-13.5L217.72,358.94L217.72,328.94z" android:fillColor="#FFFFFF" />
<path android:pathData="M341.62,225.94l64.2,0c13.75,0 24.9,11.15 24.9,24.9l0,122.2c0,13.75 -11.15,24.9 -24.9,24.9l-64.2,0c-13.75,0 -24.9,-11.15 -24.9,-24.9L316.72,250.84C316.72,237.09 327.87,225.94 341.62,225.94zM338.72,258.09l0,106.7c0,4.5 3.65,8.15 8.15,8.15l52.7,0c4.5,0 8.15,-3.65 8.15,-8.15L407.72,258.09c0,-4.5 -3.65,-8.15 -8.15,-8.15l-52.7,0C342.37,249.94 338.72,253.59 338.72,258.09z" android:fillColor="#FFFFFF" android:fillType="evenOdd" />
<path android:pathData="M202.72,207.65l37,-25.81l37,25.81l0,40.29L202.72,247.94L202.72,207.65zM201.78,207.62l17.2,11.32l39.84,0l17.2,-11.32" android:strokeLineJoin="round" android:strokeWidth="5.55" android:fillColor="#00000000" android:strokeColor="#FFFFFF" android:strokeLineCap="round" />
<path android:pathData="M358.94,298.44l31.57,0c1.5,0 2.72,1.22 2.72,2.72l0,24.57c0,1.5 -1.22,2.72 -2.72,2.72l-31.57,0c-1.5,0 -2.72,-1.22 -2.72,-2.72l0,-24.57C356.22,299.65 357.44,298.44 358.94,298.44zM356.61,302.06l17.66,13.58l17.66,-13.58" android:strokeLineJoin="round" android:strokeWidth="3.59" android:fillColor="#00000000" android:strokeColor="#FFFFFF" android:strokeLineCap="round" />
</group>
</vector>
2 changes: 1 addition & 1 deletion app/src/main/res/drawable/ic_notification.xml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:fillColor="#FFFFFFFF" android:fillType="evenOdd" android:pathData="M3 3H19Q21 3 21 5V8H19V5H3V16H14V18H12V20H15V22H6V20H9V18H3Q1 18 1 16V5Q1 3 3 3Z M17 9H21Q23 9 23 11V20Q23 22 21 22H17Q15 22 15 20V11Q15 9 17 9Z M17 11V20H21V11Z" />
<path android:pathData="M3.013,2.938h16c1.105,0 2,0.895 2,2v3h-2v-3h-16v11h11v2h-2v2h3v2h-9v-2h3v-2H3.013c-1.105,0 -2,-0.895 -2,-2v-11C1.013,3.833 1.909,2.938 3.013,2.938zM17.013,8.938h4c1.105,0 2,0.895 2,2v9c0,1.105 -0.895,2 -2,2h-4c-1.105,0 -2,-0.895 -2,-2v-9C15.013,9.833 15.909,8.938 17.013,8.938zM17.013,10.938v9h4v-9H17.013z" android:fillColor="#FFFFFF" android:fillType="evenOdd" />
</vector>
Binary file modified app/src/main/res/mipmap-hdpi/ic_launcher.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified app/src/main/res/mipmap-hdpi/ic_launcher_round.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified app/src/main/res/mipmap-mdpi/ic_launcher.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified app/src/main/res/mipmap-mdpi/ic_launcher_round.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified app/src/main/res/mipmap-xhdpi/ic_launcher.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
131 changes: 106 additions & 25 deletions assets/branding/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,53 +2,108 @@

| Asset | Use |
| --- | --- |
| [icon.svg](icon.svg) · [PNG](icon.png) | Canonical square artwork for web and PNG exports |
| [branding.ai](branding.ai) | Editable Illustrator master for all four artboards |
| [icon.svg](icon.svg) · [PNG](icon.png) | Exported square artwork for web and launcher/store resources |
| [icon-round.svg](icon-round.svg) · [PNG](icon-round.png) | Circular artwork used in the project README and round launcher PNGs |
| [status-icon.svg](status-icon.svg) · [PNG](status-icon.png) | Simplified monochrome monitor/phone mark for small sizes |
| [feature.svg](feature.svg) · [PNG](feature.png) | Canonical store banner artwork, 1024×500 |
| [feature.svg](feature.svg) · [PNG](feature.png) | Exported store banner artwork, 1024×500 |
| [icon.png](../../fastlane/metadata/android/en-US/images/icon.png) | Store app icon, 512×512 RGB PNG |
| [featureGraphic.png](../../fastlane/metadata/android/en-US/images/featureGraphic.png) | Store feature graphic, 1024×500 opaque RGB PNG; shared by both locales |
| `fastlane/metadata/android/{en-US,ru-RU}/images/phoneScreenshots/` | Actual phone screenshots in English and Russian, 1080×1920 |

The selected design shows an open envelope on a monitor and a closed envelope on a phone,
with a cyan/blue/violet/magenta gradient inspired by AndroidMegaProxy. The initial concepts
were created with the built-in `image_gen` tool, then reconstructed as native vectors.
The final design prompt was: “Monitor with a small open envelope and phone with a small closed
envelope on a cyan/blue/violet/magenta gradient; preserve generous margins in a circular crop.”
The current launcher artwork shows an open envelope on a monitor and a closed envelope
on a phone, in white on a dark purple background. The editable master is
[`branding.ai`](branding.ai), containing the square, round, status and banner artwork.
Each SVG has a matching PNG beside it for sharing where SVG is unsupported.
Sizes: square/round icon 512×512, status icon 24×24, feature graphic 1024×500.
The round and status PNGs retain transparency; the white status mark needs a contrasting background.

Android adaptive layers live in `app/src/main/res/drawable/ic_launcher_{background,foreground}.xml`.
The adaptive foreground follows the square SVG at scale `0.73` about the center of its
1254-unit viewport. The separate round SVG retains scale `0.65` for extra circular-mask padding.
Both SVGs share the Android paths and gradient, cropped to the central 836×836 viewport.
The SVG files remain separate editable sources; regenerate derived files after editing them.
The adaptive foreground and background are derived from the square SVG. Its artboard is
kept as the visible icon area; a 1.5× viewport provides Android adaptive-icon overscan.
The round artboard has its own layout and clipping and is exported to a separate SVG.
The SVG files remain separate exports; edit the Illustrator master and regenerate them.
The themed launcher and notification small icon share `ic_notification.xml`, the simplified
white monitor/phone silhouette without envelopes or gradient. Update notifications use this
small icon. The app UI retains its purple Material theme and standard Material icons.

Generate all derived assets from the **separate** SVG sources:
Save your edits in **branding.ai**, then generate all SVG, PNG and Android XML assets
from the repository root:

```sh
python3 scripts/generate-branding.py
./scripts/generate-branding.py
```

Requires `rsvg-convert` and ImageMagick's `magick`. Run from any directory.
Use `--output-root /tmp/message487-branding` to inspect exports without overwriting repository files.
To install the regenerated resources on the development emulator afterwards:

| Source | Generated assets |
```sh
./scripts/run-without-debugging.sh
```

Generation requires macOS, Adobe Illustrator, Python 3, `rsvg-convert`, ImageMagick's
`magick`, JDK 21 and the project's Gradle dependencies (downloaded by a normal Android
build). The Android SDK must be configured as for a normal build. Allow macOS Automation
access to Illustrator when prompted on the first run. The exporter reads the saved file,
not unsaved editor changes. It configures SVG export options automatically.

To inspect all exports without overwriting repository assets:

```sh
./scripts/generate-branding.py --output-root /tmp/message487-branding
```

The script resolves the master and build tools relative to the repository, so it can also
be invoked by its absolute path from another directory.

The master must contain exactly one artboard with each of these names and dimensions:

| Artboard | Size |
| --- | --- |
| `icon.svg` | Square branding PNG, opaque RGB store icon, square launcher PNGs, adaptive foreground/background XML |
| `icon-round.svg` | Transparent round branding PNG, round launcher PNGs |
| `status-icon.svg` | Transparent 24×24 PNG and `ic_notification.xml` |
| `feature.svg` | Branding banner PNG and opaque RGB store feature graphic |
| `icon` | 512×512 |
| `icon-round` | 512×512 |
| `status-icon` | 24×24 |
| `feature` | 1024×500 |

The exporter works on a temporary disk copy, exports each artboard independently, and
expands symbols only in temporary export documents. The master and its symbol links remain
unchanged. Keep intended artwork visible and unlocked so Illustrator can select it.
SVG export uses presentation attributes, outlined text and seven decimal places; random
Illustrator IDs are normalized for repeatable exports. Artboard positions and padding are
preserved without snapping. SVGs are refreshed alongside the generated PNG/XML assets.
Asset generation always uses Illustrator. CI and ordinary Android builds use the committed
generated resources and do not require Illustrator or run the asset generator.

`run-without-debugging.sh` builds and installs the existing resources; it does not regenerate
them from the Illustrator master. Commit `branding.ai` and all generated SVG, PNG and XML
files together. Screenshots are captured separately and are not generated by this script.

| Master artboard | Exported SVG | Other generated assets |
| --- | --- | --- |
| `icon` | `icon.svg` | Square branding PNG, opaque RGB store icon, square launcher PNGs, adaptive foreground/background XML |
| `icon-round` | `icon-round.svg` | Transparent round branding PNG, round launcher PNGs |
| `status-icon` | `status-icon.svg` | Transparent 24×24 PNG and `ic_notification.xml` |
| `feature` | `feature.svg` | Branding banner PNG and opaque RGB store feature graphic |

Launcher PNG sizes: mdpi 48, hdpi 72, xhdpi 96, xxhdpi 144, xxxhdpi 192.
The script preserves each source's layout, including the square/round scale difference.
The Android vector exporter supports the existing path fill/stroke attributes, the square
artwork's `translate(x y) scale(s)` transform, and its diagonal background gradient.
Unsupported path features or gradient changes cause an error instead of silently losing details.
The script preserves each artboard's layout, including any square/round scale differences.
Each PNG is rasterized directly at its target resolution, without resizing another PNG.
Adaptive viewport offsets are applied as Android group translations to avoid rounding
path coordinates just to reposition the artwork. Explicit transforms inside the source
are still handled by the Android importer and may introduce coordinate rounding.
This preserves geometry; it does not snap curves or strokes to the pixel grid, and
anti-aliasing can differ between SVG renderers and Android launchers.
Android XML is converted by the SVG importer bundled with the Android Gradle Plugin,
through `convertBrandingVectors` (offline). Nested groups, arbitrary IDs, solid backgrounds,
clipping paths and transforms are handled by that importer; unsupported SVG features fail
before repository outputs are written. No specific layer names or ID values are required.
The first drawable shape in `icon.svg` is the background; the remaining shapes form the
foreground. Keep the background as a single full-artboard shape, behind the artwork.
The square SVG must have a positive square `viewBox`; it determines the visible icon area.
Transparent margins inside that artboard are preserved, so a smaller clipping rectangle
does not automatically crop the export or remove padding.
Full-artboard rectangular clipping paths are expanded for adaptive-icon overscan; internal
clipping paths are retained. Each generation refreshes the exported SVG files as well.

Adaptive-icon and monochrome-inset XML files only reference these generated drawables and
remain static. Samsung previews are optional and use the separate command below.

Expand All @@ -59,12 +114,14 @@ Preview the adaptive artwork with an approximate Samsung-style rounded-square ma

```sh
python3 scripts/preview-samsung-icon.py --source assets/branding/icon.svg /tmp/message487-samsung.png
# Edit the source SVG, then rerun the same command to regenerate the PNG.
python3 scripts/preview-samsung-icon.py --transparent /tmp/message487-samsung-transparent.png
# After editing branding.ai, run generate-branding.py, then rerun the preview.
# Use an .svg output path instead to generate a masked vector preview.
```

Output defaults to 140×140 pixels, approximately the icon size in the supplied screenshot.
Use `--size 512` for a larger preview.
The preview has a white background by default; use `--transparent` to disable it.
SVG output uses only Python's standard library; PNG output also needs `rsvg-convert`.
Use `--source` to choose another SVG; its `viewBox` defines the mask bounds.
By default the preview reuses `icon.svg`, which shares the adaptive foreground's scale and
Expand All @@ -77,6 +134,30 @@ and shade are a visual approximation, not a recovered Samsung bevel algorithm.
Use `--bevel 0.3` to adjust edge strength (0–1), or `--flat` for the mask alone.
Themes, night-mode tint and animations are not reproduced.

Check the generator and preview with:

```sh
python3 -m unittest discover -s scripts/tests -p test_generate_branding.py
python3 -m unittest discover -s scripts/tests -p test_preview_samsung_icon.py
```

The default generator tests check SVG layer preparation without Android build tools or
Illustrator. To also check Android conversion and PNG exports, prepare the generation
dependencies above and run:

```sh
MESSAGE487_TEST_ANDROID_VECTORS=1 python3 -m unittest discover -s scripts/tests -p test_generate_branding.py
```

CI runs Android conversion checks in the Android job after the build has populated the
Gradle cache. PNG export checks additionally require `rsvg-convert` and `magick`.
To also verify real Illustrator export, repeatability and preservation of the master
file on macOS, use:

```sh
MESSAGE487_TEST_ILLUSTRATOR=1 python3 -m unittest discover -s scripts/tests -p test_generate_branding.py
```

For a device-rendered reference, [One UI Icon Extractor](https://github.com/Palmoe/OneUI-Icon-Extractor)
uses Samsung's `semGetApplicationIconForIconTray` / `semGetActivityIconForIconTray` APIs
on a Samsung device, retaining One UI effects. Compare its export from the target phone
Expand Down
Loading
Loading