wip: iOS refactor — iMessage extension, sticker browser, app icon assets

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-05 13:43:06 +01:00
parent 20b93df9b0
commit b5d50c45da
8 changed files with 410 additions and 23 deletions

146
PLAN.md Normal file
View File

@@ -0,0 +1,146 @@
# Plan: standalone iOS app + invisible iMessage sticker extension
## Goal
- **iOS app** on the home screen: paste Telegram URL, download packs, preview stickers, toggle individual stickers on/off
- **iMessage extension** is invisible — no custom UI. It serves stickers via `MSStickerBrowserViewController` so they appear in the system sticker picker (emoji keyboard → stickers tab on iOS 17+)
- Data flows from the iOS app → shared App Group container → extension reads it
## Why the 404 happens
The backend always calls `manager.getPack()` (Telegram Bot API) before checking its cache. If the bot token is invalid or Telegram is unreachable, it returns `None` → 404, even when sticker files exist on disk. This is a **backend bug** — fix separately. The iOS URL construction is correct.
## Architecture
```
┌─────────────────────────┐ ┌──────────────────────────┐
│ iOS App (home screen) │ │ iMessage Extension │
│ │ │ (no custom UI) │
│ - Paste URL │ │ │
│ - Download packs │ ──► │ MSStickerBrowserVC │
│ - Preview stickers │ │ reads shared container │
│ - Toggle on/off │ │ → stickers appear in │
│ - Delete packs │ │ system sticker picker │
└─────────────────────────┘ └──────────────────────────┘
│ │
└──────── App Group ───────────────┘
UserDefaults(suiteName:)
shared Caches/stickers/
```
## File layout after changes
```
StickerCloner/
├── Shared/ ← NEW synced folder, both targets
│ ├── Models.swift moved + updated
│ └── StickerStore.swift moved + rewritten (no Messages import)
├── StickerCloner/ ← iOS app target (auto-synced)
│ ├── Assets.xcassets/ existing
│ ├── StickerClonerApp.swift NEW — SwiftUI @main entry
│ ├── PackListView.swift NEW — list of packs + URL input
│ ├── PackDetailView.swift NEW — sticker grid with toggles
│ └── StickerClonerAPI.swift moved from extension (only app needs it)
├── StickerCloner MessagesExtension/ ← extension target (auto-synced)
│ ├── Assets.xcassets/ existing
│ ├── Base.lproj/MainInterface.storyboard existing
│ ├── Info.plist existing
│ ├── MessagesViewController.swift rewritten (minimal)
│ └── StickerBrowserViewController.swift rewritten (reads shared data)
└── StickerCloner.xcodeproj/
```
## Project file (pbxproj) changes
1. **Change main target product type**: `com.apple.product-type.application.messages``com.apple.product-type.application`
2. **Add Sources build phase** to main target (empty — synced groups handle inclusion)
3. **Add `Shared/` PBXFileSystemSynchronizedRootGroup** to both targets' `fileSystemSynchronizedGroups`
4. **Add App Group entitlements** via `CODE_SIGN_ENTITLEMENTS` build setting on both targets
## Data model changes
### Models.swift (Shared/)
```swift
// API types (unchanged)
struct StickerSetResponse: Codable { ... }
struct StickerResponse: Codable { ... }
// Local persistence now tracks enabled stickers
struct SavedPack: Codable {
let name: String
let title: String
let stickerIds: [String] // all sticker IDs in order
var enabledStickerIds: Set<String> // subset the user wants
}
```
### StickerStore.swift (Shared/)
- Uses `UserDefaults(suiteName: "group.de.felixfoertsch.StickerCloner")` instead of `.standard`
- Uses `FileManager.containerURL(forSecurityApplicationGroupIdentifier:)` instead of caches directory
- `stickerFileURLs(for pack:)` returns `[URL]` (not MSSticker — no Messages dependency)
- Returns only enabled stickers
### StickerClonerAPI.swift (iOS app only)
Stays in the app target — the extension never calls the API. Unchanged except move location.
## iOS App views
### StickerClonerApp.swift
- `@main` SwiftUI app
- Single `PackListView` as root in NavigationStack
### PackListView.swift
- Text field + "Add" button to paste Telegram URL or pack name
- List of saved packs with swipe-to-delete
- Navigation link to `PackDetailView` per pack
- Loading/error states during download
### PackDetailView.swift
- Grid of sticker thumbnails (loaded from local PNG files)
- Tap to toggle enabled/disabled (dimmed + checkmark overlay)
- "Select All" / "Deselect All" toolbar buttons
- Pack title in navigation bar
## Extension changes
### MessagesViewController.swift
- Hosts `StickerBrowserViewController` as child, fills entire view
- No expanded mode handling, no SwiftUI views
- Reloads stickers on `willBecomeActive`
### StickerBrowserViewController.swift
- Loads enabled sticker file URLs from `StickerStore`
- Creates `MSSticker` from each URL
- Standard `MSStickerBrowserViewController` data source
## Entitlements
Both targets get identical entitlements:
```xml
<key>com.apple.security.application-groups</key>
<array>
<string>group.de.felixfoertsch.StickerCloner</string>
</array>
```
## Commits
1. `convert to standalone iOS app, add shared folder, app group entitlements`
2. `add iOS app ui: pack list, pack detail with sticker filtering`
3. `simplify iMessage extension to read-only sticker browser`
## Verification
1. Build in Xcode → no compile errors for both targets
2. iOS app appears on home screen
3. Paste pack URL → downloads (once backend 404 is fixed)
4. Tap pack → see sticker grid, toggle individual stickers
5. Open Messages → stickers appear in sticker picker
6. Only enabled stickers shown
7. Kill app, reopen → state persists via App Group