@maccesar/titools 2.7.2 → 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,84 +0,0 @@
1
- # Master input guidelines
2
-
3
- ## Preferred: SVG
4
-
5
- SVG is the best source because:
6
-
7
- - Vector: re-renders cleanly at any density, no upscaling artifacts
8
- - Preserves transparency natively
9
- - Small file size (~5–50 KB for typical logos vs 100+ KB for 1024² PNG)
10
- - Easy to edit by designers in Illustrator / Affinity / Figma / Inkscape
11
-
12
- **SVG requirements**:
13
-
14
- - Square viewBox (or content that's square within a rectangular viewBox)
15
- - Paths only — avoid `<script>`, `<foreignObject>`, web fonts
16
- - Solid fills or simple gradients — very complex gradients may rasterize inconsistently across densities
17
- - No external references (embedded images, fonts loaded by URL)
18
-
19
- The skill uses `rsvg-convert` to rasterize to 1024×1024 PNG before any further processing.
20
-
21
- ## Acceptable: PNG ≥ 1024×1024 with transparency
22
-
23
- A PNG master works when SVG isn't available:
24
-
25
- - Minimum size: 1024×1024 (smaller input produces visibly soft xxxhdpi icons)
26
- - Square preferred — non-square inputs get center-cropped with a warning
27
- - Transparent background for best monochrome layer results
28
-
29
- ## Accepted with warning: PNG with solid background
30
-
31
- PNGs that already have a non-transparent background still work, but:
32
-
33
- - The monochrome layer will be a solid square silhouette (every pixel is non-transparent, so "everything" becomes white)
34
- - You cannot change the adaptive background color — it's baked in
35
- - Recommend re-exporting from the source with transparency if possible
36
-
37
- ## Padding and the "pegado" problem
38
-
39
- If the icon's content already fills the full canvas (logo touching the edges), every generated asset will show the same cramped look. The skill cannot add space that isn't there.
40
-
41
- Two ways to fix:
42
-
43
- 1. **Prepare the master with transparent padding** — export the logo so there's ~15% empty space around it in the source
44
- 2. **Increase the `--padding` flag** — the skill re-centers the logo inside the target canvas. Higher padding pushes the logo inward
45
-
46
- Recommended padding values:
47
-
48
- | Padding | Logo fill | When to use |
49
- |---------|-----------|-------------|
50
- | 19 (spec floor) | 62% | Intricate logos needing maximum detail; risky with aggressive launcher masks |
51
- | 22 (default) | 56% | Balanced, compliant with Android safe-zone spec + 3% buffer |
52
- | 25 | 50% | Logos with fine strokes or details near the edge |
53
- | 28–32 | 44–36% | Bold simple shapes, single-letter monograms |
54
-
55
- If the user sees the logo pegado after running the skill once, re-run with a higher `--padding`.
56
-
57
- ## Alpha channel handling
58
-
59
- PNG inputs commonly have one of three alpha configurations:
60
-
61
- - **Straight alpha** (most common): `rgba(r, g, b, a)` — R/G/B hold true color, A holds coverage
62
- - **Premultiplied alpha**: `rgba(r × a, g × a, b × a, a)` — R/G/B are scaled by A
63
- - **No alpha channel**: opaque only
64
-
65
- ImageMagick handles straight alpha transparently. Premultiplied inputs may show dark halos at the edges when composited — if this happens, re-export from the source app with straight alpha.
66
-
67
- ## Checking the master before running
68
-
69
- Quick sanity check:
70
-
71
- ```bash
72
- # Dimensions and alpha info
73
- magick identify -format "%wx%h alpha=%A\n" /path/to/logo.png
74
-
75
- # Visualize the alpha channel (useful for debugging halos)
76
- magick /path/to/logo.png -alpha extract /tmp/alpha.png && open /tmp/alpha.png
77
- ```
78
-
79
- For SVG:
80
-
81
- ```bash
82
- # Preview at output size
83
- rsvg-convert -w 1024 -h 1024 /path/to/logo.svg -o /tmp/preview.png && open /tmp/preview.png
84
- ```
@@ -1,63 +0,0 @@
1
- # Notification icons
2
-
3
- ## The white-only rule
4
-
5
- Android notification icons, since API 21 (Lollipop), must be **white pixels on a transparent background**. The system applies a runtime tint based on the notification's `color` property — color in the source image is discarded.
6
-
7
- If the icon is not white+alpha, it renders as a blank white square on API 21+ (a common bug symptom when developers reuse their launcher icon as the notification icon).
8
-
9
- ## Generation
10
-
11
- The skill's `gen-notification.sh`:
12
-
13
- 1. Scales the foreground logo down to the notification size
14
- 2. Runs ImageMagick's `-channel RGB -fill white -colorize 100 +channel` to force all RGB channels to 255 while preserving alpha
15
- 3. Pads transparently to match the target canvas size
16
-
17
- ## Sizes
18
-
19
- | Density | Size |
20
- |----------|------|
21
- | mdpi | 24 |
22
- | hdpi | 36 |
23
- | xhdpi | 48 |
24
- | xxhdpi | 72 |
25
- | xxxhdpi | 96 |
26
-
27
- ## File naming
28
-
29
- Titanium has no hard convention, but the de-facto name is `ic_stat_notify.png`. The skill emits this name. To reference it:
30
-
31
- **In JavaScript (local notification)**:
32
-
33
- ```js
34
- Ti.Android.NotificationManager.notify(1, Ti.Android.createNotification({
35
- contentTitle: 'Hello',
36
- contentText: 'Hi there',
37
- icon: Ti.App.Android.R.drawable.ic_stat_notify // white+alpha
38
- }))
39
- ```
40
-
41
- **In `platform/android/AndroidManifest.xml` (Firebase default)**:
42
-
43
- ```xml
44
- <meta-data
45
- android:name="com.google.firebase.messaging.default_notification_icon"
46
- android:resource="@drawable/ic_stat_notify"/>
47
- <meta-data
48
- android:name="com.google.firebase.messaging.default_notification_color"
49
- android:resource="@color/notification_tint"/>
50
- ```
51
-
52
- With a matching `values/colors.xml`:
53
-
54
- ```xml
55
- <color name="notification_tint">#FDD900</color>
56
- ```
57
-
58
- The `default_notification_color` is the tint the runtime applies to the white pixels.
59
-
60
- ## References
61
-
62
- - Android: [Notifications overview](https://developer.android.com/develop/ui/views/notifications)
63
- - Firebase: [Setting the default notification icon](https://firebase.google.com/docs/cloud-messaging/android/client#manifest)
@@ -1,81 +0,0 @@
1
- # Splash screens (modern)
2
-
3
- ## Two strategies by platform
4
-
5
- ### iOS — storyboard-driven (iOS 14+)
6
-
7
- Modern iOS uses `LaunchScreen.storyboard`. Titanium enables this via `tiapp.xml`:
8
-
9
- ```xml
10
- <ios>
11
- <enable-launch-screen-storyboard>true</enable-launch-screen-storyboard>
12
- <default-background-color>#0B1326</default-background-color>
13
- </ios>
14
- ```
15
-
16
- The storyboard is a single layout that scales dynamically to any device. No per-device PNG launch images are needed. The `<default-background-color>` sets both the storyboard background AND the gap-color between the storyboard and the first view.
17
-
18
- This skill does NOT generate iOS launch images. It prints the `<default-background-color>` snippet for the user to paste.
19
-
20
- ### Android 12+ — SplashScreen API (API 31+)
21
-
22
- Android 12 introduced a system-managed splash that replaces the old "put a PNG in drawable/background.png" approach. It's theme-driven:
23
-
24
- ```xml
25
- <!-- platform/android/res/values-v31/splash_theme.xml -->
26
- <resources>
27
- <style name="Theme.SnapGym.SplashScreen" parent="Theme.SplashScreen">
28
- <item name="windowSplashScreenBackground">#0B1326</item>
29
- <item name="windowSplashScreenAnimatedIcon">@drawable/splash_icon</item>
30
- <item name="postSplashScreenTheme">@style/Theme.AppDerived</item>
31
- </style>
32
- </resources>
33
- ```
34
-
35
- With a matching `AndroidManifest.xml` entry (inside `<application>`):
36
-
37
- ```xml
38
- <meta-data android:name="io.tidev.titanium.splash.theme"
39
- android:value="@style/Theme.SnapGym.SplashScreen"/>
40
- ```
41
-
42
- Critical: **do NOT apply this theme to `<application android:theme>` or to a specific `<activity>`** — it only affects the system SplashScreen via the manifest meta-data. Applying it to the application breaks the ActionBar / TitleBar in every subsequent screen (as SNAP Gym learned the hard way).
43
-
44
- ### Android <12 — fallback to launcher icon
45
-
46
- On API <31, the system uses the launcher icon as the splash. No extra file needed. The `background.9.png` 9-patch pattern is obsolete in Titanium SDK 13+.
47
-
48
- ## splash_icon.png canvas (Android 12+)
49
-
50
- The SplashScreen API spec:
51
-
52
- - Total canvas: **288 dp**
53
- - Icon safe-zone: **192 dp centered** (≈ 67% of canvas)
54
- - OS applies a circular mask automatically — keep content centered
55
-
56
- Density sizes:
57
-
58
- | Density | Total canvas | Icon area |
59
- |----------|--------------|-----------|
60
- | mdpi | 288 | 192 |
61
- | hdpi | 432 | 288 |
62
- | xhdpi | 576 | 384 |
63
- | xxhdpi | 864 | 576 |
64
- | xxxhdpi | 1152 | 768 |
65
-
66
- The skill's `gen-splash-icon.sh` places the logo at `192/288 ≈ 67%` of the canvas with transparent padding around it.
67
-
68
- ## Why we don't generate `background.9.png`
69
-
70
- The old 9-patch splash pattern works like this:
71
-
72
- - PNG with 1px borders marked as stretchable regions
73
- - Titanium references it as `background.9.png` in `drawable-*dpi/`
74
- - SDK pre-13 used it as `windowBackground` for the root activity
75
-
76
- Titanium 13+ handles splash via the modern SplashScreen API on 12+ and via the launcher icon itself on older versions. `background.9.png` produces worse results on 12+ (competing with the SplashScreen API) and offers no benefit on older versions that the launcher fallback can't provide.
77
-
78
- ## References
79
-
80
- - Android: [Splash screens](https://developer.android.com/develop/ui/views/launch/splash-screen)
81
- - Apple: [Configuring your app's launch screen](https://developer.apple.com/documentation/xcode/configuring-your-apps-launch-screen)
@@ -1,84 +0,0 @@
1
- # Canonical paths — Titanium SDK 13.x
2
-
3
- ## Alloy layout (primary)
4
-
5
- ```
6
- project-root/
7
- ├── DefaultIcon-ios.png # iOS master (1024² no alpha)
8
- ├── DefaultIcon.png # Generic master (1024² no alpha) — fallback
9
- ├── iTunesConnect.png # 1024² App Store submission
10
- ├── MarketplaceArtwork.png # 512² Google Play submission
11
- ├── tiapp.xml
12
- └── app/
13
- ├── assets/
14
- │ ├── iphone/ # (optional) manual iOS appiconset — skill doesn't write here
15
- │ └── android/
16
- │ ├── appicon.png # 128² legacy fallback (pre-adaptive)
17
- │ └── images/ # density-scaled in-app image assets (NOT launcher)
18
- └── platform/
19
- └── android/
20
- └── res/
21
- ├── mipmap-mdpi/
22
- │ ├── ic_launcher.png # legacy 48²
23
- │ ├── ic_launcher_foreground.png # adaptive 108²
24
- │ ├── ic_launcher_background.png # adaptive 108²
25
- │ └── ic_launcher_monochrome.png # themed 108²
26
- ├── mipmap-hdpi/ # 72 / 162 / 162 / 162
27
- ├── mipmap-xhdpi/ # 96 / 216 / 216 / 216
28
- ├── mipmap-xxhdpi/ # 144 / 324 / 324 / 324
29
- ├── mipmap-xxxhdpi/ # 192 / 432 / 432 / 432
30
- ├── mipmap-anydpi-v26/
31
- │ └── ic_launcher.xml # binds the 3 adaptive layers
32
- ├── drawable-mdpi/
33
- │ ├── ic_stat_notify.png # (optional) 24² white+alpha
34
- │ └── splash_icon.png # (optional) 288² Android 12+ splash
35
- ├── drawable-hdpi/ # 36 / 432
36
- ├── drawable-xhdpi/ # 48 / 576
37
- ├── drawable-xxhdpi/ # 72 / 864
38
- └── drawable-xxxhdpi/ # 96 / 1152
39
- ```
40
-
41
- ## Classic layout
42
-
43
- Same structure, different roots. The skill detects `Resources/` and remaps:
44
-
45
- | Alloy path | Classic path |
46
- |---|---|
47
- | `app/assets/android/appicon.png` | `Resources/android/appicon.png` |
48
- | `app/platform/android/res/...` | `platform/android/res/...` |
49
-
50
- Root-level files (`DefaultIcon*.png`, `iTunesConnect.png`, `MarketplaceArtwork.png`) are identical in both layouts.
51
-
52
- ## What Titanium does at build time
53
-
54
- - **iOS**: reads the root master → writes to `build/iphone/Assets.xcassets/AppIcon.appiconset/` (build directory, regenerated every build, never commited)
55
- - **Android**: copies `app/platform/android/res/*` verbatim to `build/android/app/src/main/res/`, plus copies `app/assets/android/appicon.png` to `build/.../res/drawable-*dpi/appicon.png` if the legacy path is used
56
-
57
- Result: anything the skill writes to `app/platform/android/res/` or `app/assets/` survives builds. Nothing the skill generates can be accidentally overwritten by `ti build`.
58
-
59
- ## What the skill writes
60
-
61
- After confirming with the user, the skill copies from its staging directory to:
62
-
63
- | Target | Files |
64
- |---|---|
65
- | Project root | `DefaultIcon-ios.png`, `iTunesConnect.png`, `MarketplaceArtwork.png` |
66
- | `app/platform/android/res/mipmap-*/` | Launcher icons (adaptive triplet + legacy flat), 5 densities |
67
- | `app/platform/android/res/mipmap-anydpi-v26/` | `ic_launcher.xml` |
68
- | `app/platform/android/res/drawable-*/` (optional) | `ic_stat_notify.png`, `splash_icon.png` |
69
-
70
- The skill never touches `tiapp.xml`, `config.json`, source code, or anything outside the asset/resource paths above.
71
-
72
- ## Required `tiapp.xml` entry
73
-
74
- For the adaptive icon to be picked up, `tiapp.xml` must reference it:
75
-
76
- ```xml
77
- <android>
78
- <manifest>
79
- <application android:icon="@mipmap/ic_launcher"/>
80
- </manifest>
81
- </android>
82
- ```
83
-
84
- The skill checks for this and, if missing, prints the snippet — does not edit the file. See `tiapp-xml-snippets.md` for the full set.
@@ -1,92 +0,0 @@
1
- # tiapp.xml snippets
2
-
3
- The skill prints these blocks after generating assets. The user reviews and pastes them manually — `tiapp.xml` often contains sensitive info (API keys, signing identities, staging URLs) and automated editing would be fragile.
4
-
5
- ## Block 1 — iOS background color (for storyboard splash)
6
-
7
- Under the `<ios>` element:
8
-
9
- ```xml
10
- <ios>
11
- <enable-launch-screen-storyboard>true</enable-launch-screen-storyboard>
12
- <default-background-color>#0B1326</default-background-color>
13
- </ios>
14
- ```
15
-
16
- Replace `#0B1326` with the user's `--bg-color`.
17
-
18
- ## Block 2 — Android application icon reference
19
-
20
- Under `<android><manifest><application>`:
21
-
22
- ```xml
23
- <android>
24
- <manifest xmlns:android="http://schemas.android.com/apk/res/android">
25
- <application android:icon="@mipmap/ic_launcher"
26
- android:usesCleartextTraffic="false"/>
27
- </manifest>
28
- </android>
29
- ```
30
-
31
- The `android:icon="@mipmap/ic_launcher"` tells Android to use the adaptive icon XML (via `mipmap-anydpi-v26/ic_launcher.xml` on API 26+) with automatic fallback to the flat `mipmap-*dpi/ic_launcher.png` on older devices.
32
-
33
- ## Block 3 — Android 12+ splash screen (optional, requires `--with-splash-icon`)
34
-
35
- Requires three changes:
36
-
37
- ### 3a. Splash theme resource
38
-
39
- Create `app/platform/android/res/values-v31/splash_theme.xml`:
40
-
41
- ```xml
42
- <?xml version="1.0" encoding="utf-8"?>
43
- <resources>
44
- <style name="Theme.App.SplashScreen" parent="Theme.SplashScreen">
45
- <item name="windowSplashScreenBackground">#0B1326</item>
46
- <item name="windowSplashScreenAnimatedIcon">@drawable/splash_icon</item>
47
- <item name="postSplashScreenTheme">@style/Theme.AppDerived</item>
48
- </style>
49
- </resources>
50
- ```
51
-
52
- ### 3b. Manifest meta-data (NOT a theme on `<application>` or `<activity>`)
53
-
54
- Under `<android><manifest><application>`:
55
-
56
- ```xml
57
- <meta-data android:name="io.tidev.titanium.splash.theme"
58
- android:value="@style/Theme.App.SplashScreen"/>
59
- ```
60
-
61
- ### 3c. DO NOT do this
62
-
63
- Setting `android:theme="@style/Theme.App.SplashScreen"` on `<application>` or `<activity>` will strip the ActionBar / TitleBar from every screen in the app because Titanium's `Theme.AppDerived` inherits from the application theme. The SplashScreen API is designed to work through the manifest meta-data entry above, not through activity theming.
64
-
65
- ## Block 4 — Firebase Cloud Messaging notification icon (optional, requires `--with-notification`)
66
-
67
- Create `app/platform/android/res/values/colors.xml` (if it doesn't exist) with:
68
-
69
- ```xml
70
- <?xml version="1.0" encoding="utf-8"?>
71
- <resources>
72
- <color name="notification_tint">#FDD900</color>
73
- </resources>
74
- ```
75
-
76
- Then under `<android><manifest><application>`:
77
-
78
- ```xml
79
- <meta-data android:name="com.google.firebase.messaging.default_notification_icon"
80
- android:resource="@drawable/ic_stat_notify"/>
81
- <meta-data android:name="com.google.firebase.messaging.default_notification_color"
82
- android:resource="@color/notification_tint"/>
83
- ```
84
-
85
- ## What the skill reports
86
-
87
- After generation, the skill prints only the blocks relevant to the flags used and the project's current state:
88
-
89
- - Always prints Block 2 if `android:icon="@mipmap/ic_launcher"` is missing from tiapp.xml
90
- - Always prints Block 1 if `<default-background-color>` is missing
91
- - Prints Block 3 only if `--with-splash-icon` was used
92
- - Prints Block 4 only if `--with-notification` was used
@@ -1,230 +0,0 @@
1
- #!/usr/bin/env bash
2
- # cleanup-legacy.sh — context-aware removal of legacy branding artifacts.
3
- #
4
- # Reads tiapp.xml to decide what's safe to delete. Categorizes targets into:
5
- #
6
- # SAFE always deleted — universally obsolete (dead qualifiers etc.)
7
- # CONDITIONAL deleted only when project config guarantees they're unused
8
- # (e.g. iOS legacy launch images when storyboard is enabled)
9
- # AGGRESSIVE behind --aggressive flag — strongly defensible but some
10
- # edge cases remain (e.g. ldpi drawables on <1% of devices)
11
- #
12
- # Always prints the plan before acting. Respects --dry-run.
13
-
14
- # Append "path|reason" pair to a named array in a bash-3.2-compatible way.
15
- _add_target() {
16
- local array_name="$1"
17
- local path="$2"
18
- local reason="$3"
19
- eval "$array_name+=(\"\$path|\$reason\")"
20
- }
21
-
22
- cleanup_legacy() {
23
- local project_root="$1"
24
- local project_type="$2"
25
- local aggressive="$3"
26
- local dry_run="$4"
27
-
28
- local tiapp="$project_root/tiapp.xml"
29
- local storyboard_enabled=0
30
- local portrait_only=0
31
-
32
- if [[ -f "$tiapp" ]]; then
33
- grep -q "<enable-launch-screen-storyboard>true" "$tiapp" && storyboard_enabled=1
34
- # Portrait-only heuristic: <orientations> block contains UIInterfaceOrientationPortrait
35
- # for iphone AND no Landscape entries.
36
- if grep -q '<orientations' "$tiapp" && \
37
- ! grep -iqE 'UIInterfaceOrientationLandscape' "$tiapp"; then
38
- portrait_only=1
39
- fi
40
- fi
41
-
42
- local android_images=""
43
- local iphone_dir=""
44
- local android_assets=""
45
- case "$project_type" in
46
- alloy)
47
- android_images="$project_root/app/assets/android/images"
48
- iphone_dir="$project_root/app/assets/iphone"
49
- android_assets="$project_root/app/assets/android"
50
- ;;
51
- classic)
52
- android_images="$project_root/Resources/android/images"
53
- iphone_dir="$project_root/Resources/iphone"
54
- android_assets="$project_root/Resources/android"
55
- ;;
56
- esac
57
-
58
- local has_adaptive=0
59
- [[ -d "$project_root/app/platform/android/res/mipmap-anydpi-v26" ]] && has_adaptive=1
60
- [[ -d "$project_root/platform/android/res/mipmap-anydpi-v26" ]] && has_adaptive=1
61
-
62
- local safe_targets=()
63
- local cond_targets=()
64
- local aggressive_targets=()
65
-
66
- # -------------------------------------------------------------------------
67
- # SAFE
68
- # -------------------------------------------------------------------------
69
-
70
- if [[ -n "$android_images" && -d "$android_images" ]]; then
71
- local d
72
- for d in "$android_images"/res-long-* "$android_images"/res-notlong-*; do
73
- [[ -d "$d" ]] && _add_target safe_targets "$d" "Android long/notlong qualifier (dead since Android 3.0, 2011)"
74
- done
75
- fi
76
-
77
- # -------------------------------------------------------------------------
78
- # CONDITIONAL
79
- # -------------------------------------------------------------------------
80
-
81
- # iOS legacy launch images — only safe with storyboard enabled
82
- if [[ $storyboard_enabled -eq 1 && -n "$iphone_dir" && -d "$iphone_dir" ]]; then
83
- local f
84
- for f in "$iphone_dir"/Default-*.png "$iphone_dir"/Default@2x.png; do
85
- [[ -f "$f" ]] && _add_target cond_targets "$f" "iOS legacy launch image (storyboard enabled → not consulted)"
86
- done
87
- fi
88
-
89
- # Android landscape-variant folders — safe when app is portrait-only
90
- if [[ $portrait_only -eq 1 && -n "$android_images" && -d "$android_images" ]]; then
91
- local d
92
- for d in "$android_images"/res-*-land-* "$android_images"/res-land-*; do
93
- [[ -d "$d" ]] && _add_target cond_targets "$d" "Landscape variant (app is portrait-only)"
94
- done
95
- fi
96
-
97
- # Android legacy splash PNG — safe when adaptive icons are in place
98
- if [[ $has_adaptive -eq 1 && -n "$android_assets" && -f "$android_assets/default.png" ]]; then
99
- _add_target cond_targets "$android_assets/default.png" "Legacy Android splash PNG (adaptive launcher handles splash now)"
100
- fi
101
-
102
- # Android legacy appicon.png — redundant when adaptive icons are in place
103
- if [[ $has_adaptive -eq 1 && -n "$android_assets" && -f "$android_assets/appicon.png" ]]; then
104
- _add_target cond_targets "$android_assets/appicon.png" "Legacy appicon.png (adaptive launcher takes precedence)"
105
- fi
106
-
107
- # -------------------------------------------------------------------------
108
- # AGGRESSIVE
109
- # -------------------------------------------------------------------------
110
-
111
- if [[ $aggressive -eq 1 ]]; then
112
- # ldpi everywhere — <1% of active devices globally in 2026
113
- local d
114
- if [[ -n "$android_images" && -d "$android_images" ]]; then
115
- for d in "$android_images"/res-ldpi "$android_images"/res-*-ldpi; do
116
- [[ -d "$d" ]] && _add_target aggressive_targets "$d" "ldpi density (<1% global market)"
117
- done
118
- fi
119
- for d in "$project_root/app/platform/android/res/drawable-ldpi" \
120
- "$project_root/app/platform/android/res/values-ldpi" \
121
- "$project_root/app/platform/android/res/mipmap-ldpi" \
122
- "$project_root/platform/android/res/drawable-ldpi" \
123
- "$project_root/platform/android/res/values-ldpi" \
124
- "$project_root/platform/android/res/mipmap-ldpi"; do
125
- [[ -d "$d" ]] && _add_target aggressive_targets "$d" "ldpi resource folder (<1% global market)"
126
- done
127
- fi
128
-
129
- # -------------------------------------------------------------------------
130
- # Print plan
131
- # -------------------------------------------------------------------------
132
-
133
- echo
134
- echo "${C_YELLOW}⚠ WARNING${C_RESET} — --cleanup-legacy deletes files permanently."
135
- echo " Recommended: commit your project with git before running without --dry-run."
136
- echo " Provided AS IS under the MIT License. No warranty — use at your own risk."
137
- echo
138
- echo "${C_BOLD}Cleanup plan${C_RESET}"
139
- echo " ${C_DIM}Project: $project_root ($project_type)${C_RESET}"
140
- echo " ${C_DIM}Storyboard: $([[ $storyboard_enabled -eq 1 ]] && echo "enabled" || echo "disabled")${C_RESET}"
141
- echo " ${C_DIM}Orientation: $([[ $portrait_only -eq 1 ]] && echo "portrait-only" || echo "landscape allowed")${C_RESET}"
142
- echo " ${C_DIM}Adaptive icons: $([[ $has_adaptive -eq 1 ]] && echo "present" || echo "not detected")${C_RESET}"
143
- echo " ${C_DIM}Aggressive mode: $([[ $aggressive -eq 1 ]] && echo "on (includes ldpi)" || echo "off")${C_RESET}"
144
-
145
- local total=$(( ${#safe_targets[@]} + ${#cond_targets[@]} + ${#aggressive_targets[@]} ))
146
-
147
- if [[ $total -eq 0 ]]; then
148
- echo
149
- log_ok "No legacy artifacts detected. Project is already clean."
150
- return 0
151
- fi
152
-
153
- # bash 3.2 under `set -u` errors on "${empty_array[@]}" — iterate per-bucket
154
- # with explicit length guards instead of expanding empty arrays into args.
155
- _print_bucket_safe() {
156
- local t path reason size
157
- echo
158
- echo "${C_GREEN}SAFE — universally obsolete${C_RESET}"
159
- for t in "${safe_targets[@]}"; do
160
- path="${t%%|*}"; reason="${t#*|}"
161
- size=$(du -sk "$path" 2>/dev/null | awk '{print $1}')
162
- [[ -z "$size" ]] && size=0
163
- printf " %-6sK %s\n" "$size" "${path#$project_root/}"
164
- printf " ${C_DIM}%s${C_RESET}\n" "$reason"
165
- done
166
- }
167
- _print_bucket_cond() {
168
- local t path reason size
169
- echo
170
- echo "${C_YELLOW}CONDITIONAL — safe given your project config${C_RESET}"
171
- for t in "${cond_targets[@]}"; do
172
- path="${t%%|*}"; reason="${t#*|}"
173
- size=$(du -sk "$path" 2>/dev/null | awk '{print $1}')
174
- [[ -z "$size" ]] && size=0
175
- printf " %-6sK %s\n" "$size" "${path#$project_root/}"
176
- printf " ${C_DIM}%s${C_RESET}\n" "$reason"
177
- done
178
- }
179
- _print_bucket_aggr() {
180
- local t path reason size
181
- echo
182
- echo "${C_RED}AGGRESSIVE — --aggressive enabled${C_RESET}"
183
- for t in "${aggressive_targets[@]}"; do
184
- path="${t%%|*}"; reason="${t#*|}"
185
- size=$(du -sk "$path" 2>/dev/null | awk '{print $1}')
186
- [[ -z "$size" ]] && size=0
187
- printf " %-6sK %s\n" "$size" "${path#$project_root/}"
188
- printf " ${C_DIM}%s${C_RESET}\n" "$reason"
189
- done
190
- }
191
-
192
- [[ ${#safe_targets[@]} -gt 0 ]] && _print_bucket_safe
193
- [[ ${#cond_targets[@]} -gt 0 ]] && _print_bucket_cond
194
- [[ ${#aggressive_targets[@]} -gt 0 ]] && _print_bucket_aggr
195
-
196
- echo
197
- echo " ${C_BOLD}Total: $total item(s) to remove${C_RESET}"
198
-
199
- if [[ $dry_run -eq 1 ]]; then
200
- echo
201
- log_info "Dry-run mode — nothing deleted. Re-run without --dry-run to apply."
202
- return 0
203
- fi
204
-
205
- # -------------------------------------------------------------------------
206
- # Apply
207
- # -------------------------------------------------------------------------
208
-
209
- echo
210
- log_step "Applying cleanup"
211
- local t path
212
- if [[ ${#safe_targets[@]} -gt 0 ]]; then
213
- for t in "${safe_targets[@]}"; do
214
- path="${t%%|*}"; rm -rf "$path"
215
- log_ok "Removed ${path#$project_root/}"
216
- done
217
- fi
218
- if [[ ${#cond_targets[@]} -gt 0 ]]; then
219
- for t in "${cond_targets[@]}"; do
220
- path="${t%%|*}"; rm -rf "$path"
221
- log_ok "Removed ${path#$project_root/}"
222
- done
223
- fi
224
- if [[ ${#aggressive_targets[@]} -gt 0 ]]; then
225
- for t in "${aggressive_targets[@]}"; do
226
- path="${t%%|*}"; rm -rf "$path"
227
- log_ok "Removed ${path#$project_root/}"
228
- done
229
- fi
230
- }