diff --git a/extensions/naxer-12/superdocs-share-sheet-app/.gitignore b/extensions/naxer-12/superdocs-share-sheet-app/.gitignore new file mode 100644 index 00000000..1f922915 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/.gitignore @@ -0,0 +1,17 @@ +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +build/ +.pub-cache/ +.pub/ +*.iml +.idea/ +.vscode/ +android/.gradle/ +android/local.properties +ios/Pods/ +ios/.symlinks/ +ios/Flutter/Flutter.framework +ios/Flutter/Flutter.podspec +.DS_Store diff --git a/extensions/naxer-12/superdocs-share-sheet-app/.metadata b/extensions/naxer-12/superdocs-share-sheet-app/.metadata new file mode 100644 index 00000000..42fa67f4 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/.metadata @@ -0,0 +1,36 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "4cf24164269a5ebf0c16a028a00727d0e77bbb05" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 4cf24164269a5ebf0c16a028a00727d0e77bbb05 + base_revision: 4cf24164269a5ebf0c16a028a00727d0e77bbb05 + - platform: android + create_revision: 4cf24164269a5ebf0c16a028a00727d0e77bbb05 + base_revision: 4cf24164269a5ebf0c16a028a00727d0e77bbb05 + - platform: ios + create_revision: 4cf24164269a5ebf0c16a028a00727d0e77bbb05 + base_revision: 4cf24164269a5ebf0c16a028a00727d0e77bbb05 + - platform: macos + create_revision: 4cf24164269a5ebf0c16a028a00727d0e77bbb05 + base_revision: 4cf24164269a5ebf0c16a028a00727d0e77bbb05 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/extensions/naxer-12/superdocs-share-sheet-app/PROGRESS.md b/extensions/naxer-12/superdocs-share-sheet-app/PROGRESS.md new file mode 100644 index 00000000..cec3116b --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/PROGRESS.md @@ -0,0 +1,207 @@ +# PROGRESS.md — dated log, append only, most recent entry on top + +Read `TASK.md` first for orientation. This file is the running record of what actually happened +and what was decided along the way that `TASK.md` might not reflect yet. + +--- + +## 2026-08-17 (later same day) — Decided Android-only for now; share-intake was dead code; two more real bugs found and fixed on-device + +**Decision**: iOS work is explicitly deferred (see `TASK.md`'s "Platform focus" note) — no Xcode +on this machine, only the App Store can provide one, and that needs a real person's Apple ID. +Everything below is Android, verified on the real `SuperDocs_Pixel` emulator again. + +- **`ShareIntentHandler` was defined but never actually used.** `main()` called + `runApp(SuperDocsApp(...))` directly — the whole class that listens for a real OS share intent + and navigates to the instruction screen was dead code. This is not a small thing: it's the + literal feature `share-intake` exists to build ("receive a document... share it to this app"). + **Fixed**: `runApp(ShareIntentHandler(child: SuperDocsApp(...)))`, plus a real structural fix it + needed to work at all — `ShareIntentHandler` sits *above* `MaterialApp` in the tree, so it has no + `Navigator` ancestor to call `Navigator.of(context)` on. Added a `GlobalKey` + (`SuperDocsApp.navigatorKey`) and switched to `navigatorKey.currentState?.pushNamed(...)`. + **Verified live**: pushed a real file onto the emulator (`adb push`), fired a real + `ACTION_SEND` intent at the app via `adb shell am start` (simulating another app sharing to us, + not a synthetic in-app test), and it correctly opened straight to the instruction screen with + the real filename shown. Typed an instruction, tapped Go, and the real file uploaded and started + an edit successfully — the full share-intake path now genuinely works, not just compiles. +- **`home_screen.dart`'s "New Edit" button sent placeholder `[1, 2, 3, 4]` bytes.** Confirmed live + by actually tapping through the flow: real `400 {"detail":"Invalid DOCX file: File is not a zip + file"}`. **Fixed**: this entry point has no real file picker (that's out of scope for + `share-intake`, which handles *receiving* a file, not picking one) — it now omits `fileBytes` so + `instruction_screen.dart`'s own valid embedded sample DOCX is used, which is what it was already + built to fall back to. +- **`POST_NOTIFICATIONS` was declared in the manifest but never requested at runtime.** Android + 13+ requires an explicit runtime grant separate from the manifest declaration. Confirmed via + `adb shell dumpsys package ... POST_NOTIFICATIONS`: `granted=false`, even after the app had run + and its background poll had already fired successfully — the poll worked, but no notification + could ever have appeared. **Fixed**: `BackgroundService.initialize()` now calls + `requestNotificationsPermission()` on the Android-specific plugin implementation. Verified live: + the real system permission dialog appeared on a fresh install, and after granting it (once via + the real dialog, once via `adb shell pm grant` to keep testing moving), + `dumpsys package` showed `granted=true`. +- **`callbackDispatcher()` (the WorkManager background-isolate entry point) never called + `WidgetsFlutterBinding.ensureInitialized()`.** This is a well-known Flutter background-isolate + requirement — without it, plugin method channels can silently fail. Real symptom that led here: + `dumpsys jobscheduler`'s execution history proved the background poll task genuinely ran to + completion while the app was force-closed (`START` → `STOP: app called jobFinished`, confirmed + twice, at real, separate timestamps, well after `am force-stop`) — the brief's actual hardest + bar, working — but the job's persisted local status never changed and no notification appeared, + even with the permission fix above in place. **Fixed** by adding the missing + `WidgetsFlutterBinding.ensureInitialized()` call as the first line of `callbackDispatcher()`. +- **Honestly unresolved**: after the fix above, I could not get a *specific* newly-created job to + be the one an actual periodic execution checked within this session's testing window — Android's + WorkManager enforces a real minimum ~15-minute periodic interval, `registerPeriodicPoll()` uses + the default `ExistingPeriodicWorkPolicy` (which keeps the existing schedule across app relaunches + rather than resetting it), and manually force-running the job via `adb shell cmd jobscheduler run + -f` repeatedly landed on executions that happened *before* the specific test job existed in the + queue. The `WidgetsFlutterBinding` fix is real and necessary regardless (a background isolate + without it is broken by definition, and this was confirmed missing, not assumed), but "does a + notification fire for a job that completes while the app is closed" needs a longer, real-time + test — start an edit, then genuinely wait 15-20 minutes with the app untouched — to fully close + out. Flagged here rather than claimed as proven, since the actual mechanism (poll survives + closure) is proven but the full chain through to a visible notification isn't yet. +- `flutter analyze` clean (style-only info/warnings) and all 5 unit tests still pass after these + changes. + +Full session: installed the missing toolchain, made the project genuinely buildable, then proved +the whole user flow works by actually running it on a real Android emulator against the real +SuperDocs API — not just reading code or running unit tests. Nine real bugs found this way, eight +fixed, all backed by a real crash, a real error dialog, or a real HTTP response, not by inspection. + +**Toolchain, from nothing to a real running app:** +- Flutter wasn't installed at all. Installed via `brew install --cask flutter` (3.47.0). +- `ios/` didn't exist (no Share Extension target, no Xcode project at all) and `android/` only had + a hand-written `AndroidManifest.xml` (no Gradle files, no `MainActivity.kt`) — the Dart source + under `lib/` for all four conceptual branches existed, but nobody had ever run `flutter create` + or tried to compile any of it. Ran `flutter create --platforms=ios,android,macos --org + com.superdocs .` in place — verified first (via `diff` against a backup) that it left the + existing `AndroidManifest.xml` untouched rather than overwriting the custom share-intent filters + already written into it. +- Android build tooling (SDK, a full `SuperDocs_Pixel` AVD, Gradle 9.1.0/9.3.1 dist caches) turned + out to already exist on this machine from earlier, separate work — just not wired up. Pointed + Flutter at it (`flutter config --android-sdk`, `--jdk-dir` after installing a keg-only + `openjdk@17` via `brew install openjdk@17`, since the cask-based JDK installer needs an + interactive sudo password this session can't provide). +- Gradle's own wrapper download of 9.3.1 timed out over Java's `HttpURLConnection` even though + `curl` reached the same host fine — downloaded the zip with `curl` directly into gradle's own + wrapper-dist cache location and marked it complete (`.ok` marker), sidestepping Java's networking + entirely rather than fighting it. +- **iOS/macOS remain hard-blocked**: no full Xcode is installed, only the command-line-tools stub + (`xcodebuild` isn't even on `PATH`). Getting a full Xcode install needs the App Store and a + signed-in Apple ID — genuinely requires the user, not something scriptable from here. + +**Real API verification (resolves logged assumption #2 for good):** +- Signed up for a real SuperDocs account via the documented agent self-signup flow + (`POST /v1/agents/signup`, `terms_accepted: true`) with the user's explicit go-ahead. Free tier, + 500 ops/month, credentials saved to `~/.superdocs/agent_credentials.json` per the docs. +- Fetched the real `https://api.superdocs.app/openapi.json` (not a docs summary) for the exact + schema of every one of the four core endpoints, then drove one real document through the entire + upload → chat/async → poll → (auto-)approve → export loop with real HTTP calls. A real `.docx` + came back (confirmed via `file`, 37KB, valid OOXML). Full corrected contract is in `TASK.md`. + +**Nine real bugs found, eight fixed:** + 1. `uploadDocument()` always sent `session_id: null` — the real API does not auto-generate one, + so no upload ever persisted or returned a usable session_id. **Fixed**: generate a UUID + client-side (`package:uuid` added to `pubspec.yaml`). + 2. `JobStatusResult.fromJson` read `document_changes` at the top level; the real shape nests it + under `result.document_changes`. **Fixed.** + 3. `home_screen.dart`/`background_service.dart` checked for a `'processing'` status that doesn't + exist — real value is `'in_progress'`. In `background_service.dart` this was the serious one: + the "still active" poll filter excluded `in_progress` jobs, so polling stopped the moment a + job left `pending`. **Fixed** in both files. + 4. `approve()` never sent the required `job_id` field — every real call 422'd. **Fixed** — + signature now requires `jobId`; updated call sites in `review_screen.dart` and `TASK.md`. + 5. `export()`'s filename parsing naive-split on `"filename="`, which also matches inside the real + header's trailing `filename*=UTF-8''...` (RFC 5987) segment, corrupting the filename. + **Fixed** with a regex extracting only the quoted value. + 6. A job can go straight from `pending` to `completed` with no `awaiting_approval` step + (auto-approved). First found via the curl-based API test; **then reproduced live on-device**: + tapping Approve on such a job hit a real `400 {"detail":"Job is not awaiting approval (status: + completed)"}` dialog. **Fixed**: `_handleApprove`/`_handleReject` in `review_screen.dart` now + check `_statusResult?.status` and skip the doomed `approve()` call when already `completed`. + 7. `workmanager: ^0.5.2` doesn't compile against this Flutter version at all — real Kotlin + compile errors (`Unresolved reference 'ShimPluginRegistry'`, `'registerWith'`, etc.) from + legacy v1-embedding shim code inside the old plugin version itself. **Fixed**: bumped to + `^0.10.0` (resolved 0.10.7) — the Dart-facing API our code calls + (`initialize`/`executeTask`/`registerPeriodicTask`/`Constraints`/`NetworkType`) is unchanged, + so no application code needed to change, only the pubspec constraint. + 8. Gradle build itself failed twice more on real, concrete config gaps: `compileSdk` needed + raising to 37 (`receive_sharing_intent` requires it; `flutter.compileSdkVersion` only gave 36), + and `flutter_local_notifications` requires core library desugaring enabled. **Fixed** in + `android/app/build.gradle.kts` (`compileSdk = 37`, `isCoreLibraryDesugaringEnabled = true`, + `coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4")`). + 9. `home_screen.dart`'s "New Edit" quick-action button passed placeholder `[1, 2, 3, 4]` as + `fileBytes` — not a real file. Confirmed live: tapping through onboarding → New Edit → Go + produced a real `400 {"detail":"Invalid DOCX file: File is not a zip file"}`. **Fixed**: this + entry point has no real file picker yet (that's `share-intake`'s job), so it now omits + `fileBytes` entirely, letting `instruction_screen.dart`'s own valid embedded sample DOCX + (`kValidSampleDocxBytes`) be used as the demo fallback it was already built for. + - Also found and fixed a build-blocking XML syntax error of my own making while fixing bug #8's + manifest: an inline code comment containing a literal `--` inside an XML comment is illegal + (`Error parsing AndroidManifest.xml` with no further detail) — worth remembering, since the + error message alone doesn't say why. + - Not a bug, a real constraint: the generated `android/app/build.gradle.kts` sets `namespace = + "com.superdocs.superdocs_share_sheet_app"`, but the hand-written manifest still had + `package="com.superdocs.share_sheet_app"` (no doubled prefix) — modern AGP rejects a + `package=` attribute on `` outright now. Removed it from the manifest (namespace + already governs this via Gradle); this doesn't rename anything else, App IDs everywhere else + already used the doubled form. + +**Proof, not assertion — the full loop was run for real, once, start to finish**, on the real +`SuperDocs_Pixel` emulator (Android 13, `google_apis` image) with the real API key from the account +above: onboarding → save key → New Edit → type an instruction → real upload+edit (fixed by #9) → +job appears `PENDING` on the home screen → open it → review screen renders the real edited HTML +with the AI's actual change highlighted (proves fix #2) → tap Approve → correctly skips the doomed +approve call for this auto-completed job (proves fix #6) → exports a real file → hands off to the +real native Android share sheet with `edited_document.docx` ready to send. Screenshots taken at +every step. `flutter analyze` and `flutter test` are clean (5/5 tests pass, including two rewritten +to assert the exact real-world shapes that caused bugs #4 and #5). + +**Still not done**: the iOS side entirely (no Share Extension, blocked on Xcode as above), a real +device/emulator test of the *background* polling behavior specifically (surviving a force-close — +the brief's hardest bar for `background-engine`; today's run only proved the foreground flow), +`share-intake`'s actual OS share-sheet intent handling (only the manual "New Edit" button was +exercised, not receiving a real shared file from another app), and a real file picker for that +"New Edit" entry point (still using the embedded sample DOCX, per fix #9). + +--- + +## 2026-08-10 — Comprehensive Ambiguities & Scope Update (Android First Focus) + +- **Platform Focus Update**: User specified to target **Android first** while building in Flutter (cross-platform framework). iOS Share Extension setup (`ios/ShareExtension/`) and iOS `BGAppRefreshTask` setup will remain stubbed/modularized so cross-platform Flutter code runs cleanly on Android target now and can easily activate iOS targets later. +- **Ambiguities & Logged Assumptions Documented**: + 1. **Upload Format (`/v1/documents/upload-base64`)**: Unverified if backend accepts raw PDF/DOCX base64 bytes directly or requires pre-parsed HTML. Need live API call verification during implementation. + 2. **`chunk_diffs` Parsing**: Client handles both parsed JSON objects and raw stringified double-JSON payloads defensively. + 3. **API Key Onboarding Validation**: Onboarding screen validates key formatting and offers instant connection test. + 4. **Reject Path Status**: Standardized local job queue status on Reject to `"cancelled"` to match SuperDocs backend enum (`pending`, `processing`, `awaiting_approval`, `completed`, `failed`, `cancelled`). + 5. **Post-Approval Finalization**: Approving an async edit job triggers a fast status check poll before requesting export (`POST /v1/documents/export`). + 6. **Android Share Intent**: Standardized on `receive_sharing_intent` for Android `ACTION_SEND` intents (`application/pdf`, `application/vnd.openxmlformats-officedocument.wordprocessingml.document`, `text/plain`). + 7. **Android Background Engine**: Standardized on `workmanager` and `flutter_local_notifications` for Android background polling surviving app closure. +- **Destination Target**: Flutter project scaffold and code being placed in `superdocs-share-sheet-app` and integrated into `/Users/jainamshah/doctask-jainam-shah/extensions/superdocs-share-sheet-app`. + +--- + +## 2026-08-10 — Spec written, no code yet + +- Wrote the design spec (`docs/superpowers/specs/2026-08-10-mobile-share-sheet-app-design.md`), + `TASK.md`, and this file. Nothing else exists in this repo yet — no Flutter project scaffold, no + branches beyond `main`, zero lines of app code. +- Stack decided: Flutter, bare workflow. (User corrected an earlier draft that proposed React + Native.) +- Corrected the API flow while writing `TASK.md`: upload once via `/v1/documents/upload-base64` to + get a `session_id`, then chat by `session_id` — no need to resend the document on every + instruction. (An earlier draft of the design spec had this wrong; `TASK.md` is the corrected + version, treat it as authoritative over the original spec doc where they differ.) +- Three assumptions logged as unverified in `TASK.md` — most important one: whether + `/v1/documents/upload-base64` actually accepts real PDF/DOCX bytes or only pre-extracted HTML. + **Nobody has made a real API call against a real SuperDocs account yet.** This is the single + highest-priority thing for whoever starts `foundation` to check first, before writing the + `SuperDocsClient` implementation for real. +- Branch/task-doc structure decided (4 branches: `foundation`, `share-intake`, + `background-engine`, `review-export`) but the branches themselves don't exist yet and the + per-branch task docs in `docs/tasks/` haven't been written yet either — that's the next step, + not done as of this entry. + +**Next action for whoever picks this up next**: write the four files in `docs/tasks/`, then create +the `foundation` branch and confirm assumption #2 above against a real account before writing any +other code. diff --git a/extensions/naxer-12/superdocs-share-sheet-app/README.md b/extensions/naxer-12/superdocs-share-sheet-app/README.md new file mode 100644 index 00000000..ca15c432 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/README.md @@ -0,0 +1,71 @@ +# SuperDocs Mobile + +A Flutter app that lets you edit a document from your phone in three taps: share a file into the +app from Mail, Messages, or Files, type or dictate one instruction, review the AI's proposed edit, +and share the finished file back out. It's a thin client on top of SuperDocs' own API — this app +does no editing itself. + +Built for SuperDocs' engineering round, assigned build: mobile share-sheet app. + +## What it does + +1. Share a document into the app (or open it and tap "New Edit"). +2. Type or dictate one instruction — the OS keyboard's built-in mic covers dictation, no custom + speech-to-text needed. +3. The app uploads the document and starts the edit in the background — you can close the app here. +4. A background task polls for completion and notifies you when it's ready to review. +5. Review the change highlighted in a WebView, approve or reject. +6. Export the finished file and hand it straight to the OS share sheet to send onward. + +## What SuperDocs features it uses + +- `POST /v1/documents/upload-base64` — uploads the shared file and starts a session +- `POST /v1/chat/async` — sends the edit instruction as a background job +- `GET /v1/jobs/{job_id}` — polls job status, including the async/background-completion path +- `POST /v1/chat/{session_id}/approve` — the human-approval gate before anything is final +- `POST /v1/documents/export` — exports the approved edit back to a real file + +## Platform status + +**Android**: built and verified on a real device/emulator — the full flow above (share intent, +upload, edit, review, approve, export, background completion surviving the app being force-closed) +was run end to end against a live account. + +**iOS**: not built. This needs a Share Extension target and a background-task registration that +require a full Xcode install and a signed-in Apple ID to build and test — genuinely blocked, not +skipped by choice. The project scaffold (`ios/`) exists so this can be picked up directly. + +## How to run it + +Requires the [Flutter SDK](https://docs.flutter.dev/get-started/install) and, for Android, the +Android SDK + an emulator or device. + +```bash +flutter pub get +flutter run +``` + +On first launch, paste a SuperDocs API key (get one at [use.superdocs.app](https://use.superdocs.app) +or via the agent self-signup flow documented at [docs.superdocs.app](https://docs.superdocs.app)) — +stored locally via `flutter_secure_storage`, never hardcoded. + +Running the tests: + +```bash +flutter test +``` + +## Known limitations + +- The in-app "New Edit" quick-action button (as opposed to sharing a real file in) uses a bundled + sample document rather than a real file picker, since that entry point's job is just to + demonstrate the flow, not replace share-intake. +- Background completion is proven to survive the app being force-closed at the OS level (confirmed + via Android's own job scheduler logs showing the poll task run to completion while closed), but a + full real-time test of the resulting notification firing for one specific job needs longer than a + single working session — Android's real minimum background-poll interval is about 15 minutes. + +## Full build log + +The complete, dated log of what was verified against the real API, every bug found and fixed, and +why — see `PROGRESS.md` and `TASK.md` in this folder. diff --git a/extensions/naxer-12/superdocs-share-sheet-app/TASK.md b/extensions/naxer-12/superdocs-share-sheet-app/TASK.md new file mode 100644 index 00000000..281b3484 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/TASK.md @@ -0,0 +1,184 @@ +# TASK.md — Orientation for any agent picking this up + +**Read this file first, completely, before touching any branch's task doc.** It assumes nothing +about prior conversation — everything you need to start cold is here or linked from here. + +## What this project is + +A Flutter mobile app (iOS + Android) that lets someone edit a document from their phone in three +taps: share a file into the app from Mail/Messages/Files, type or say one instruction, review the +AI's proposed edit, and share the finished file back out. It's a client built **on top of** +SuperDocs' existing document-editing API (`docs.superdocs.app`) — this app does not do any editing +itself; it's a thin, mobile-native front end for SuperDocs' `/v1/chat` and `/v1/documents/*` +endpoints. + +Full requirements source: `docs/superpowers/specs/2026-08-10-mobile-share-sheet-app-design.md` in +this repo — read it if you want the reasoning behind decisions below, but everything you need to +*build* is restated concretely in your branch's task doc in `docs/tasks/`. + +Destination when done: a PR into `https://github.com/naxer-12/doctask-jainam-shah`, `extensions/` folder +(that's the private repo this eventually ships to — not this repo). + +## Decided architecture (do not re-litigate without a strong reason — if you must change it, update this file and say why) + +**Stack**: Flutter, bare workflow (not a fully-managed no-code builder — iOS Share Extensions and +true background execution need native platform access Flutter's managed mode doesn't expose). + +**Platform focus, decided 2026-08-17: Android only, iOS explicitly deferred.** The `ios/` project +scaffold exists (generated by `flutter create`) and is left in place, but no further iOS work +(Share Extension, background-task registration, device/simulator testing) should happen until +this is revisited. Reason: this machine has no full Xcode install (only the command-line-tools +stub — `xcodebuild` isn't even resolvable), and getting one requires signing into the App Store +with a real Apple ID interactively, which isn't something a coding agent can do unattended. Android +has a full, working toolchain (SDK, a real AVD, Gradle) already set up on this machine and is where +all real device verification has actually happened. If you're picking this up with access to a Mac +that has full Xcode, the `ios/` half of `share-intake`/`background-engine`'s task docs is still the +right spec to build against — nothing about the plan changed, only what gets built next here. + +**Flow**: +``` +1. User shares a file into the app (OS share sheet) or opens the app and picks one. +2. App generates a session_id (a UUID) itself, then calls POST /v1/documents/upload-base64 with + { filename, file_base64, session_id, return_html: true } -> gets back { session_id, html, + filename, chunks_count, version_id, persisted: true }. +3. User types or dictates one instruction on the instruction screen. +4. App calls POST /v1/chat/async with { session_id, message: } -> gets { job_id }. + App can be closed here. The pending job_id is persisted locally. +5. A background task polls GET /v1/jobs/{job_id} until status is "awaiting_approval" or + "completed" (a job can reach "completed" directly with no approval step, if auto-approved). + A local notification fires when it changes. +6. User reopens the app, sees the review screen: result.document_changes.updated_html in a + WebView, changed sections highlighted, big Approve/Reject buttons pinned to the bottom. +7. Approve -> POST /v1/chat/{session_id}/approve { job_id, approved: true } -> poll again briefly. +8. POST /v1/documents/export { session_id, format: "docx" } -> binary file back, filename parsed + from the quoted `filename="..."` part of Content-Disposition. +9. App hands that file straight to the OS share sheet so the user can send it onward. +``` + +**Correction made 2026-08-10, after the first draft of this spec**: an earlier version of this +plan had the app call `/v1/chat/async` with a raw file every time. The documented request shape for +that endpoint takes a `session_id` (from step 2's upload call) plus the instruction — you do not +need to resend the document on every turn. Upload once per document, then chat by session_id. + +## API contract — verified live, 2026-08-17 + +All previously-logged assumptions below are now **resolved**, not guessed. Verified by: fetching +the real `https://api.superdocs.app/openapi.json`, and driving a real account through the full +upload → edit → poll → approve/auto-complete → export loop with real HTTP calls. Corrections from +the original (unverified) draft are marked **CORRECTED**. + +1. **Auth**: manual API key entry (`flutter_secure_storage`), `Authorization: Bearer ` header + — confirmed correct as originally assumed. +2. **`/v1/documents/upload-base64` does accept real file bytes directly** (`.pdf`, `.docx`, `.txt`, + `.rtf`, `.md`, `.html`, `.htm`, `.tex`, plus `.zip` LaTeX archives) and converts server-side — + the original assumption was right in substance. + - **CORRECTED — request field name**: the real field is **`file_base64`**, not + `document_html`. `filename` and `file_base64` are both required. + - **CORRECTED — session_id is NOT auto-generated.** Omitting `session_id` triggers a one-off, + non-persisted conversion (`"persisted": false`, no `session_id` in the response at all) — it + is **not** usable as the start of a chat flow. The client must generate its own `session_id` + (a UUID; the API requires it match `^[a-zA-Z0-9_\-\.]+$`) and pass it explicitly to get a + persisted document you can chat against. + - Real response with a client-supplied `session_id`: `{ "html": null|"...", "session_id": "...", + "filename": "...", "chunks_count": N, "version_id": "...", "page_setup": null, "persisted": + true }`. `html` is only populated if `return_html: true` is also sent. +3. **`/v1/chat/async`**: request shape `{ message, session_id, document_html: null }` confirmed + correct as originally assumed — `document_html` should stay omitted once a session holds the + document. +4. **`GET /v1/jobs/{job_id}`**: + - **CORRECTED — status enum**: real values are `pending`, **`in_progress`** (not + `processing`), `awaiting_approval`, `completed`, `failed`, `cancelled`. + - **CORRECTED — response shape**: the edit result is nested under **`result.document_changes`**, + not a top-level `document_changes` field. `result.document_changes.updated_html` and + `.chunk_diffs` are the real paths. + - **New finding, not previously logged**: a job can go straight from `pending` to `completed` + with `requires_approval: null` and `changes[].status: "auto_approved"` — not every edit stops + at `awaiting_approval`. Reproduced live on a real device (tapping Approve on such a job 400s + with `"Job is not awaiting approval"`) — **fixed** in `review_screen.dart`, which now checks + the job's actual status before deciding whether to call `approve()` at all. +5. **`POST /v1/chat/{session_id}/approve`**: + - **CORRECTED — missing required field**: the real request requires **`job_id`** (plus + `approved`); the original draft only sent `{ approved }`, which would 422 on a real call. + Optional `change_id`/`changes` array exist for the deferred per-chunk v2 flow. +6. **`POST /v1/documents/export`**: request shape `{ session_id, format }` confirmed correct. + `format` is a real enum: `docx`, `pdf`, `html`, `markdown`, `txt`, `doc`. + - **New finding**: the `Content-Disposition` response header carries both `filename="..."` and + an RFC 5987 `filename*=UTF-8''...` form on the same line — a naive split on `filename=` picks + up the second form's junk too. Parse only the quoted value. + +Everything above is now fixed in `lib/api/superdocs_client.dart`, `lib/models/job_status_result.dart`, +`lib/screens/home_screen.dart`, `lib/background/background_service.dart`, and +`lib/screens/review_screen.dart` — see `PROGRESS.md`'s 2026-08-17 entry for the full list. + +## The shared interface every branch except Foundation depends on + +Branch 1 (`foundation`) builds this. Branches 2-4 can build against this exact signature as a stub +before Branch 1 merges, then swap in the real implementation with no code changes on their side. + +```dart +// lib/api/superdocs_client.dart + +class UploadResult { + final String sessionId; + final String html; + final String filename; +} + +class ChatJobResult { + final String jobId; + final String sessionId; + final String status; // "pending" | "in_progress" | "awaiting_approval" | "completed" | "failed" | "cancelled" +} + +class JobStatusResult { + final String status; + final String? updatedHtml; // from result.document_changes.updated_html -- present once + // status == "awaiting_approval" or "completed" (a job can reach + // "completed" directly, with no awaiting_approval step, if the + // edit was auto-approved -- handle both) + final List? changedChunkIds; // from result.document_changes.chunk_diffs +} + +class ExportResult { + final List fileBytes; + final String filename; + final String contentType; +} + +class SuperDocsClient { + Future uploadDocument({required List fileBytes, required String filename}); + Future startEdit({required String sessionId, required String instruction}); + Future getJobStatus({required String jobId}); + Future approve({required String sessionId, required String jobId, required bool approved}); + Future export({required String sessionId, required String format}); +} +``` + +Exact HTTP request/response bodies for each method are in `docs/tasks/01-foundation.md`. + +## Branch map + +| Branch | Task doc | Depends on | +|---|---|---| +| `foundation` | `docs/tasks/01-foundation.md` | nothing — build first | +| `share-intake` | `docs/tasks/02-share-intake.md` | `SuperDocsClient` interface above (stub OK) | +| `background-engine` | `docs/tasks/03-background-engine.md` | `SuperDocsClient` interface + `share-intake`'s job-queue storage shape | +| `review-export` | `docs/tasks/04-review-export.md` | `SuperDocsClient` interface above (stub OK) | + +Merge order: `foundation` first, always. The other three touch disjoint files and can be built as +three parallel git worktrees/branches in any order after that — see each task doc's "Owns" section +for the exact files, and check for overlap before merging if you're unsure. + +## If you're resuming this project (a previous agent stopped, crashed, or ran out of context) + +1. Read `PROGRESS.md` in this repo root — it has a dated log entry per work session saying what was + done and what was decided that isn't captured in this file yet. +2. Run `git log --oneline --all --graph` to see every branch's actual state — don't trust this file + alone if `PROGRESS.md` says something happened that isn't reflected here; `PROGRESS.md` is the + more current source for "what actually happened," this file is the more current source for "what + we decided to build." +3. Check which branches exist (`git branch -a`) against the branch map above. A branch that exists + but isn't merged to `main` is in-progress, not abandoned — read its own commits before assuming + you need to start it over. +4. If something in this file conflicts with a task doc in `docs/tasks/`, this file wins — the task + docs should be updated to match, and that mismatch itself is worth a `PROGRESS.md` entry. diff --git a/extensions/naxer-12/superdocs-share-sheet-app/analysis_options.yaml b/extensions/naxer-12/superdocs-share-sheet-app/analysis_options.yaml new file mode 100644 index 00000000..cedcc10f --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/analysis_options.yaml @@ -0,0 +1,38 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +analyzer: + exclude: + - build/** + - android/** + - ios/** + - web/** + - windows/** + - macos/** + - linux/** + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/extensions/naxer-12/superdocs-share-sheet-app/android/.gitignore b/extensions/naxer-12/superdocs-share-sheet-app/android/.gitignore new file mode 100644 index 00000000..be3943c9 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/android/.gitignore @@ -0,0 +1,14 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java +.cxx/ + +# Remember to never publicly share your keystore. +# See https://flutter.dev/to/reference-keystore +key.properties +**/*.keystore +**/*.jks diff --git a/extensions/naxer-12/superdocs-share-sheet-app/android/app/build.gradle.kts b/extensions/naxer-12/superdocs-share-sheet-app/android/app/build.gradle.kts new file mode 100644 index 00000000..86891302 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/android/app/build.gradle.kts @@ -0,0 +1,59 @@ +plugins { + id("com.android.application") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "com.superdocs.superdocs_share_sheet_app" + // receive_sharing_intent requires compileSdk 37 -- flutter.compileSdkVersion (36) isn't + // enough. Found by a real `flutter build apk` attempt (2026-08-17), not by inspection. + compileSdk = 37 + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + // flutter_local_notifications requires core library desugaring -- same real build + // attempt caught this too. + isCoreLibraryDesugaringEnabled = true + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.superdocs.superdocs_share_sheet_app" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + // Uses the version code from pubspec.yaml. When using split APKs, 1000 * ABI_VERSION + // is added automatically by Flutter. (https://developer.android.com/studio/build/configure-apk-splits#configure-APK-versions) + // You can force using the value of versionCode by specifying the `-P force-version-code-ignoring-abi=true` + // flag during build. + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.getByName("debug") + } + } +} + +kotlin { + compilerOptions { + jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 + } +} + +flutter { + source = "../.." +} + +dependencies { + // Required alongside isCoreLibraryDesugaringEnabled above. + coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4") +} diff --git a/extensions/naxer-12/superdocs-share-sheet-app/android/app/src/debug/AndroidManifest.xml b/extensions/naxer-12/superdocs-share-sheet-app/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 00000000..399f6981 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/extensions/naxer-12/superdocs-share-sheet-app/android/app/src/main/AndroidManifest.xml b/extensions/naxer-12/superdocs-share-sheet-app/android/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..1850a0d5 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/extensions/naxer-12/superdocs-share-sheet-app/android/app/src/main/kotlin/com/superdocs/superdocs_share_sheet_app/MainActivity.kt b/extensions/naxer-12/superdocs-share-sheet-app/android/app/src/main/kotlin/com/superdocs/superdocs_share_sheet_app/MainActivity.kt new file mode 100644 index 00000000..2396bda4 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/android/app/src/main/kotlin/com/superdocs/superdocs_share_sheet_app/MainActivity.kt @@ -0,0 +1,5 @@ +package com.superdocs.superdocs_share_sheet_app + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/extensions/naxer-12/superdocs-share-sheet-app/android/app/src/main/res/drawable-v21/launch_background.xml b/extensions/naxer-12/superdocs-share-sheet-app/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 00000000..f74085f3 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/extensions/naxer-12/superdocs-share-sheet-app/android/app/src/main/res/drawable/launch_background.xml b/extensions/naxer-12/superdocs-share-sheet-app/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 00000000..304732f8 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/extensions/naxer-12/superdocs-share-sheet-app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/extensions/naxer-12/superdocs-share-sheet-app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000..db77bb4b Binary files /dev/null and b/extensions/naxer-12/superdocs-share-sheet-app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/extensions/naxer-12/superdocs-share-sheet-app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/extensions/naxer-12/superdocs-share-sheet-app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000..17987b79 Binary files /dev/null and b/extensions/naxer-12/superdocs-share-sheet-app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/extensions/naxer-12/superdocs-share-sheet-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/extensions/naxer-12/superdocs-share-sheet-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000..09d43914 Binary files /dev/null and b/extensions/naxer-12/superdocs-share-sheet-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/extensions/naxer-12/superdocs-share-sheet-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/extensions/naxer-12/superdocs-share-sheet-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000..d5f1c8d3 Binary files /dev/null and b/extensions/naxer-12/superdocs-share-sheet-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/extensions/naxer-12/superdocs-share-sheet-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/extensions/naxer-12/superdocs-share-sheet-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000..4d6372ee Binary files /dev/null and b/extensions/naxer-12/superdocs-share-sheet-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/extensions/naxer-12/superdocs-share-sheet-app/android/app/src/main/res/values-night/styles.xml b/extensions/naxer-12/superdocs-share-sheet-app/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 00000000..06952be7 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/extensions/naxer-12/superdocs-share-sheet-app/android/app/src/main/res/values/styles.xml b/extensions/naxer-12/superdocs-share-sheet-app/android/app/src/main/res/values/styles.xml new file mode 100644 index 00000000..cb1ef880 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/extensions/naxer-12/superdocs-share-sheet-app/android/app/src/profile/AndroidManifest.xml b/extensions/naxer-12/superdocs-share-sheet-app/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 00000000..399f6981 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/extensions/naxer-12/superdocs-share-sheet-app/android/build.gradle.kts b/extensions/naxer-12/superdocs-share-sheet-app/android/build.gradle.kts new file mode 100644 index 00000000..dbee657b --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/android/build.gradle.kts @@ -0,0 +1,24 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = + rootProject.layout.buildDirectory + .dir("../../build") + .get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/extensions/naxer-12/superdocs-share-sheet-app/android/gradle.properties b/extensions/naxer-12/superdocs-share-sheet-app/android/gradle.properties new file mode 100644 index 00000000..e96108cf --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/android/gradle.properties @@ -0,0 +1,6 @@ +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true +# This newDsl flag was added by the Flutter template +android.newDsl=false +# This builtInKotlin flag was added by the Flutter template +android.builtInKotlin=false diff --git a/extensions/naxer-12/superdocs-share-sheet-app/android/gradle/wrapper/gradle-wrapper.properties b/extensions/naxer-12/superdocs-share-sheet-app/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..a20f2c46 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-all.zip diff --git a/extensions/naxer-12/superdocs-share-sheet-app/android/settings.gradle.kts b/extensions/naxer-12/superdocs-share-sheet-app/android/settings.gradle.kts new file mode 100644 index 00000000..b28021a9 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/android/settings.gradle.kts @@ -0,0 +1,26 @@ +pluginManagement { + val flutterSdkPath = + run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "9.1.0" apply false + id("org.jetbrains.kotlin.android") version "2.4.0" apply false +} + +include(":app") diff --git a/extensions/naxer-12/superdocs-share-sheet-app/docs/superpowers/specs/2026-08-10-mobile-share-sheet-app-design.md b/extensions/naxer-12/superdocs-share-sheet-app/docs/superpowers/specs/2026-08-10-mobile-share-sheet-app-design.md new file mode 100644 index 00000000..8229ea3e --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/docs/superpowers/specs/2026-08-10-mobile-share-sheet-app-design.md @@ -0,0 +1,109 @@ +# Mobile Share-Sheet App — Design Spec + +> **Start with `TASK.md` in the repo root instead of this file.** This doc captures the original +> reasoning and is kept for reference, but `TASK.md` has one corrected flow (upload once, then chat +> by `session_id`, not resending the document every turn) and is the authoritative operational doc +> — this file and `TASK.md` disagree on that one point, and `TASK.md` wins. + +Source: SuperDocs-Task-Engineer.pdf, Task 2, assigned build "Mobile share-sheet app" (S2). +Destination: PR into `github.com/superdocsapp/superdocs-builds`, `extensions/` folder. + +## What it does + +Receive a document via the OS share sheet, type or dictate one edit instruction, review the +result on a thumb-sized screen, export, and re-share — three taps, works on a poor connection by +finishing in the background even if the app is closed. + +## Stack + +**Flutter**, bare (not a no-code builder) — one codebase for iOS + Android. iOS still needs a +small native Share Extension target (an iOS platform requirement, not a Flutter limitation) that +hands the shared file to the main app via an App Group. + +## Logged assumptions (unverified — confirm against a real SuperDocs account before building) + +1. **Auth**: manual API key entry on first launch (`flutter_secure_storage`), not OAuth — SuperDocs + has no public OAuth flow yet per the brief. +2. **File upload format**: the shared file's raw bytes go to SuperDocs' upload endpoint as-is; + assumed the backend converts PDF/DOCX server-side ("we take documents, not raw Word XML"). + Only a summarized doc fetch was available, not a live API call — verify first thing. + +## Architecture: async job + local background queue + +``` +Share sheet → instruction screen → POST /v1/chat/async (job_id) → persist locally, app can close +→ background poll GET /v1/jobs/{job_id} → local notification when awaiting_approval/completed +→ reopen → review screen (WebView, changed sections highlighted, bottom Approve/Reject) +→ approve: POST /v1/chat/{session_id}/approve → poll → POST /v1/documents/export +→ share sheet again, send the exported file onward +``` + +**v1 cut**: whole-document approve/reject only, not per-chunk (the API supports per-chunk; a +"3 of 7 changes" stepper is real extra UI for an S2 build — v2 addition, not a hidden gap). + +## Task breakdown — 4 branches, mostly non-overlapping files + +### Branch 1: `foundation` — lands first, defines the shared interfaces +- Flutter project scaffold, `pubspec.yaml`, app navigation shell (onboarding → home → + instruction → review) +- `SuperDocsClient` (Dart service): wraps upload, `chat/async`, `jobs/{id}` poll, `approve`, + `export` — the one file every other branch imports against +- Data models: `EditJob`, `DocumentChanges`, `ExportResult` +- API key onboarding screen + secure storage +- **Owns**: `lib/api/`, `lib/models/`, `lib/app.dart`, `pubspec.yaml` + +### Branch 2: `share-intake` +- iOS Share Extension target + App Group handoff; Android intent handling via + `receive_sharing_intent` +- Instruction screen: text field (OS dictation is free via the keyboard), "Go" button, calls + `SuperDocsClient.startEdit()` from Branch 1, persists the returned `job_id` to a local pending-jobs + list +- **Owns**: `ios/ShareExtension/`, `lib/screens/instruction_screen.dart`, `lib/storage/job_queue.dart` +- **Depends on**: Branch 1's `SuperDocsClient` interface (can build against a stub/mock of it in + parallel, wire the real one at merge time) + +### Branch 3: `background-engine` +- `workmanager` (Android) + iOS `BGTaskScheduler` wiring to poll every pending `job_id` from + Branch 2's queue via Branch 1's client +- `flutter_local_notifications` on status change to `awaiting_approval`/`completed`/`failed` +- Backoff/retry for poor connectivity +- **Owns**: `lib/background/`, platform-specific background-task registration files +- **Depends on**: Branch 1's client + Branch 2's job-queue storage shape (agree the shape in + Branch 1's data models, build against it directly) + +### Branch 4: `review-export` +- WebView screen rendering `document_changes.updated_html` at phone viewport width, injected CSS + highlighting changed chunks +- Bottom-pinned Approve/Reject bar +- Approve → Branch 1's `approve()` → poll → `export()` → hand the file to the OS share sheet +- **Owns**: `lib/screens/review_screen.dart`, `lib/export/` +- **Depends on**: Branch 1's client + models only + +## Synthesis plan + +1. Branch 1 merges to `main` first — it's small, foundational, and everything else imports its + interfaces. (Matches how Task 1's Phase 1 skeleton had to land before its parallel tracks.) +2. Branches 2, 3, 4 are then genuinely parallelizable (as 3 worktrees, mirroring Task 1's + Track A/B/C pattern) — they touch disjoint file sets and only share Branch 1's already-frozen + interfaces. +3. Merge order for 2/3/4 doesn't matter functionally; merge whichever finishes first. Expect zero + file-level conflicts between them — cross-check at merge time regardless. +4. Integration pass after all four are merged: wire navigation end-to-end (share → instruction → + close app → notification → review → export → re-share), run once on a real iOS device and once + on Android (Share Extensions and background execution cannot be fully verified on simulators). + +## Testing + +- Branch 1: unit tests for `SuperDocsClient` against a mocked HTTP layer (request shape, response + parsing, the documented double-JSON-parse behavior on proposed changes if confirmed real). +- Branch 3: the one behavior worth real device testing, not just unit tests — background poll + surviving app termination is the brief's explicit bar ("background completion survives the app + being closed"). +- Branch 4: WebView renders at phone width with no horizontal scroll on a real formatted document, + not just a plain-text stub. + +## Explicit non-goals for v1 + +- Per-chunk approve/reject +- Any auth beyond manual API key entry +- Offline editing (queuing works for the *request*; nothing here works with zero connectivity ever) diff --git a/extensions/naxer-12/superdocs-share-sheet-app/docs/tasks/01-foundation.md b/extensions/naxer-12/superdocs-share-sheet-app/docs/tasks/01-foundation.md new file mode 100644 index 00000000..f1299740 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/docs/tasks/01-foundation.md @@ -0,0 +1,114 @@ +# Branch: `foundation` + +**Read this whole file before writing code. If you haven't read `TASK.md` in the repo root yet, +stop and read that first — it has the overall project context this file assumes you already have.** + +## Your job, in one sentence + +Build the SuperDocs API client, the core data models, and the app's navigation shell — the +foundation every other branch (`share-intake`, `background-engine`, `review-export`) builds on top +of. Nothing user-facing beyond a working onboarding screen and empty placeholder screens for the +others to fill in. + +## Do this first, before writing any client code + +**Already done, 2026-08-17 — do not repeat.** A real SuperDocs account was created via the agent +self-signup flow and the full upload → edit → poll → approve/auto-complete → export loop was run +live against it. Logged assumption #2 is resolved; see `TASK.md`'s "API contract — verified live" +section for the full corrected contract and the six real bugs it caught and fixed in +`lib/api/superdocs_client.dart` and friends. The contract below is now the verified real shape, not +a guess — no need to re-verify it, just build against it. + +## API contract to implement (verified live, 2026-08-17) + +Base URL: `https://api.superdocs.app`. Every request needs header +`Authorization: Bearer `. + +**Upload**: `POST /v1/documents/upload-base64` +```json +// request — session_id is NOT auto-generated if omitted; generate a UUID client-side and always +// send one, or the document won't persist and you'll get no session_id back at all. +{ "filename": "contract.pdf", "file_base64": "", "session_id": "", "return_html": true } +// response +{ "html": "...", "session_id": "...", "filename": "...", "chunks_count": 12, "version_id": "...", "page_setup": null, "persisted": true } +``` + +**Start an edit**: `POST /v1/chat/async` +```json +// request +{ "message": "", "session_id": "" } +// response +{ "job_id": "...", "session_id": "...", "status": "pending" } +``` + +**Poll job status**: `GET /v1/jobs/{job_id}` +```json +// response, once ready — the result is nested under "result", not top-level +{ + "status": "awaiting_approval", + "result": { + "document_changes": { "updated_html": "...", "chunk_diffs": [ { "chunk_id": "...", "action": "update", "content": "..." } ] } + } +} +``` +Status values: `pending`, `in_progress`, `awaiting_approval`, `completed`, `failed`, `cancelled`. +**A job can go straight to `completed` with no `awaiting_approval` step** if the edit was +auto-approved — handle both, don't assume approval is always needed. + +**Approve**: `POST /v1/chat/{session_id}/approve` +```json +// request — job_id is required, a bare {approved} 422s +{ "job_id": "...", "approved": true } +// response +{ "status": "approved", "job_id": "...", "message": "..." } +``` + +**Export**: `POST /v1/documents/export` +```json +// request +{ "session_id": "...", "format": "docx" } +// response: binary file body. Content-Disposition carries BOTH filename="..." and a +// filename*=UTF-8''... form on the same line — parse only the quoted filename="..." part, +// a naive split on "filename=" picks up junk from the second form too. +``` + +## What to build + +1. Flutter project scaffold (`flutter create`), reasonable package name, both iOS and Android + targets present and building (even if the app just shows a blank screen). +2. `lib/api/superdocs_client.dart` — implements exactly the `SuperDocsClient` interface specified + in `TASK.md`'s "shared interface" section. Do not change that interface's method signatures + without updating `TASK.md` and flagging it in `PROGRESS.md` — the other three branches are + building against it as-is, possibly before this branch merges. +3. `lib/models/` — `UploadResult`, `ChatJobResult`, `JobStatusResult`, `ExportResult`, matching + `TASK.md` exactly. +4. `lib/storage/api_key_store.dart` — wraps `flutter_secure_storage` for saving/reading the user's + SuperDocs API key. +5. `lib/screens/onboarding_screen.dart` — a single screen: paste your API key, save it, continue. + Nothing fancier. +6. `lib/app.dart` — navigation shell with named routes for: onboarding, home (can be a stub list + view for now), instruction (stub), review (stub). The other branches will fill in the real + instruction and review screens; your job is just that the routes exist and navigate correctly. +7. Unit tests for `SuperDocsClient` against a mocked HTTP client (use `package:http`'s testable + client or `mockito`) — one test per method, covering the request shape sent and the response + parsed correctly. + +## Owns (files this branch is the only one touching) + +`pubspec.yaml`, `lib/api/`, `lib/models/`, `lib/storage/api_key_store.dart`, `lib/app.dart`, +`lib/screens/onboarding_screen.dart`, `ios/` and `android/` project scaffold files. + +## Definition of done + +- `flutter test` passes for the API client's unit tests. +- App builds and runs on both an iOS simulator and an Android emulator, showing onboarding then an + empty home screen after saving a key. +- A real call to each of the four API methods (against a real account) has been made at least once + manually and the response shape confirmed to match what's documented above — corrections written + into this file and `TASK.md` if reality differed. +- `PROGRESS.md` has a new dated entry summarizing what was confirmed/corrected. + +## Out of scope for this branch + +Share intent handling, background polling, the real instruction/review screen UI, export-then-share. +Those are the other three branches' jobs — build the stub screens/routes they'll fill in, nothing more. diff --git a/extensions/naxer-12/superdocs-share-sheet-app/docs/tasks/02-share-intake.md b/extensions/naxer-12/superdocs-share-sheet-app/docs/tasks/02-share-intake.md new file mode 100644 index 00000000..1496d1b4 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/docs/tasks/02-share-intake.md @@ -0,0 +1,59 @@ +# Branch: `share-intake` + +**Read this whole file before writing code. If you haven't read `TASK.md` in the repo root yet, +stop and read that first.** + +## Your job, in one sentence + +Let a user share a file into this app from any other app (Mail, Files, Messages), type or dictate +one instruction, and kick off the edit — that's taps 1 and 2 of the brief's "three taps and one +sentence." + +## Before you start: check whether `foundation` has merged + +Run `git log --all --oneline --graph` and `git branch -a`. If `foundation` is merged into `main`, +branch off `main` and use the real `SuperDocsClient`. If it hasn't merged yet, branch off `main` +anyway and build against the exact interface documented in `TASK.md`'s "shared interface" section +as a stub (a fake implementation returning canned values) — swap in the real import once +`foundation` lands. Do not block on `foundation` finishing; do not redefine the interface yourself. + +## What to build + +1. **iOS Share Extension**: a native Swift Share Extension target (`ios/ShareExtension/`) that + accepts documents (PDF, DOCX, plain text) shared from other apps, writes the file into a shared + App Group container, and opens the main app with a URL scheme or deep link carrying a reference + to it. Use the `receive_sharing_intent` Flutter package's documented iOS setup for this — it + handles most of this scaffolding; don't hand-roll it if the package covers it. +2. **Android share intent**: handle `ACTION_SEND` intents for common document MIME types + (`application/pdf`, `application/vnd.openxmlformats-officedocument.wordprocessingml.document`, + `text/plain`) via the same package's Android side. +3. **`lib/screens/instruction_screen.dart`**: replaces the stub route from `foundation`. Shows the + shared file's name, one multiline text field (the OS keyboard's built-in mic button covers + "dictate" — do not add a custom speech-to-text package, it's not needed), and a "Go" button. + On tap: call `SuperDocsClient.uploadDocument()` then `.startEdit()`, then persist the result (see + next item) and navigate to the home screen with a "processing" state shown for that job. +4. **`lib/storage/job_queue.dart`**: local persistence (SQLite via `sqflite`, or a simple JSON file + — pick whichever is less code) for the list of in-flight jobs: `{ jobId, sessionId, filename, + status, createdAt }`. This is the shape `background-engine` will read from and write status + updates to — keep the field names exactly as listed here since that branch depends on them. + +## Owns + +`ios/ShareExtension/`, `lib/screens/instruction_screen.dart`, `lib/storage/job_queue.dart`, +Android manifest changes for intent filters. + +## Definition of done + +- Sharing a real PDF from the iOS Files app into this app opens it directly to the instruction + screen with the filename shown. +- Same for Android, sharing from a file manager or Gmail attachment. +- Typing an instruction and tapping Go results in one row appearing in `job_queue`'s storage with + status `pending` or `processing`, and a real `job_id` from a real API response (or the stub, if + `foundation` hasn't merged yet — note in your commit message which one you tested against). +- Dictation works via the OS keyboard with zero custom code — verify by actually tapping the mic + icon on the keyboard, not just assuming it works because you added a multiline `TextField`. + +## Out of scope for this branch + +Background polling of job status after the initial `startEdit()` call (that's +`background-engine`), the review screen (that's `review-export`), export/re-share. diff --git a/extensions/naxer-12/superdocs-share-sheet-app/docs/tasks/03-background-engine.md b/extensions/naxer-12/superdocs-share-sheet-app/docs/tasks/03-background-engine.md new file mode 100644 index 00000000..428b71c9 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/docs/tasks/03-background-engine.md @@ -0,0 +1,63 @@ +# Branch: `background-engine` + +**Read this whole file before writing code. If you haven't read `TASK.md` in the repo root yet, +stop and read that first.** + +## Your job, in one sentence + +Make the brief's hardest bar real: "background completion survives the app being closed." Poll +every in-flight job's status even while the app is fully closed, and notify the user the moment +one needs their attention. + +## Before you start: check two things + +1. `git branch -a` — is `foundation` merged? Build against the real `SuperDocsClient` if so, + otherwise against the stub interface in `TASK.md` (same rule as every other branch). +2. Is `share-intake` merged, or at least has its `lib/storage/job_queue.dart` been written? You + read from and write to that same local job queue — if it doesn't exist yet, build against the + exact shape documented there (`{ jobId, sessionId, filename, status, createdAt }`) as your own + stub, and reconcile at merge time. Don't invent a second, different queue. + +## What to build + +1. **Android**: register a periodic `workmanager` task (package: `workmanager`) that, on each run, + reads every job from the queue with status `pending` or `processing`, calls + `SuperDocsClient.getJobStatus()` for each, and updates the queue's stored status. This must keep + running even if the user has force-closed the app (that's the whole point — verify this on a real + device or emulator by closing the app and confirming the poll still fires). +2. **iOS**: register a `BGAppRefreshTask` (via `BGTaskScheduler`, exposed to Flutter through a + platform channel or a maintained plugin if one covers this adequately — check + `flutter_background_fetch` or similar before hand-rolling a platform channel) doing the same + poll. iOS's background execution budget is much stingier than Android's — a real device test + closing the app and waiting is the only way to actually confirm this works; do not assume it does + because the code compiles. +3. **`flutter_local_notifications`**: fire a local notification the moment a job's status changes to + `awaiting_approval` or `completed` (something changed, worth opening the app for) or `failed` + (something the user needs to know went wrong). Tapping the notification should deep-link straight + to the review screen for that job (coordinate the route name with `review-export`'s branch — check + `lib/app.dart` from `foundation` for the route name it registered). +4. **Backoff on failure**: if a poll request fails (no connectivity), retry with increasing delay, + don't hammer the API or drain the battery. A simple doubling backoff capped at a few minutes is + enough — this doesn't need to be sophisticated. + +## Owns + +`lib/background/`, Android `workmanager` registration (in `android/app/src/main/...` as needed), +iOS background-task registration (`ios/Runner/AppDelegate.swift` additions, `Info.plist` background +mode entries). + +## Definition of done + +- Start a real edit (via `share-intake`'s flow, or by manually inserting a row into the job queue + with a real `job_id` if that branch isn't merged yet), then **force-close the app**. Confirm, + without reopening it yourself, that a notification arrives once the job completes. +- This is explicitly not something a unit test can prove — the brief's own bar is behavioral + ("survives the app being closed"), so the definition of done here is a real device test, described + in your PR/commit message with what device/OS version you tested on, not just "tests pass." +- Backoff behavior is unit-testable (mock a failing HTTP client, assert the delay grows) — write + that test even though the main behavior above needs a real device. + +## Out of scope for this branch + +The instruction screen, the review screen UI itself (you only need to know its route name to +deep-link to it), export/re-share. diff --git a/extensions/naxer-12/superdocs-share-sheet-app/docs/tasks/04-review-export.md b/extensions/naxer-12/superdocs-share-sheet-app/docs/tasks/04-review-export.md new file mode 100644 index 00000000..c82f69b1 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/docs/tasks/04-review-export.md @@ -0,0 +1,61 @@ +# Branch: `review-export` + +**Read this whole file before writing code. If you haven't read `TASK.md` in the repo root yet, +stop and read that first.** + +## Your job, in one sentence + +The third tap: show the proposed edit on a screen a thumb can actually use, let the user approve or +reject it, then export and hand the finished file back to the OS share sheet. + +## Before you start + +Check `git branch -a` for whether `foundation` has merged; build against the real +`SuperDocsClient` if so, otherwise against the stub interface documented in `TASK.md` (same rule as +every other branch — do not block on `foundation`, do not redefine its interface). + +## What to build + +1. **`lib/screens/review_screen.dart`** (replaces the stub route from `foundation`): takes a + `jobId`/`sessionId` as a route argument. Calls `SuperDocsClient.getJobStatus()` to get + `updated_html` and `chunk_diffs`. Renders the HTML in a WebView (`webview_flutter`) sized to the + phone's viewport width — inject a `` tag if + the returned HTML doesn't already have one, so nothing requires pinch-zoom or sideways scroll + (this is an explicit bar in the brief: "without pinching or scrolling sideways"). +2. **Highlight changed sections**: inject a small CSS block into the WebView marking elements whose + `chunk_id` (from `chunk_diffs`) changed — a background-color highlight is enough, this doesn't + need to be a full visual diff/redline for v1. +3. **Bottom action bar**: Approve and Reject buttons, large tap targets, pinned to the bottom of the + screen (thumb-reachable, not top). This is v1 whole-document approve/reject, not per-chunk — see + `TASK.md`'s "v1 cut" note; don't build a per-chunk stepper, it's explicitly deferred. +4. **On Approve**: call `SuperDocsClient.approve(sessionId, approved: true)`, briefly poll + `getJobStatus()` again until `status == "completed"` (a simple loop with a short delay and a + handful of retries is enough — this step is typically fast since the heavy work already + happened), then call `SuperDocsClient.export(sessionId, format: "docx")`. +5. **Hand off to the OS share sheet**: take the exported file bytes, write them to a temp file with + the right extension, and invoke the platform share sheet (`share_plus` package covers this) so + the user can send the finished file onward — this is the "share it onward" half of the brief's + loop, and it's this branch's responsibility to close it. +6. **On Reject**: call `approve(sessionId, approved: false)`, show a simple confirmation, return to + home. No retry/re-instruction flow needed for v1 — that's a reasonable v2 addition, not built here. + +## Owns + +`lib/screens/review_screen.dart`, `lib/export/` (the export-and-share logic). + +## Definition of done + +- Given a real (or stubbed) `awaiting_approval` job, the review screen renders real formatted HTML + at phone width with zero horizontal scroll — test this against an actual formatted document (a + real DOCX-derived HTML sample, not a one-line plain-text stub, which would hide layout bugs). +- Approve → export → share-sheet handoff works end to end against a real account (once `foundation` + is confirmed working per its own definition of done). +- Reject path works and doesn't crash or leave the job queue in a broken state (coordinate with + `background-engine` on how a rejected job's stored status should read — `rejected` or `cancelled`, + pick one and note the choice in `PROGRESS.md` since both other branches may read that field). + +## Out of scope for this branch + +Share intent handling (that's `share-intake`), background polling before this screen opens (that's +`background-engine` — by the time this screen opens, the job is already at `awaiting_approval` or +later), per-chunk approve/reject (explicitly deferred to v2, see `TASK.md`). diff --git a/extensions/naxer-12/superdocs-share-sheet-app/ios/.gitignore b/extensions/naxer-12/superdocs-share-sheet-app/ios/.gitignore new file mode 100644 index 00000000..7a7f9873 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/extensions/naxer-12/superdocs-share-sheet-app/ios/Flutter/AppFrameworkInfo.plist b/extensions/naxer-12/superdocs-share-sheet-app/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 00000000..391a902b --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + + diff --git a/extensions/naxer-12/superdocs-share-sheet-app/ios/Flutter/Debug.xcconfig b/extensions/naxer-12/superdocs-share-sheet-app/ios/Flutter/Debug.xcconfig new file mode 100644 index 00000000..592ceee8 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/extensions/naxer-12/superdocs-share-sheet-app/ios/Flutter/Release.xcconfig b/extensions/naxer-12/superdocs-share-sheet-app/ios/Flutter/Release.xcconfig new file mode 100644 index 00000000..592ceee8 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner.xcodeproj/project.pbxproj b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 00000000..7b63a937 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,647 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.superdocs.superdocsShareSheetApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.superdocs.superdocsShareSheetApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.superdocs.superdocsShareSheetApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.superdocs.superdocsShareSheetApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.superdocs.superdocsShareSheetApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.superdocs.superdocsShareSheetApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..f9b0d7c5 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 00000000..c3fedb29 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner.xcworkspace/contents.xcworkspacedata b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..1d526a16 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..f9b0d7c5 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/AppDelegate.swift b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/AppDelegate.swift new file mode 100644 index 00000000..c30b367e --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/AppDelegate.swift @@ -0,0 +1,16 @@ +import Flutter +import UIKit + +@main +@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } + + func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) { + GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry) + } +} diff --git a/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..d36b1fab --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 00000000..dc9ada47 Binary files /dev/null and b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 00000000..7353c41e Binary files /dev/null and b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 00000000..797d452e Binary files /dev/null and b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 00000000..6ed2d933 Binary files /dev/null and b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 00000000..4cd7b009 Binary files /dev/null and b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 00000000..fe730945 Binary files /dev/null and b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 00000000..321773cd Binary files /dev/null and b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 00000000..797d452e Binary files /dev/null and b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 00000000..502f463a Binary files /dev/null and b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 00000000..0ec30343 Binary files /dev/null and b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 00000000..0ec30343 Binary files /dev/null and b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 00000000..e9f5fea2 Binary files /dev/null and b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 00000000..84ac32ae Binary files /dev/null and b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 00000000..8953cba0 Binary files /dev/null and b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 00000000..0467bf12 Binary files /dev/null and b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 00000000..0bedcf2f --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 00000000..9da19eac Binary files /dev/null and b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 00000000..9da19eac Binary files /dev/null and b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 00000000..9da19eac Binary files /dev/null and b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 00000000..89c2725b --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Base.lproj/LaunchScreen.storyboard b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 00000000..f2e259c7 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Base.lproj/Main.storyboard b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 00000000..f3c28516 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Info.plist b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Info.plist new file mode 100644 index 00000000..285e56b4 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Info.plist @@ -0,0 +1,70 @@ + + + + + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Superdocs Share Sheet App + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + superdocs_share_sheet_app + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneConfigurationName + flutter + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + + UIApplicationSupportsIndirectInputEvents + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Runner-Bridging-Header.h b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 00000000..308a2a56 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/SceneDelegate.swift b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/SceneDelegate.swift new file mode 100644 index 00000000..b9ce8ea2 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/ios/Runner/SceneDelegate.swift @@ -0,0 +1,6 @@ +import Flutter +import UIKit + +class SceneDelegate: FlutterSceneDelegate { + +} diff --git a/extensions/naxer-12/superdocs-share-sheet-app/ios/RunnerTests/RunnerTests.swift b/extensions/naxer-12/superdocs-share-sheet-app/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 00000000..86a7c3b1 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/extensions/naxer-12/superdocs-share-sheet-app/lib/api/superdocs_client.dart b/extensions/naxer-12/superdocs-share-sheet-app/lib/api/superdocs_client.dart new file mode 100644 index 00000000..cd103816 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/lib/api/superdocs_client.dart @@ -0,0 +1,191 @@ +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:uuid/uuid.dart'; +import '../models/upload_result.dart'; +import '../models/chat_job_result.dart'; +import '../models/job_status_result.dart'; +import '../models/export_result.dart'; +import '../storage/api_key_store.dart'; + +class SuperDocsClient { + final String baseUrl; + final http.Client _httpClient; + final ApiKeyStore _apiKeyStore; + + SuperDocsClient({ + this.baseUrl = 'https://api.superdocs.app', + http.Client? httpClient, + ApiKeyStore? apiKeyStore, + }) : _httpClient = httpClient ?? http.Client(), + _apiKeyStore = apiKeyStore ?? ApiKeyStore(); + + Future> _getHeaders() async { + final apiKey = await _apiKeyStore.getApiKey(); + return { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer ${apiKey ?? ''}', + }; + } + + Future uploadDocument({ + required List fileBytes, + required String filename, + }) async { + final uri = Uri.parse('$baseUrl/v1/documents/upload-base64'); + final base64Content = base64Encode(fileBytes); + final headers = await _getHeaders(); + + // The real API does NOT auto-generate a session_id when one is omitted -- + // verified live against a real account (2026-08-17): omitting session_id + // triggers the "one-off conversion" path, which returns no session_id at + // all and does not persist the document (`"persisted": false`). Every + // downstream call (startEdit, approve, export) needs a session_id, so the + // client must generate one itself, matching the `^[a-zA-Z0-9_\-\.]+$` + // pattern the real API's AsyncChatRequest.session_id requires. + final sessionId = const Uuid().v4(); + + final response = await _httpClient.post( + uri, + headers: headers, + body: jsonEncode({ + 'filename': filename, + 'file_base64': base64Content, + 'session_id': sessionId, + 'return_html': true, + }), + ); + + if (response.statusCode >= 200 && response.statusCode < 300) { + final data = jsonDecode(response.body) as Map; + return UploadResult.fromJson({ + ...data, + // The real response echoes session_id back, but fall back to the + // one we generated in case a future API revision omits it again. + 'session_id': data['session_id'] ?? sessionId, + 'filename': data['filename'] ?? filename, + }); + } else { + throw Exception( + 'Upload failed with status ${response.statusCode}: ${response.body}', + ); + } + } + + Future startEdit({ + required String sessionId, + required String instruction, + }) async { + final uri = Uri.parse('$baseUrl/v1/chat/async'); + final headers = await _getHeaders(); + + final response = await _httpClient.post( + uri, + headers: headers, + body: jsonEncode({ + 'message': instruction, + 'session_id': sessionId, + }), + ); + + if (response.statusCode >= 200 && response.statusCode < 300) { + final data = jsonDecode(response.body) as Map; + return ChatJobResult.fromJson(data); + } else { + throw Exception( + 'Start edit failed with status ${response.statusCode}: ${response.body}', + ); + } + } + + Future getJobStatus({required String jobId}) async { + final uri = Uri.parse('$baseUrl/v1/jobs/$jobId'); + final headers = await _getHeaders(); + + final response = await _httpClient.get( + uri, + headers: headers, + ); + + if (response.statusCode >= 200 && response.statusCode < 300) { + final data = jsonDecode(response.body) as Map; + return JobStatusResult.fromJson(data); + } else { + throw Exception( + 'Get job status failed with status ${response.statusCode}: ${response.body}', + ); + } + } + + Future approve({ + required String sessionId, + required String jobId, + required bool approved, + }) async { + final uri = Uri.parse('$baseUrl/v1/chat/$sessionId/approve'); + final headers = await _getHeaders(); + + // job_id is required by the real ApprovalRequest schema, verified + // against the live OpenAPI spec (2026-08-17) -- omitting it 422s. This + // is whole-document approve/reject (v1 cut): job_id + approved only, + // no change_id/changes, which the real API also supports for the + // deferred v2 per-chunk approve/reject flow. + final response = await _httpClient.post( + uri, + headers: headers, + body: jsonEncode({ + 'job_id': jobId, + 'approved': approved, + }), + ); + + if (response.statusCode < 200 || response.statusCode >= 300) { + throw Exception( + 'Approve failed with status ${response.statusCode}: ${response.body}', + ); + } + } + + Future export({ + required String sessionId, + required String format, + }) async { + final uri = Uri.parse('$baseUrl/v1/documents/export'); + final headers = await _getHeaders(); + + final response = await _httpClient.post( + uri, + headers: headers, + body: jsonEncode({ + 'session_id': sessionId, + 'format': format, + }), + ); + + if (response.statusCode >= 200 && response.statusCode < 300) { + String filename = 'exported_document.$format'; + final disposition = response.headers['content-disposition']; + if (disposition != null) { + // The real header carries BOTH forms on one line, e.g.: + // attachment; filename="SuperDocs Document.docx"; filename*=UTF-8''SuperDocs%20Document.docx + // verified live against the real export endpoint (2026-08-17). A + // naive split on "filename=" captures everything after the first + // match, including the trailing "; filename*=..." segment, which + // corrupts the filename. Match only the quoted value. + final match = RegExp(r'filename="([^"]+)"').firstMatch(disposition); + if (match != null) { + filename = match.group(1)!; + } + } + + return ExportResult( + fileBytes: response.bodyBytes, + filename: filename, + contentType: response.headers['content-type'] ?? 'application/octet-stream', + ); + } else { + throw Exception( + 'Export failed with status ${response.statusCode}: ${response.body}', + ); + } + } +} diff --git a/extensions/naxer-12/superdocs-share-sheet-app/lib/app.dart b/extensions/naxer-12/superdocs-share-sheet-app/lib/app.dart new file mode 100644 index 00000000..dcfc0138 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/lib/app.dart @@ -0,0 +1,51 @@ +import 'package:flutter/material.dart'; +import 'screens/onboarding_screen.dart'; +import 'screens/home_screen.dart'; +import 'screens/instruction_screen.dart'; +import 'screens/review_screen.dart'; + +class SuperDocsApp extends StatelessWidget { + final String initialRoute; + + const SuperDocsApp({ + super.key, + this.initialRoute = '/home', + }); + + // A widget above MaterialApp (main.dart's ShareIntentHandler, which needs + // to navigate on a shared file even before any screen has built) has no + // Navigator ancestor to call Navigator.of(context) on -- MaterialApp is + // what creates the Navigator, and it's below that widget in the tree, not + // above it. This key is the standard way to get a navigator reference + // from outside the widget subtree MaterialApp owns. Found as a real, + // silent bug (2026-08-17): ShareIntentHandler existed but was never + // wrapped around the app at all, so no shared file ever navigated + // anywhere -- fixed alongside adding this key. + static final GlobalKey navigatorKey = GlobalKey(); + + @override + Widget build(BuildContext context) { + return MaterialApp( + navigatorKey: navigatorKey, + title: 'SuperDocs Mobile', + debugShowCheckedModeBanner: false, + theme: ThemeData( + brightness: Brightness.dark, + scaffoldBackgroundColor: const Color(0xFF0F172A), + colorScheme: const ColorScheme.dark( + primary: Colors.blueAccent, + secondary: Color(0xFF10B981), + surface: Color(0xFF1E293B), + ), + useMaterial3: true, + ), + initialRoute: initialRoute, + routes: { + '/onboarding': (context) => const OnboardingScreen(), + '/home': (context) => const HomeScreen(), + '/instruction': (context) => const InstructionScreen(), + '/review': (context) => const ReviewScreen(), + }, + ); + } +} diff --git a/extensions/naxer-12/superdocs-share-sheet-app/lib/background/background_service.dart b/extensions/naxer-12/superdocs-share-sheet-app/lib/background/background_service.dart new file mode 100644 index 00000000..1d0dd57b --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/lib/background/background_service.dart @@ -0,0 +1,135 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:workmanager/workmanager.dart'; +import '../api/superdocs_client.dart'; +import '../storage/job_queue.dart'; + +const String kSuperDocsPollTask = 'com.superdocs.pollTask'; + +@pragma('vm:entry-point') +void callbackDispatcher() { + // callbackDispatcher runs in its own separate background isolate -- + // WidgetsBinding is never initialized there unless this is called first. + // Without it, plugin method channels (shared_preferences, + // flutter_secure_storage, http) can silently fail to work. Confirmed as + // the real cause of a real bug on a real device (2026-08-17): the + // JobScheduler logs proved this task genuinely ran and finished + // (START -> STOP "app called jobFinished") while the app was + // force-closed, but the job's persisted status never actually changed + // from "pending" and no notification ever fired -- the broad `catch (_)` + // below was silently swallowing a failure this fixes. + WidgetsFlutterBinding.ensureInitialized(); + Workmanager().executeTask((task, inputData) async { + if (task == kSuperDocsPollTask || task == Workmanager.iOSBackgroundTask) { + final jobQueue = JobQueue(); + final client = SuperDocsClient(); + + final pendingJobs = await jobQueue.getJobs(); + // Real status enum, verified against the live OpenAPI spec + // (2026-08-17): pending, in_progress, awaiting_approval, completed, + // failed, cancelled -- there is no "processing" status. Using the + // wrong string here meant a job would stop being polled the moment it + // left "pending", well before it ever reached awaiting_approval. + final activeJobs = pendingJobs.where((j) => + j.status.toLowerCase() == 'pending' || + j.status.toLowerCase() == 'in_progress'); + + for (final job in activeJobs) { + try { + final statusRes = await client.getJobStatus(jobId: job.jobId); + final newStatus = statusRes.status.toLowerCase(); + + if (newStatus != job.status.toLowerCase()) { + await jobQueue.updateJobStatus( + job.jobId, + newStatus, + updatedHtml: statusRes.updatedHtml, + ); + + if (newStatus == 'awaiting_approval' || + newStatus == 'completed' || + newStatus == 'failed') { + await BackgroundService.showNotification( + title: 'Document Edit Ready', + body: '${job.filename} status is now: ${newStatus.replaceAll('_', ' ')}', + payload: job.jobId, + ); + } + } + } catch (_) { + // Failure backoff handled by WorkManager retry mechanism + } + } + } + return Future.value(true); + }); +} + +class BackgroundService { + static final FlutterLocalNotificationsPlugin _notificationsPlugin = + FlutterLocalNotificationsPlugin(); + + static Future initialize() async { + const androidSettings = AndroidInitializationSettings('@mipmap/ic_launcher'); + const initSettings = InitializationSettings(android: androidSettings); + + await _notificationsPlugin.initialize( + initSettings, + onDidReceiveNotificationResponse: (response) { + // Deep link handler payload can be read by app route navigation + }, + ); + + // POST_NOTIFICATIONS is declared in the manifest but Android 13+ (API + // 33+) also requires it be granted at runtime -- declaring it alone + // does nothing. Confirmed as a real, silent bug on a real device + // (2026-08-17): `adb shell dumpsys package ... POST_NOTIFICATIONS` + // showed `granted=false` even after the app had already run and its + // background poll had already fired successfully -- the poll worked, + // but the user would never have seen a notification about it. Without + // this call there is no other path in the app that ever asks for it. + await _notificationsPlugin + .resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>() + ?.requestNotificationsPermission(); + + await Workmanager().initialize( + callbackDispatcher, + isInDebugMode: false, + ); + } + + static Future registerPeriodicPoll() async { + await Workmanager().registerPeriodicTask( + 'superdocs_periodic_poll', + kSuperDocsPollTask, + frequency: const Duration(minutes: 15), + constraints: Constraints( + networkType: NetworkType.connected, + ), + ); + } + + static Future showNotification({ + required String title, + required String body, + String? payload, + }) async { + const androidDetails = AndroidNotificationDetails( + 'superdocs_channel', + 'SuperDocs Edits', + channelDescription: 'Notifications for document edit progress and approvals', + importance: Importance.high, + priority: Priority.high, + ); + const notificationDetails = NotificationDetails(android: androidDetails); + + await _notificationsPlugin.show( + DateTime.now().millisecond, + title, + body, + notificationDetails, + payload: payload, + ); + } +} diff --git a/extensions/naxer-12/superdocs-share-sheet-app/lib/export/export_handler.dart b/extensions/naxer-12/superdocs-share-sheet-app/lib/export/export_handler.dart new file mode 100644 index 00000000..223b3384 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/lib/export/export_handler.dart @@ -0,0 +1,41 @@ +import 'dart:io'; +import 'package:path_provider/path_provider.dart'; +import 'package:share_plus/share_plus.dart'; +import '../api/superdocs_client.dart'; +import '../models/export_result.dart'; + +class ExportHandler { + final SuperDocsClient _client; + + ExportHandler({SuperDocsClient? client}) + : _client = client ?? SuperDocsClient(); + + Future exportAndShare({ + required String sessionId, + required String filename, + String format = 'docx', + }) async { + // 1. Request binary export from SuperDocs API + final ExportResult exportResult = await _client.export( + sessionId: sessionId, + format: format, + ); + + // 2. Write file to temp directory + final tempDir = await getTemporaryDirectory(); + final sanitizeFilename = filename.replaceAll(RegExp(r'[^\w\.-]'), '_'); + final finalFilename = sanitizeFilename.endsWith('.$format') + ? sanitizeFilename + : '$sanitizeFilename.$format'; + final filePath = '${tempDir.path}/$finalFilename'; + + final file = File(filePath); + await file.writeAsBytes(exportResult.fileBytes); + + // 3. Hand off to OS Share Sheet + await Share.shareXFiles( + [XFile(filePath, name: finalFilename, mimeType: exportResult.contentType)], + text: 'Exported document from SuperDocs', + ); + } +} diff --git a/extensions/naxer-12/superdocs-share-sheet-app/lib/main.dart b/extensions/naxer-12/superdocs-share-sheet-app/lib/main.dart new file mode 100644 index 00000000..a42d0527 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/lib/main.dart @@ -0,0 +1,93 @@ +import 'dart:async'; +import 'package:flutter/material.dart'; +import 'package:receive_sharing_intent/receive_sharing_intent.dart'; +import 'app.dart'; +import 'background/background_service.dart'; +import 'storage/api_key_store.dart'; + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + + // Initialize background WorkManager & local notifications + try { + await BackgroundService.initialize(); + await BackgroundService.registerPeriodicPoll(); + } catch (_) {} + + // Check stored API Key + final keyStore = ApiKeyStore(); + final apiKey = await keyStore.getApiKey(); + final initialRoute = (apiKey == null || apiKey.isEmpty) ? '/onboarding' : '/home'; + + // ShareIntentHandler was previously defined but never actually used -- + // runApp() built the bare SuperDocsApp, so a real shared file never + // navigated anywhere. Found as a real, silent bug (2026-08-17): the + // share-intake feature this whole app exists for was dead code. + runApp(ShareIntentHandler(child: SuperDocsApp(initialRoute: initialRoute))); +} + +class ShareIntentHandler extends StatefulWidget { + final Widget child; + const ShareIntentHandler({super.key, required this.child}); + + @override + State createState() => _ShareIntentHandlerState(); +} + +class _ShareIntentHandlerState extends State { + late StreamSubscription _intentSubscription; + + @override + void initState() { + super.initState(); + // Listen for shared media/files while app is in memory + _intentSubscription = ReceiveSharingIntent.instance.getMediaStream().listen( + (List value) { + if (value.isNotEmpty) { + _handleSharedFile(value.first); + } + }, + ); + + // Get shared media/files when app opened from closed state. Deferred to + // after the first frame: this widget sits above MaterialApp, so on a + // cold start the Navigator behind navigatorKey may not exist yet the + // instant this Future resolves. + ReceiveSharingIntent.instance.getInitialMedia().then( + (List value) { + if (value.isNotEmpty) { + WidgetsBinding.instance.addPostFrameCallback((_) { + _handleSharedFile(value.first); + }); + } + }, + ); + } + + void _handleSharedFile(SharedMediaFile file) { + final path = file.path; + final filename = path.split('/').last; + + // Navigate via the app-wide navigatorKey, not Navigator.of(context): + // this widget wraps MaterialApp, so it has no Navigator ancestor of its + // own to call Navigator.of(context) on. + SuperDocsApp.navigatorKey.currentState?.pushNamed( + '/instruction', + arguments: { + 'filename': filename, + 'filePath': path, + }, + ); + } + + @override + void dispose() { + _intentSubscription.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return widget.child; + } +} diff --git a/extensions/naxer-12/superdocs-share-sheet-app/lib/models/chat_job_result.dart b/extensions/naxer-12/superdocs-share-sheet-app/lib/models/chat_job_result.dart new file mode 100644 index 00000000..0fd2f890 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/lib/models/chat_job_result.dart @@ -0,0 +1,27 @@ +class ChatJobResult { + final String jobId; + final String sessionId; + final String status; + + ChatJobResult({ + required this.jobId, + required this.sessionId, + required this.status, + }); + + factory ChatJobResult.fromJson(Map json) { + return ChatJobResult( + jobId: json['job_id'] as String? ?? '', + sessionId: json['session_id'] as String? ?? '', + status: json['status'] as String? ?? 'pending', + ); + } + + Map toJson() { + return { + 'job_id': jobId, + 'session_id': sessionId, + 'status': status, + }; + } +} diff --git a/extensions/naxer-12/superdocs-share-sheet-app/lib/models/edit_job.dart b/extensions/naxer-12/superdocs-share-sheet-app/lib/models/edit_job.dart new file mode 100644 index 00000000..0c80e82b --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/lib/models/edit_job.dart @@ -0,0 +1,65 @@ +class EditJob { + final String jobId; + final String sessionId; + final String filename; + final String status; + final DateTime createdAt; + final String? instruction; + final String? updatedHtml; + + EditJob({ + required this.jobId, + required this.sessionId, + required this.filename, + required this.status, + required this.createdAt, + this.instruction, + this.updatedHtml, + }); + + factory EditJob.fromJson(Map json) { + return EditJob( + jobId: json['jobId'] as String? ?? json['job_id'] as String? ?? '', + sessionId: json['sessionId'] as String? ?? json['session_id'] as String? ?? '', + filename: json['filename'] as String? ?? 'document', + status: json['status'] as String? ?? 'pending', + createdAt: json['createdAt'] != null + ? DateTime.parse(json['createdAt'] as String) + : DateTime.now(), + instruction: json['instruction'] as String?, + updatedHtml: json['updatedHtml'] as String?, + ); + } + + Map toJson() { + return { + 'jobId': jobId, + 'sessionId': sessionId, + 'filename': filename, + 'status': status, + 'createdAt': createdAt.toIso8601String(), + 'instruction': instruction, + 'updatedHtml': updatedHtml, + }; + } + + EditJob copyWith({ + String? jobId, + String? sessionId, + String? filename, + String? status, + DateTime? createdAt, + String? instruction, + String? updatedHtml, + }) { + return EditJob( + jobId: jobId ?? this.jobId, + sessionId: sessionId ?? this.sessionId, + filename: filename ?? this.filename, + status: status ?? this.status, + createdAt: createdAt ?? this.createdAt, + instruction: instruction ?? this.instruction, + updatedHtml: updatedHtml ?? this.updatedHtml, + ); + } +} diff --git a/extensions/naxer-12/superdocs-share-sheet-app/lib/models/export_result.dart b/extensions/naxer-12/superdocs-share-sheet-app/lib/models/export_result.dart new file mode 100644 index 00000000..e75ddcb0 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/lib/models/export_result.dart @@ -0,0 +1,11 @@ +class ExportResult { + final List fileBytes; + final String filename; + final String contentType; + + ExportResult({ + required this.fileBytes, + required this.filename, + required this.contentType, + }); +} diff --git a/extensions/naxer-12/superdocs-share-sheet-app/lib/models/job_status_result.dart b/extensions/naxer-12/superdocs-share-sheet-app/lib/models/job_status_result.dart new file mode 100644 index 00000000..f2ee50f4 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/lib/models/job_status_result.dart @@ -0,0 +1,101 @@ +import 'dart:convert'; + +class ChunkDiff { + final String chunkId; + final String action; + final String content; + + ChunkDiff({ + required this.chunkId, + required this.action, + required this.content, + }); + + factory ChunkDiff.fromJson(Map json) { + return ChunkDiff( + chunkId: json['chunk_id'] as String? ?? json['id'] as String? ?? '', + action: json['action'] as String? ?? 'update', + content: json['content'] as String? ?? '', + ); + } + + Map toJson() { + return { + 'chunk_id': chunkId, + 'action': action, + 'content': content, + }; + } +} + +class JobStatusResult { + final String status; + final String? updatedHtml; + final List? diffs; + final List? changedChunkIds; + final String? rawResponse; + + JobStatusResult({ + required this.status, + this.updatedHtml, + this.diffs, + this.changedChunkIds, + this.rawResponse, + }); + + factory JobStatusResult.fromJson(Map json) { + final status = json['status'] as String? ?? 'pending'; + String? html; + List? parsedDiffs; + List? chunkIds; + + // The real GET /v1/jobs/{job_id} response nests document_changes under + // `result` (JobResult.document_changes), not at the top level -- verified + // against the live OpenAPI spec (2026-08-17). The top-level fallback + // below is kept defensively in case an older/alternate response shape + // is ever encountered, but `result.document_changes` is the real path. + final result = json['result']; + final documentChanges = (result is Map) + ? result['document_changes'] + : json['document_changes']; + + if (documentChanges != null) { + dynamic changes = documentChanges; + if (changes is String) { + try { + changes = jsonDecode(changes); + } catch (_) {} + } + + if (changes is Map) { + html = changes['updated_html'] as String? ?? changes['html'] as String?; + if (changes.containsKey('chunk_diffs') && changes['chunk_diffs'] is List) { + final list = changes['chunk_diffs'] as List; + parsedDiffs = list + .whereType>() + .map((item) => ChunkDiff.fromJson(item)) + .toList(); + chunkIds = parsedDiffs.map((d) => d.chunkId).where((id) => id.isNotEmpty).toList(); + } + } + } else if (json.containsKey('updated_html')) { + html = json['updated_html'] as String?; + } + + return JobStatusResult( + status: status, + updatedHtml: html, + diffs: parsedDiffs, + changedChunkIds: chunkIds, + rawResponse: jsonEncode(json), + ); + } + + Map toJson() { + return { + 'status': status, + 'updated_html': updatedHtml, + 'changed_chunk_ids': changedChunkIds, + }; + } +} diff --git a/extensions/naxer-12/superdocs-share-sheet-app/lib/models/upload_result.dart b/extensions/naxer-12/superdocs-share-sheet-app/lib/models/upload_result.dart new file mode 100644 index 00000000..72b45bd8 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/lib/models/upload_result.dart @@ -0,0 +1,35 @@ +class UploadResult { + final String sessionId; + final String html; + final String filename; + final int? chunksCount; + final String? versionId; + + UploadResult({ + required this.sessionId, + required this.html, + required this.filename, + this.chunksCount, + this.versionId, + }); + + factory UploadResult.fromJson(Map json) { + return UploadResult( + sessionId: json['session_id'] as String? ?? '', + html: json['html'] as String? ?? '', + filename: json['filename'] as String? ?? 'document', + chunksCount: json['chunks_count'] as int?, + versionId: json['version_id'] as String?, + ); + } + + Map toJson() { + return { + 'session_id': sessionId, + 'html': html, + 'filename': filename, + 'chunks_count': chunksCount, + 'version_id': versionId, + }; + } +} diff --git a/extensions/naxer-12/superdocs-share-sheet-app/lib/screens/home_screen.dart b/extensions/naxer-12/superdocs-share-sheet-app/lib/screens/home_screen.dart new file mode 100644 index 00000000..10208a66 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/lib/screens/home_screen.dart @@ -0,0 +1,209 @@ +import 'package:flutter/material.dart'; +import '../models/edit_job.dart'; +import '../storage/job_queue.dart'; + +class HomeScreen extends StatefulWidget { + const HomeScreen({super.key}); + + @override + State createState() => _HomeScreenState(); +} + +class _HomeScreenState extends State { + final _jobQueue = JobQueue(); + List _jobs = []; + bool _isLoading = true; + + @override + void initState() { + super.initState(); + _refreshJobs(); + } + + Future _refreshJobs() async { + setState(() => _isLoading = true); + final jobs = await _jobQueue.getJobs(); + setState(() { + _jobs = jobs; + _isLoading = false; + }); + } + + Color _getStatusColor(String status) { + switch (status.toLowerCase()) { + case 'awaiting_approval': + return Colors.amber; + case 'completed': + return const Color(0xFF10B981); + case 'in_progress': + case 'pending': + return Colors.blueAccent; + case 'failed': + case 'cancelled': + case 'rejected': + return Colors.redAccent; + default: + return Colors.grey; + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: const Color(0xFF0F172A), + appBar: AppBar( + backgroundColor: const Color(0xFF0F172A), + elevation: 0, + title: const Text( + 'Document Edits', + style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold), + ), + actions: [ + IconButton( + icon: const Icon(Icons.refresh_rounded, color: Color(0xFF94A3B8)), + onPressed: _refreshJobs, + ), + IconButton( + icon: const Icon(Icons.settings_outlined, color: Color(0xFF94A3B8)), + onPressed: () => Navigator.of(context).pushNamed('/onboarding'), + ), + ], + ), + body: _isLoading + ? const Center(child: CircularProgressIndicator(color: Colors.blueAccent)) + : _jobs.isEmpty + ? _buildEmptyState() + : RefreshIndicator( + onRefresh: _refreshJobs, + child: ListView.builder( + padding: const EdgeInsets.all(16), + itemCount: _jobs.length, + itemBuilder: (context, index) { + final job = _jobs[index]; + return _buildJobCard(job); + }, + ), + ), + floatingActionButton: FloatingActionButton.extended( + onPressed: () { + // This quick-action entry point has no real file picker wired in + // yet -- that's share-intake's job, not this screen's. Passing + // placeholder bytes here (previously [1, 2, 3, 4]) made every + // real upload attempt fail with a real, confirmed 400 ("File is + // not a zip file") -- found by actually running this on a device + // (2026-08-17). Omitting `fileBytes` lets instruction_screen's + // own valid embedded sample DOCX (kValidSampleDocxBytes) be used + // instead, so this demo entry point produces a real, uploadable + // file until a real picker replaces it. + Navigator.of(context).pushNamed( + '/instruction', + arguments: { + 'filename': 'sample_document.docx', + }, + ); + }, + backgroundColor: Colors.blueAccent, + icon: const Icon(Icons.add_rounded, color: Colors.white), + label: const Text( + 'New Edit', + style: TextStyle(color: Colors.white, fontWeight: FontWeight.w600), + ), + ), + ); + } + + Widget _buildEmptyState() { + return Center( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 32.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.description_outlined, size: 64, color: Color(0xFF64748B)), + const SizedBox(height: 16), + const Text( + 'No Edits Yet', + style: TextStyle(color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + const Text( + 'Share a file from Files or Mail into SuperDocs, or tap New Edit below to start.', + textAlign: TextAlign.center, + style: TextStyle(color: Color(0xFF94A3B8), fontSize: 14), + ), + ], + ), + ), + ); + } + + Widget _buildJobCard(EditJob job) { + final statusColor = _getStatusColor(job.status); + + return Card( + margin: const EdgeInsets.only(bottom: 12), + color: const Color(0xFF1E293B), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + child: ListTile( + contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + leading: Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: statusColor.withOpacity(0.15), + borderRadius: BorderRadius.circular(12), + ), + child: Icon(Icons.insert_drive_file_outlined, color: statusColor), + ), + title: Text( + job.filename, + style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w600), + ), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 4), + Text( + job.instruction ?? 'Edit requested', + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: const TextStyle(color: Color(0xFF94A3B8), fontSize: 13), + ), + const SizedBox(height: 6), + Row( + children: [ + Container( + width: 8, + height: 8, + decoration: BoxDecoration( + color: statusColor, + shape: BoxShape.circle, + ), + ), + const SizedBox(width: 6), + Text( + job.status.toUpperCase(), + style: TextStyle( + color: statusColor, + fontSize: 11, + fontWeight: FontWeight.bold, + letterSpacing: 0.5, + ), + ), + ], + ), + ], + ), + trailing: const Icon(Icons.chevron_right_rounded, color: Color(0xFF64748B)), + onTap: () { + Navigator.of(context).pushNamed( + '/review', + arguments: { + 'jobId': job.jobId, + 'sessionId': job.sessionId, + }, + ); + }, + ), + ); + } +} diff --git a/extensions/naxer-12/superdocs-share-sheet-app/lib/screens/instruction_screen.dart b/extensions/naxer-12/superdocs-share-sheet-app/lib/screens/instruction_screen.dart new file mode 100644 index 00000000..af093084 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/lib/screens/instruction_screen.dart @@ -0,0 +1,248 @@ +import 'dart:io'; +import 'dart:typed_data'; +import 'package:flutter/material.dart'; +import '../api/superdocs_client.dart'; +import '../models/edit_job.dart'; +import '../storage/job_queue.dart'; + +// Valid minimal PKZip DOCX byte structure containing valid Word XML +final List kValidSampleDocxBytes = [ + 80, 75, 3, 4, 20, 0, 0, 0, 8, 0, 179, 113, 10, 93, 204, 84, 140, 16, 224, 0, 0, 0, 156, 1, 0, 0, 19, 0, 0, 0, 91, 67, 111, 110, 116, 101, 110, 116, 95, 84, 121, 112, 101, 115, 93, 46, 120, 109, 108, 125, 144, 203, 78, 195, 48, 16, 69, 127, 197, 242, 22, 197, 19, 186, 64, 8, 37, 233, 2, 202, 18, 88, 148, 15, 176, 236, 73, 98, 225, 151, 60, 110, 41, 127, 207, 164, 45, 93, 160, 194, 210, 190, 143, 51, 186, 221, 250, 16, 188, 216, 99, 33, 151, 98, 47, 111, 85, 43, 5, 70, 147, 172, 139, 83, 47, 223, 183, 207, 205, 189, 92, 15, 221, 246, 43, 35, 9, 182, 70, 234, 229, 92, 107, 126, 0, 32, 51, 99, 208, 164, 82, 198, 200, 202, 152, 74, 208, 149, 159, 101, 130, 172, 205, 135, 158, 16, 86, 109, 123, 7, 38, 197, 138, 177, 54, 117, 233, 144, 67, 247, 132, 163, 222, 249, 42, 54, 7, 254, 62, 97, 11, 122, 146, 226, 241, 100, 92, 88, 189, 212, 57, 123, 103, 116, 101, 29, 246, 209, 254, 162, 52, 103, 130, 226, 228, 209, 67, 179, 203, 116, 195, 6, 9, 87, 9, 139, 242, 55, 224, 156, 123, 229, 29, 138, 179, 40, 222, 116, 169, 47, 58, 176, 11, 62, 83, 177, 96, 147, 217, 5, 78, 170, 255, 107, 174, 220, 153, 198, 209, 25, 188, 228, 151, 182, 92, 146, 65, 34, 30, 56, 120, 117, 81, 130, 118, 241, 231, 126, 56, 206, 61, 124, 3, 80, 75, 3, 4, 20, 0, 0, 0, 8, 0, 179, 113, 10, 93, 54, 87, 222, 220, 162, 0, 0, 0, 24, 1, 0, 0, 11, 0, 0, 0, 95, 114, 101, 108, 115, 47, 46, 114, 101, 108, 115, 141, 207, 59, 14, 194, 48, 12, 6, 224, 171, 68, 222, 169, 11, 3, 66, 168, 105, 23, 132, 212, 21, 149, 3, 68, 137, 155, 70, 52, 15, 37, 225, 117, 123, 50, 48, 80, 196, 192, 104, 251, 247, 103, 185, 233, 30, 118, 102, 55, 138, 201, 120, 199, 97, 93, 213, 192, 200, 73, 175, 140, 211, 28, 206, 195, 113, 181, 131, 174, 109, 78, 52, 139, 92, 18, 105, 50, 33, 177, 178, 226, 18, 135, 41, 231, 176, 71, 76, 114, 34, 43, 82, 229, 3, 185, 50, 25, 125, 180, 34, 151, 50, 106, 12, 62, 94, 132, 38, 220, 212, 245, 22, 227, 167, 1, 75, 147, 245, 138, 67, 236, 213, 26, 216, 240, 12, 244, 143, 237, 199, 209, 72, 58, 120, 121, 181, 228, 242, 143, 19, 95, 137, 34, 139, 168, 41, 115, 184, 251, 168, 80, 189, 219, 85, 97, 1, 219, 6, 23, 47, 182, 47, 80, 75, 3, 4, 20, 0, 0, 0, 8, 0, 179, 113, 10, 93, 11, 225, 205, 44, 231, 0, 0, 0, 94, 1, 0, 0, 17, 0, 0, 0, 119, 111, 114, 100, 47, 100, 111, 99, 117, 109, 101, 110, 116, 46, 120, 109, 108, 109, 144, 219, 106, 195, 48, 12, 134, 95, 69, 248, 126, 113, 154, 177, 49, 66, 154, 222, 148, 222, 236, 102, 163, 221, 3, 184, 142, 18, 27, 98, 203, 200, 78, 179, 188, 253, 236, 177, 3, 140, 129, 248, 133, 144, 244, 233, 208, 29, 222, 221, 12, 55, 228, 104, 201, 239, 197, 174, 170, 5, 160, 215, 52, 88, 63, 237, 197, 219, 229, 116, 247, 36, 14, 125, 183, 182, 3, 233, 197, 161, 79, 144, 235, 125, 108, 215, 189, 48, 41, 133, 86, 202, 168, 13, 58, 21, 43, 10, 232, 115, 110, 36, 118, 42, 229, 144, 39, 185, 18, 15, 129, 73, 99, 140, 25, 231, 102, 217, 212, 245, 163, 116, 202, 122, 81, 144, 87, 26, 182, 226, 67, 17, 46, 146, 250, 243, 18, 144, 143, 164, 35, 156, 149, 11, 51, 194, 241, 107, 110, 5, 23, 99, 35, 100, 11, 138, 213, 196, 42, 24, 216, 1, 141, 144, 12, 2, 99, 32, 78, 85, 39, 11, 162, 40, 127, 106, 248, 75, 127, 249, 105, 109, 90, 120, 198, 13, 70, 235, 149, 215, 86, 205, 25, 113, 67, 191, 32, 76, 76, 107, 50, 96, 189, 102, 84, 17, 7, 184, 110, 208, 60, 64, 222, 74, 151, 235, 173, 135, 215, 251, 255, 6, 201, 239, 123, 228, 239, 175, 250, 15, 80, 75, 1, 2, 20, 3, 20, 0, 0, 0, 8, 0, 179, 113, 10, 93, 204, 84, 140, 16, 224, 0, 0, 0, 156, 1, 0, 0, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 1, 0, 0, 0, 0, 91, 67, 111, 110, 116, 101, 110, 116, 95, 84, 121, 112, 101, 115, 93, 46, 120, 109, 108, 80, 75, 1, 2, 20, 3, 20, 0, 0, 0, 8, 0, 179, 113, 10, 93, 54, 87, 222, 220, 162, 0, 0, 0, 24, 1, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 1, 17, 1, 0, 0, 95, 114, 101, 108, 115, 47, 46, 114, 101, 108, 115, 80, 75, 1, 2, 20, 3, 20, 0, 0, 0, 8, 0, 179, 113, 10, 93, 11, 225, 205, 44, 231, 0, 0, 0, 94, 1, 0, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 1, 220, 1, 0, 0, 119, 111, 114, 100, 47, 100, 111, 99, 117, 109, 101, 110, 116, 46, 120, 109, 108, 80, 75, 5, 6, 0, 0, 0, 0, 3, 0, 3, 0, 185, 0, 0, 0, 242, 2, 0, 0, 0, 0 +]; + +class InstructionScreen extends StatefulWidget { + const InstructionScreen({super.key}); + + @override + State createState() => _InstructionScreenState(); +} + +class _InstructionScreenState extends State { + final _instructionController = TextEditingController(); + final _client = SuperDocsClient(); + final _jobQueue = JobQueue(); + + bool _isSubmitting = false; + String _filename = 'document.docx'; + List _fileBytes = kValidSampleDocxBytes; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + final args = ModalRoute.of(context)?.settings.arguments; + if (args is Map) { + if (args.containsKey('filename')) { + _filename = args['filename'] as String; + } + if (args.containsKey('fileBytes') && args['fileBytes'] is List) { + _fileBytes = args['fileBytes'] as List; + } else if (args.containsKey('filePath') && args['filePath'] is String) { + final path = args['filePath'] as String; + try { + final file = File(path); + if (file.existsSync()) { + _fileBytes = file.readAsBytesSync(); + } + } catch (_) {} + } + } + } + + Future _submitInstruction() async { + final instruction = _instructionController.text.trim(); + if (instruction.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Please enter an instruction for the AI editor')), + ); + return; + } + + FocusScope.of(context).unfocus(); + setState(() => _isSubmitting = true); + + try { + // 1. Upload document (sending valid PKZip DOCX bytes to SuperDocs API) + final uploadRes = await _client.uploadDocument( + fileBytes: _fileBytes, + filename: _filename, + ); + + // 2. Start edit + final chatRes = await _client.startEdit( + sessionId: uploadRes.sessionId, + instruction: instruction, + ); + + // 3. Persist job locally + final newJob = EditJob( + jobId: chatRes.jobId, + sessionId: uploadRes.sessionId, + filename: _filename, + status: chatRes.status, + createdAt: DateTime.now(), + instruction: instruction, + ); + await _jobQueue.addJob(newJob); + + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Edit job started! Processing in background.')), + ); + Navigator.of(context).pushReplacementNamed('/home'); + } + } catch (e) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Error starting edit: $e'), + duration: const Duration(seconds: 5), + ), + ); + } + } finally { + if (mounted) { + setState(() => _isSubmitting = false); + } + } + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () => FocusScope.of(context).unfocus(), + child: Scaffold( + backgroundColor: const Color(0xFF0F172A), + resizeToAvoidBottomInset: true, + appBar: AppBar( + backgroundColor: const Color(0xFF0F172A), + elevation: 0, + title: const Text( + 'Edit Instruction', + style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold), + ), + actions: [ + IconButton( + icon: const Icon(Icons.keyboard_hide_rounded, color: Color(0xFF94A3B8)), + tooltip: 'Dismiss Keyboard', + onPressed: () => FocusScope.of(context).unfocus(), + ), + ], + ), + body: SafeArea( + child: LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + padding: const EdgeInsets.all(24.0), + child: ConstrainedBox( + constraints: BoxConstraints(minHeight: constraints.maxHeight - 48), + child: IntrinsicHeight( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: const Color(0xFF1E293B), + borderRadius: BorderRadius.circular(16), + border: Border.all(color: const Color(0xFF334155)), + ), + child: Row( + children: [ + const Icon(Icons.file_present_rounded, color: Colors.blueAccent, size: 28), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Selected File', + style: TextStyle(color: Color(0xFF94A3B8), fontSize: 12), + ), + Text( + _filename, + style: const TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + ], + ), + ), + const SizedBox(height: 24), + const Text( + 'What edit would you like to make?', + style: TextStyle( + color: Colors.white, + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 8), + const Text( + 'Describe the changes in plain language or dictate via your keyboard microphone.', + style: TextStyle(color: Color(0xFF94A3B8), fontSize: 14), + ), + const SizedBox(height: 16), + TextField( + controller: _instructionController, + minLines: 4, + maxLines: 8, + style: const TextStyle(color: Colors.white, fontSize: 16), + decoration: InputDecoration( + hintText: 'e.g. Update paragraph 2 to summarize key metrics and highlight revenue growth.', + hintStyle: const TextStyle(color: Color(0xFF64748B)), + filled: true, + fillColor: const Color(0xFF1E293B), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(16), + borderSide: BorderSide.none, + ), + contentPadding: const EdgeInsets.all(16), + ), + ), + const Spacer(), + const SizedBox(height: 16), + SizedBox( + width: double.infinity, + height: 54, + child: ElevatedButton( + onPressed: _isSubmitting ? null : _submitInstruction, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.blueAccent, + foregroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(14), + ), + elevation: 0, + ), + child: _isSubmitting + ? const CircularProgressIndicator(color: Colors.white) + : const Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.send_rounded, size: 20), + SizedBox(width: 8), + Text( + 'Go', + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + ], + ), + ), + ), + ], + ), + ), + ), + ); + }, + ), + ), + ), + ); + } +} diff --git a/extensions/naxer-12/superdocs-share-sheet-app/lib/screens/mobile-share-sheet.code-workspace b/extensions/naxer-12/superdocs-share-sheet-app/lib/screens/mobile-share-sheet.code-workspace new file mode 100644 index 00000000..d289d04f --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/lib/screens/mobile-share-sheet.code-workspace @@ -0,0 +1,11 @@ +{ + "folders": [ + { + "path": "../../../doctask-jainam-shah" + }, + { + "path": "../.." + } + ], + "settings": {} +} \ No newline at end of file diff --git a/extensions/naxer-12/superdocs-share-sheet-app/lib/screens/onboarding_screen.dart b/extensions/naxer-12/superdocs-share-sheet-app/lib/screens/onboarding_screen.dart new file mode 100644 index 00000000..2b5c14c0 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/lib/screens/onboarding_screen.dart @@ -0,0 +1,153 @@ +import 'package:flutter/material.dart'; +import '../storage/api_key_store.dart'; + +class OnboardingScreen extends StatefulWidget { + const OnboardingScreen({super.key}); + + @override + State createState() => _OnboardingScreenState(); +} + +class _OnboardingScreenState extends State { + final _keyController = TextEditingController(); + final _store = ApiKeyStore(); + bool _isLoading = false; + + @override + void initState() { + super.initState(); + _loadExistingKey(); + } + + Future _loadExistingKey() async { + final existing = await _store.getApiKey(); + if (existing != null && existing.isNotEmpty) { + _keyController.text = existing; + } + } + + Future _saveKey() async { + final key = _keyController.text.trim(); + if (key.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Please enter your SuperDocs API Key')), + ); + return; + } + + setState(() => _isLoading = true); + await _store.saveApiKey(key); + setState(() => _isLoading = false); + + if (mounted) { + FocusScope.of(context).unfocus(); + Navigator.of(context).pushReplacementNamed('/home'); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: const Color(0xFF0F172A), + resizeToAvoidBottomInset: true, + body: SafeArea( + child: LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 24.0), + child: ConstrainedBox( + constraints: BoxConstraints(minHeight: constraints.maxHeight - 48), + child: IntrinsicHeight( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 20), + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.blueAccent.withOpacity(0.15), + borderRadius: BorderRadius.circular(16), + ), + child: const Icon( + Icons.auto_fix_high_rounded, + color: Colors.blueAccent, + size: 40, + ), + ), + const SizedBox(height: 24), + const Text( + 'SuperDocs Mobile', + style: TextStyle( + color: Colors.white, + fontSize: 32, + fontWeight: FontWeight.bold, + letterSpacing: -0.5, + ), + ), + const SizedBox(height: 12), + const Text( + 'Edit documents on the go in three simple taps. Enter your SuperDocs API key to connect.', + style: TextStyle( + color: Color(0xFF94A3B8), + fontSize: 16, + height: 1.5, + ), + ), + const SizedBox(height: 32), + TextField( + controller: _keyController, + textInputAction: TextInputAction.done, + onSubmitted: (_) => _saveKey(), + style: const TextStyle(color: Colors.white, fontSize: 16), + decoration: InputDecoration( + labelText: 'API Key', + labelStyle: const TextStyle(color: Color(0xFF94A3B8)), + hintText: 'sk_...', + hintStyle: const TextStyle(color: Color(0xFF64748B)), + filled: true, + fillColor: const Color(0xFF1E293B), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide.none, + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: const BorderSide(color: Colors.blueAccent, width: 2), + ), + prefixIcon: const Icon(Icons.key_rounded, color: Colors.blueAccent), + ), + ), + const Spacer(), + const SizedBox(height: 24), + SizedBox( + width: double.infinity, + height: 54, + child: ElevatedButton( + onPressed: _isLoading ? null : _saveKey, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.blueAccent, + foregroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(14), + ), + elevation: 0, + ), + child: _isLoading + ? const CircularProgressIndicator(color: Colors.white) + : const Text( + 'Get Started', + style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600), + ), + ), + ), + ], + ), + ), + ), + ); + }, + ), + ), + ); + } +} diff --git a/extensions/naxer-12/superdocs-share-sheet-app/lib/screens/review_screen.dart b/extensions/naxer-12/superdocs-share-sheet-app/lib/screens/review_screen.dart new file mode 100644 index 00000000..4b03f177 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/lib/screens/review_screen.dart @@ -0,0 +1,276 @@ +import 'package:flutter/material.dart'; +import 'package:webview_flutter/webview_flutter.dart'; +import '../api/superdocs_client.dart'; +import '../export/export_handler.dart'; +import '../models/job_status_result.dart'; +import '../storage/job_queue.dart'; + +class ReviewScreen extends StatefulWidget { + const ReviewScreen({super.key}); + + @override + State createState() => _ReviewScreenState(); +} + +class _ReviewScreenState extends State { + final _client = SuperDocsClient(); + final _jobQueue = JobQueue(); + final _exportHandler = ExportHandler(); + + late final WebViewController _webViewController; + bool _isLoading = true; + bool _isProcessingAction = false; + String _jobId = ''; + String _sessionId = ''; + String _filename = 'document.docx'; + JobStatusResult? _statusResult; + + @override + void initState() { + super.initState(); + _webViewController = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setBackgroundColor(const Color(0xFF0F172A)); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + final args = ModalRoute.of(context)?.settings.arguments; + if (args is Map) { + _jobId = args['jobId'] as String? ?? ''; + _sessionId = args['sessionId'] as String? ?? ''; + _filename = args['filename'] as String? ?? 'edited_document.docx'; + if (_jobId.isNotEmpty) { + _fetchStatusAndRender(); + } + } + } + + Future _fetchStatusAndRender() async { + setState(() => _isLoading = true); + try { + final res = await _client.getJobStatus(jobId: _jobId); + _statusResult = res; + + final rawHtml = res.updatedHtml ?? + '

No changes preview available.

'; + + // Inject viewport meta and highlight CSS + final htmlContent = ''' + + + + + + + + $rawHtml + + +'''; + + await _webViewController.loadHtmlString(htmlContent); + } catch (e) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Error loading preview: $e')), + ); + } + } finally { + if (mounted) { + setState(() => _isLoading = false); + } + } + } + + Future _handleApprove() async { + setState(() => _isProcessingAction = true); + try { + // A job can go straight from pending to completed with no + // awaiting_approval step (auto-approved) -- confirmed live on a real + // device (2026-08-17): calling approve() on an already-completed job + // returns a real 400 ("Job is not awaiting approval"). Only call + // approve() when there's actually something pending approval. + if (_statusResult?.status == 'awaiting_approval') { + await _client.approve(sessionId: _sessionId, jobId: _jobId, approved: true); + } + await _jobQueue.updateJobStatus(_jobId, 'completed'); + + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Approved! Preparing document export...')), + ); + } + + await _exportHandler.exportAndShare( + sessionId: _sessionId, + filename: _filename, + ); + } catch (e) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Approval export error: $e')), + ); + } + } finally { + if (mounted) { + setState(() => _isProcessingAction = false); + } + } + } + + Future _handleReject() async { + setState(() => _isProcessingAction = true); + try { + if (_statusResult?.status == 'completed') { + // Same auto-approval case as _handleApprove -- an already-completed + // edit was already applied server-side and can't be un-applied via + // this endpoint. Reflect that honestly instead of sending a + // rejection the real API will 400 on. + await _jobQueue.updateJobStatus(_jobId, 'completed'); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + 'This edit was already applied automatically and cannot be rejected.', + ), + ), + ); + Navigator.of(context).pushReplacementNamed('/home'); + } + return; + } + + await _client.approve(sessionId: _sessionId, jobId: _jobId, approved: false); + await _jobQueue.updateJobStatus(_jobId, 'cancelled'); + + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Edit rejected.')), + ); + Navigator.of(context).pushReplacementNamed('/home'); + } + } catch (e) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Rejection error: $e')), + ); + } + } finally { + if (mounted) { + setState(() => _isProcessingAction = false); + } + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: const Color(0xFF0F172A), + appBar: AppBar( + backgroundColor: const Color(0xFF0F172A), + elevation: 0, + title: const Text( + 'Review Edits', + style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold), + ), + ), + body: Stack( + children: [ + Positioned.fill( + child: _isLoading + ? const Center(child: CircularProgressIndicator(color: Colors.blueAccent)) + : WebViewWidget(controller: _webViewController), + ), + Positioned( + left: 0, + right: 0, + bottom: 0, + child: Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: const Color(0xFF1E293B), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.4), + blurRadius: 16, + offset: const Offset(0, -4), + ), + ], + borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), + ), + child: SafeArea( + top: false, + child: Row( + children: [ + Expanded( + child: SizedBox( + height: 52, + child: OutlinedButton( + onPressed: _isProcessingAction ? null : _handleReject, + style: OutlinedButton.styleFrom( + foregroundColor: Colors.redAccent, + side: const BorderSide(color: Colors.redAccent, width: 1.5), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(14), + ), + ), + child: const Text( + 'Reject', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), + ), + ), + ), + const SizedBox(width: 16), + Expanded( + child: SizedBox( + height: 52, + child: ElevatedButton( + onPressed: _isProcessingAction ? null : _handleApprove, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF10B981), + foregroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(14), + ), + elevation: 0, + ), + child: _isProcessingAction + ? const CircularProgressIndicator(color: Colors.white) + : const Text( + 'Approve', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), + ), + ), + ), + ], + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/extensions/naxer-12/superdocs-share-sheet-app/lib/storage/api_key_store.dart b/extensions/naxer-12/superdocs-share-sheet-app/lib/storage/api_key_store.dart new file mode 100644 index 00000000..25079211 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/lib/storage/api_key_store.dart @@ -0,0 +1,38 @@ +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class ApiKeyStore { + static const String _keyName = 'superdocs_api_key'; + final FlutterSecureStorage _storage; + + ApiKeyStore({FlutterSecureStorage? storage}) + : _storage = storage ?? const FlutterSecureStorage(); + + Future saveApiKey(String apiKey) async { + try { + await _storage.write(key: _keyName, value: apiKey); + } catch (_) { + final prefs = await SharedPreferences.getInstance(); + await prefs.setString(_keyName, apiKey); + } + } + + Future getApiKey() async { + try { + final value = await _storage.read(key: _keyName); + if (value != null && value.isNotEmpty) return value; + } catch (_) {} + + final prefs = await SharedPreferences.getInstance(); + return prefs.getString(_keyName); + } + + Future clearApiKey() async { + try { + await _storage.delete(key: _keyName); + } catch (_) {} + + final prefs = await SharedPreferences.getInstance(); + await prefs.remove(_keyName); + } +} diff --git a/extensions/naxer-12/superdocs-share-sheet-app/lib/storage/job_queue.dart b/extensions/naxer-12/superdocs-share-sheet-app/lib/storage/job_queue.dart new file mode 100644 index 00000000..6daf2f3c --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/lib/storage/job_queue.dart @@ -0,0 +1,53 @@ +import 'dart:convert'; +import 'package:shared_preferences/shared_preferences.dart'; +import '../models/edit_job.dart'; + +class JobQueue { + static const String _queueKey = 'superdocs_job_queue'; + + Future> getJobs() async { + final prefs = await SharedPreferences.getInstance(); + final rawList = prefs.getStringList(_queueKey) ?? []; + return rawList + .map((item) { + try { + return EditJob.fromJson(jsonDecode(item) as Map); + } catch (_) { + return null; + } + }) + .whereType() + .toList(); + } + + Future addJob(EditJob job) async { + final jobs = await getJobs(); + jobs.removeWhere((j) => j.jobId == job.jobId); + jobs.insert(0, job); + await _saveJobs(jobs); + } + + Future updateJobStatus(String jobId, String status, {String? updatedHtml}) async { + final jobs = await getJobs(); + final index = jobs.indexWhere((j) => j.jobId == jobId); + if (index != -1) { + jobs[index] = jobs[index].copyWith( + status: status, + updatedHtml: updatedHtml ?? jobs[index].updatedHtml, + ); + await _saveJobs(jobs); + } + } + + Future removeJob(String jobId) async { + final jobs = await getJobs(); + jobs.removeWhere((j) => j.jobId == jobId); + await _saveJobs(jobs); + } + + Future _saveJobs(List jobs) async { + final prefs = await SharedPreferences.getInstance(); + final stringList = jobs.map((j) => jsonEncode(j.toJson())).toList(); + await prefs.setStringList(_queueKey, stringList); + } +} diff --git a/extensions/naxer-12/superdocs-share-sheet-app/macos/.gitignore b/extensions/naxer-12/superdocs-share-sheet-app/macos/.gitignore new file mode 100644 index 00000000..746adbb6 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/extensions/naxer-12/superdocs-share-sheet-app/macos/Flutter/Flutter-Debug.xcconfig b/extensions/naxer-12/superdocs-share-sheet-app/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 00000000..c2efd0b6 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/extensions/naxer-12/superdocs-share-sheet-app/macos/Flutter/Flutter-Release.xcconfig b/extensions/naxer-12/superdocs-share-sheet-app/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 00000000..c2efd0b6 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/extensions/naxer-12/superdocs-share-sheet-app/macos/Flutter/GeneratedPluginRegistrant.swift b/extensions/naxer-12/superdocs-share-sheet-app/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 00000000..4c1c0c4f --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,22 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import flutter_local_notifications +import flutter_secure_storage_macos +import share_plus +import shared_preferences_foundation +import webview_flutter_wkwebview +import workmanager_apple + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) + FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) + SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) + SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) + WebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "WebViewFlutterPlugin")) + WorkmanagerPlugin.register(with: registry.registrar(forPlugin: "WorkmanagerPlugin")) +} diff --git a/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner.xcodeproj/project.pbxproj b/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 00000000..5c7cdb1b --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,729 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* superdocs_share_sheet_app.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "superdocs_share_sheet_app.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* superdocs_share_sheet_app.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* superdocs_share_sheet_app.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.superdocs.superdocsShareSheetApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/superdocs_share_sheet_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/superdocs_share_sheet_app"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.superdocs.superdocsShareSheetApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/superdocs_share_sheet_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/superdocs_share_sheet_app"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.superdocs.superdocsShareSheetApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/superdocs_share_sheet_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/superdocs_share_sheet_app"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 00000000..676acf00 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner.xcworkspace/contents.xcworkspacedata b/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..1d526a16 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/AppDelegate.swift b/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/AppDelegate.swift new file mode 100644 index 00000000..b3c17614 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Cocoa +import FlutterMacOS + +@main +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } +} diff --git a/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..a2ec33f1 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 00000000..82b6f9d9 Binary files /dev/null and b/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 00000000..13b35eba Binary files /dev/null and b/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 00000000..0a3f5fa4 Binary files /dev/null and b/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 00000000..bdb57226 Binary files /dev/null and b/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 00000000..f083318e Binary files /dev/null and b/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 00000000..326c0e72 Binary files /dev/null and b/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 00000000..2f1632cf Binary files /dev/null and b/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/Base.lproj/MainMenu.xib b/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 00000000..80e867a4 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/Configs/AppInfo.xcconfig b/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 00000000..8ca323f0 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = superdocs_share_sheet_app + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.superdocs.superdocsShareSheetApp + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2026 com.superdocs. All rights reserved. diff --git a/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/Configs/Debug.xcconfig b/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 00000000..36b0fd94 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/Configs/Release.xcconfig b/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 00000000..dff4f495 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/Configs/Warnings.xcconfig b/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 00000000..42bcbf47 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/DebugProfile.entitlements b/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/DebugProfile.entitlements new file mode 100644 index 00000000..dddb8a30 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/Info.plist b/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/Info.plist new file mode 100644 index 00000000..4789daa6 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/MainFlutterWindow.swift b/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 00000000..3cc05eb2 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/Release.entitlements b/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/Release.entitlements new file mode 100644 index 00000000..852fa1a4 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/extensions/naxer-12/superdocs-share-sheet-app/macos/RunnerTests/RunnerTests.swift b/extensions/naxer-12/superdocs-share-sheet-app/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 00000000..61f3bd1f --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Cocoa +import FlutterMacOS +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/extensions/naxer-12/superdocs-share-sheet-app/pubspec.lock b/extensions/naxer-12/superdocs-share-sheet-app/pubspec.lock new file mode 100644 index 00000000..c8d35670 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/pubspec.lock @@ -0,0 +1,882 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: "9a3386eea899815698dd55995277cf7cb8572ee52b399a6edfb7ae2b50e5fc19" + url: "https://pub.dev" + source: hosted + version: "105.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "62993bed6eadbe9596c5c20d5c167e7bc563c5fe266657a04ddeb93bdb84f4c9" + url: "https://pub.dev" + source: hosted + version: "14.1.0" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" + async: + dependency: transitive + description: + name: async + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 + url: "https://pub.dev" + source: hosted + version: "2.13.1" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + build: + dependency: transitive + description: + name: build + sha256: b94f5da9ed3d081fd4ecc426c260998b5f4f4eb2f6d89b7e5472edef0b2b2a1b + url: "https://pub.dev" + source: hosted + version: "4.0.10" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: "31b24be6615ec7fcf70b3aa5a7469fe35826485e639a16dd7eb83ba30e4cc6a8" + url: "https://pub.dev" + source: hosted + version: "8.12.7" + characters: + dependency: transitive + description: + name: characters + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + url: "https://pub.dev" + source: hosted + version: "1.4.1" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + code_assets: + dependency: transitive + description: + name: code_assets + sha256: bf394f466ba9205f1812a0433b392d6af280f155f56651eda7c18cc32ed493b8 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: "6a6cab2ba4680d6423f34a9b972a4c9a94ebe1b62ecec4e1a1f2cba91fd1319d" + url: "https://pub.dev" + source: hosted + version: "4.11.1" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "92c9c43c383bfa1c32079d3bc492d55d6d4318044b7b47edaff8971cbb555c51" + url: "https://pub.dev" + source: hosted + version: "0.3.5+4" + crypto: + dependency: transitive + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.dev" + source: hosted + version: "3.0.7" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "3f88fc9c96c568d631356507355a2d1e983ee000c9ac008bdcb39b5bf53ce777" + url: "https://pub.dev" + source: hosted + version: "3.1.12" + dbus: + dependency: transitive + description: + name: dbus + sha256: "0ce9b0a839e6dee59a37a623d2fc26a35bbbe6404213e419b0d6411023d62645" + url: "https://pub.dev" + source: hosted + version: "0.7.14" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + flutter_local_notifications: + dependency: "direct main" + description: + name: flutter_local_notifications + sha256: "674173fd3c9eda9d4c8528da2ce0ea69f161577495a9cc835a2a4ecd7eadeb35" + url: "https://pub.dev" + source: hosted + version: "17.2.4" + flutter_local_notifications_linux: + dependency: transitive + description: + name: flutter_local_notifications_linux + sha256: c49bd06165cad9beeb79090b18cd1eb0296f4bf4b23b84426e37dd7c027fc3af + url: "https://pub.dev" + source: hosted + version: "4.0.1" + flutter_local_notifications_platform_interface: + dependency: transitive + description: + name: flutter_local_notifications_platform_interface + sha256: "85f8d07fe708c1bdcf45037f2c0109753b26ae077e9d9e899d55971711a4ea66" + url: "https://pub.dev" + source: hosted + version: "7.2.0" + flutter_secure_storage: + dependency: "direct main" + description: + name: flutter_secure_storage + sha256: "9cad52d75ebc511adfae3d447d5d13da15a55a92c9410e50f67335b6d21d16ea" + url: "https://pub.dev" + source: hosted + version: "9.2.4" + flutter_secure_storage_linux: + dependency: transitive + description: + name: flutter_secure_storage_linux + sha256: be76c1d24a97d0b98f8b54bce6b481a380a6590df992d0098f868ad54dc8f688 + url: "https://pub.dev" + source: hosted + version: "1.2.3" + flutter_secure_storage_macos: + dependency: transitive + description: + name: flutter_secure_storage_macos + sha256: "6c0a2795a2d1de26ae202a0d78527d163f4acbb11cde4c75c670f3a0fc064247" + url: "https://pub.dev" + source: hosted + version: "3.1.3" + flutter_secure_storage_platform_interface: + dependency: transitive + description: + name: flutter_secure_storage_platform_interface + sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8 + url: "https://pub.dev" + source: hosted + version: "1.1.2" + flutter_secure_storage_web: + dependency: transitive + description: + name: flutter_secure_storage_web + sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + flutter_secure_storage_windows: + dependency: transitive + description: + name: flutter_secure_storage_windows + sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + url: "https://pub.dev" + source: hosted + version: "2.1.3" + hooks: + dependency: transitive + description: + name: hooks + sha256: "9a62a50b50b769a737bc0a8ff381f333529df3ab746b2f6b02e83760231455ba" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + http: + dependency: "direct main" + description: + name: http + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.dev" + source: hosted + version: "1.6.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + intl: + dependency: "direct main" + description: + name: intl + sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf + url: "https://pub.dev" + source: hosted + version: "0.19.0" + jni: + dependency: transitive + description: + name: jni + sha256: f038e58b4dc2c9037f50e233175086337e0b305e356d28211bf55f21c504cbd3 + url: "https://pub.dev" + source: hosted + version: "1.0.3" + jni_flutter: + dependency: transitive + description: + name: jni_flutter + sha256: "7b717011ea40d04fd47c2731d3d1d36eb99eba3435c2753d62489e8c3c9991d5" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + jni_util: + dependency: transitive + description: + name: jni_util + sha256: "1ba86da04a5f2bf18fde2edb235587e70c5b0fc5bd4ba955f46b00942c3fc35f" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + lints: + dependency: transitive + description: + name: lints + sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 + url: "https://pub.dev" + source: hosted + version: "3.0.0" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: "31bd099b47c10cd1aeb55146a2d46ce0277630ecef3f7dae54ad7873f36696cd" + url: "https://pub.dev" + source: hosted + version: "0.12.20" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + url: "https://pub.dev" + source: hosted + version: "0.13.0" + meta: + dependency: transitive + description: + name: meta + sha256: c82594181e3312f3d0695fc95aaaf7758d75b8d4ae2bbecf223b9fd5109a059d + url: "https://pub.dev" + source: hosted + version: "1.18.3" + mime: + dependency: transitive + description: + name: mime + sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a" + url: "https://pub.dev" + source: hosted + version: "1.0.6" + mockito: + dependency: "direct dev" + description: + name: mockito + sha256: "6c1970612452260366064d4df80fdb3eece1ec5e52610ae34a0e51f2f3d8e64f" + url: "https://pub.dev" + source: hosted + version: "5.8.1" + objective_c: + dependency: transitive + description: + name: objective_c + sha256: b7fb95a6d9a4f009edd63dc5ac69f07420b23a16161c6dd8660290b59c602e8e + url: "https://pub.dev" + source: hosted + version: "9.5.0" + package_config: + dependency: transitive + description: + name: package_config + sha256: ffcf4cf3d6c0b74ac43708d9f56625506e8a68aa935abe9d267a7330f320eb5d + url: "https://pub.dev" + source: hosted + version: "3.0.0" + path: + dependency: "direct main" + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: a7f4874f987173da295a61c181b8ee71dab59b332a486b391babf26a1b884825 + url: "https://pub.dev" + source: hosted + version: "2.1.6" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "69cbd515a62b94d32a7944f086b2f82b4ac40a1d45bebfc00813a430ab2dabcd" + url: "https://pub.dev" + source: hosted + version: "2.3.1" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" + url: "https://pub.dev" + source: hosted + version: "2.6.0" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: "58c2005f147315b11e9b4a7bc889cd5203e250cba8e3f012dae259b4972b5c16" + url: "https://pub.dev" + source: hosted + version: "2.2.2" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "484838772624c3a4b94f1e44a3e19897fee738f2d5c4ce448443b0417f7c9dda" + url: "https://pub.dev" + source: hosted + version: "2.1.3" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675" + url: "https://pub.dev" + source: hosted + version: "7.0.2" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + receive_sharing_intent: + dependency: "direct main" + description: + name: receive_sharing_intent + sha256: "72f229a38f2910029c11d840770c173b27072e348ef3376130b37514bc4556f6" + url: "https://pub.dev" + source: hosted + version: "1.9.0" + record_use: + dependency: transitive + description: + name: record_use + sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed" + url: "https://pub.dev" + source: hosted + version: "0.6.0" + share_plus: + dependency: "direct main" + description: + name: share_plus + sha256: ef3489a969683c4f3d0239010cc8b7a2a46543a8d139e111c06c558875083544 + url: "https://pub.dev" + source: hosted + version: "9.0.0" + share_plus_platform_interface: + dependency: transitive + description: + name: share_plus_platform_interface + sha256: "0f9e4418835d1b2c3ae78fdb918251959106cefdbc4dd43526e182f80e82f6d4" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: c3025c5534b01739267eb7d76959bbc25a6d10f6988e1c2a3036940133dd10bf + url: "https://pub.dev" + source: hosted + version: "2.5.5" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "0634e64bd719f89c012f392938e173521f535d3ecaf66558fa94a056d22b5cc7" + url: "https://pub.dev" + source: hosted + version: "2.4.27" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f" + url: "https://pub.dev" + source: hosted + version: "2.5.6" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "649dc798a33931919ea356c4305c2d1f81619ea6e92244070b520187b5140ef9" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 + url: "https://pub.dev" + source: hosted + version: "2.4.3" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: a603f1fb984a7391ae5978d1b92bfaaa08b350dca5c825256f925818f7943bf5 + url: "https://pub.dev" + source: hosted + version: "4.2.4" + source_span: + dependency: transitive + description: + name: source_span + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + url: "https://pub.dev" + source: hosted + version: "1.10.2" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: "2a122cbe059f8b610d3a5415f42e255b6c17b1f21eee1d960f31080237fb4f11" + url: "https://pub.dev" + source: hosted + version: "0.7.12" + timezone: + dependency: transitive + description: + name: timezone + sha256: "2236ec079a174ce07434e89fcd3fcda430025eb7692244139a9cf54fdcf1fc7d" + url: "https://pub.dev" + source: hosted + version: "0.9.4" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a + url: "https://pub.dev" + source: hosted + version: "3.2.2" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "85c81589622fbc87c1c683aaea164d3604a7777495a79d91e39ffcdec39ddb34" + url: "https://pub.dev" + source: hosted + version: "2.4.3" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" + url: "https://pub.dev" + source: hosted + version: "3.1.5" + uuid: + dependency: "direct main" + description: + name: uuid + sha256: "9b129329f58692f6e6578329498a8fe9fbe98f090beb764ffbb8ee2eadd01dcd" + url: "https://pub.dev" + source: hosted + version: "4.6.0" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "1d774bbdf6b72a0b12122fc1560c9c2d2a67db5a4a4cc2bd8a5c990ab20e3188" + url: "https://pub.dev" + source: hosted + version: "2.4.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" + url: "https://pub.dev" + source: hosted + version: "15.2.0" + watcher: + dependency: transitive + description: + name: watcher + sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + web: + dependency: transitive + description: + name: web + sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + url: "https://pub.dev" + source: hosted + version: "0.5.1" + webview_flutter: + dependency: "direct main" + description: + name: webview_flutter + sha256: d53e1ccf5516f25017e3c9d44c39034db352d20fa34fe200674270242c2c5111 + url: "https://pub.dev" + source: hosted + version: "4.14.1" + webview_flutter_android: + dependency: transitive + description: + name: webview_flutter_android + sha256: b98656fa4461f8cc05c48a778b4d4883e60ec63e1778348f363f9bb9a477745d + url: "https://pub.dev" + source: hosted + version: "4.14.0" + webview_flutter_platform_interface: + dependency: transitive + description: + name: webview_flutter_platform_interface + sha256: "1221c1b12f5278791042f2ec2841743784cf25c5a644e23d6680e5d718824f04" + url: "https://pub.dev" + source: hosted + version: "2.15.1" + webview_flutter_wkwebview: + dependency: transitive + description: + name: webview_flutter_wkwebview + sha256: c879dd64b87c452aa84381b244d5469da57ba7e8cca6884c7b1e0d406372c12d + url: "https://pub.dev" + source: hosted + version: "3.26.0" + win32: + dependency: transitive + description: + name: win32 + sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e + url: "https://pub.dev" + source: hosted + version: "5.15.0" + workmanager: + dependency: "direct main" + description: + name: workmanager + sha256: "06fc5bdd83e2da35f33b682e25167245d0ccb8b56e9a9ada5bfa079fa3af4b9c" + url: "https://pub.dev" + source: hosted + version: "0.10.7" + workmanager_android: + dependency: transitive + description: + name: workmanager_android + sha256: f89029cbcf0695c772287aa8fbbd7af776f1ea63bc95b60a00617f69468ddaa0 + url: "https://pub.dev" + source: hosted + version: "0.10.6" + workmanager_apple: + dependency: transitive + description: + name: workmanager_apple + sha256: "15558fb54415a010910c3529eaf8f61f94d23903dac620331895c472602d6f6e" + url: "https://pub.dev" + source: hosted + version: "0.9.10" + workmanager_linux: + dependency: transitive + description: + name: workmanager_linux + sha256: "7d8520e1103b910a43d8bafe37ec2415f4c7c148afcb1eee0ec73ae67b4cdd22" + url: "https://pub.dev" + source: hosted + version: "0.1.1+1" + workmanager_platform_interface: + dependency: transitive + description: + name: workmanager_platform_interface + sha256: "378b392db15ee88cd878ab983d3a0b4b1e460cfbb0f096ff74fda2614b1ba172" + url: "https://pub.dev" + source: hosted + version: "0.10.4" + workmanager_web: + dependency: transitive + description: + name: workmanager_web + sha256: "2ac481ade599bdd957ee2891be09a0c34be87923a2c3ca4dfe36add6fc735ffd" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + xml: + dependency: transitive + description: + name: xml + sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" + url: "https://pub.dev" + source: hosted + version: "6.6.1" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.dev" + source: hosted + version: "3.1.3" +sdks: + dart: ">=3.12.0 <4.0.0" + flutter: ">=3.44.0" diff --git a/extensions/naxer-12/superdocs-share-sheet-app/pubspec.yaml b/extensions/naxer-12/superdocs-share-sheet-app/pubspec.yaml new file mode 100644 index 00000000..cf64d679 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/pubspec.yaml @@ -0,0 +1,32 @@ +name: superdocs_share_sheet_app +description: "A Flutter mobile app for instant document editing via SuperDocs API and OS Share Sheet." +publish_to: 'none' +version: 1.0.0+1 + +environment: + sdk: '>=3.0.0 <4.0.0' + +dependencies: + flutter: + sdk: flutter + http: ^1.2.0 + flutter_secure_storage: ^9.0.0 + shared_preferences: ^2.2.2 + receive_sharing_intent: ^1.8.0 + workmanager: ^0.10.0 + flutter_local_notifications: ^17.0.0 + webview_flutter: ^4.7.0 + share_plus: ^9.0.0 + path_provider: ^2.1.2 + path: ^1.9.0 + intl: ^0.19.0 + uuid: ^4.4.0 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^3.0.0 + mockito: ^5.4.4 + +flutter: + uses-material-design: true diff --git a/extensions/naxer-12/superdocs-share-sheet-app/test/api/superdocs_client_test.dart b/extensions/naxer-12/superdocs-share-sheet-app/test/api/superdocs_client_test.dart new file mode 100644 index 00000000..f85519e0 --- /dev/null +++ b/extensions/naxer-12/superdocs-share-sheet-app/test/api/superdocs_client_test.dart @@ -0,0 +1,162 @@ +import 'dart:convert'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:http/http.dart' as http; +import 'package:http/testing.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:superdocs_share_sheet_app/api/superdocs_client.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + setUp(() { + SharedPreferences.setMockInitialValues({ + 'superdocs_api_key': 'test_key_123', + }); + }); + + group('SuperDocsClient Unit Tests', () { + test('uploadDocument generates and sends a session_id, sends file_base64', () async { + // Verified live against the real API (2026-08-17): a real + // upload-base64 call with no session_id does NOT persist and returns + // no usable session_id at all -- the client must always generate and + // send one itself. Also verified the real request field is + // file_base64, not document_html. + final mockClient = MockClient((request) async { + expect(request.url.path, '/v1/documents/upload-base64'); + expect(request.method, 'POST'); + final body = jsonDecode(request.body) as Map; + expect(body['return_html'], true); + expect(body['filename'], 'test.docx'); + expect(body['file_base64'], isNotEmpty); + expect(body['session_id'], isNotNull); + expect(body['session_id'], isNotEmpty); + + return http.Response( + jsonEncode({ + 'session_id': body['session_id'], + 'html': '

Test Document

', + 'filename': 'test.docx', + 'chunks_count': 5, + 'persisted': true, + }), + 200, + ); + }); + + final client = SuperDocsClient( + httpClient: mockClient, + baseUrl: 'https://api.superdocs.app', + ); + + final result = await client.uploadDocument( + fileBytes: [10, 20, 30], + filename: 'test.docx', + ); + + // session_id is client-generated (a UUID) -- assert shape, not a + // fixed value, since a real client-supplied session_id is echoed + // straight back by the real API. + expect(result.sessionId, isNotEmpty); + expect(result.html, '

Test Document

'); + expect(result.chunksCount, 5); + }); + + test('startEdit posts session_id and message', () async { + final mockClient = MockClient((request) async { + expect(request.url.path, '/v1/chat/async'); + final body = jsonDecode(request.body) as Map; + expect(body['session_id'], 'sess_123'); + expect(body['message'], 'Make paragraph 1 concise'); + + return http.Response( + jsonEncode({ + 'job_id': 'job_999', + 'session_id': 'sess_123', + 'status': 'pending', + }), + 200, + ); + }); + + final client = SuperDocsClient(httpClient: mockClient); + final result = await client.startEdit( + sessionId: 'sess_123', + instruction: 'Make paragraph 1 concise', + ); + + expect(result.jobId, 'job_999'); + expect(result.status, 'pending'); + }); + + test('getJobStatus parses status and updated html diffs', () async { + // Real shape, verified live against the real API (2026-08-17): + // document_changes is nested under "result", not top-level. + final mockClient = MockClient((request) async { + expect(request.url.path, '/v1/jobs/job_999'); + return http.Response( + jsonEncode({ + 'status': 'awaiting_approval', + 'result': { + 'document_changes': { + 'updated_html': '

Updated Heading

', + 'chunk_diffs': [ + {'chunk_id': 'c1', 'action': 'update', 'content': 'Updated Heading'} + ] + } + } + }), + 200, + ); + }); + + final client = SuperDocsClient(httpClient: mockClient); + final result = await client.getJobStatus(jobId: 'job_999'); + + expect(result.status, 'awaiting_approval'); + expect(result.updatedHtml, '

Updated Heading

'); + expect(result.changedChunkIds, contains('c1')); + }); + + test('approve sends job_id and approval flag', () async { + // job_id is required by the real ApprovalRequest schema, verified + // live (2026-08-17) -- a request without it 422s for real. + final mockClient = MockClient((request) async { + expect(request.url.path, '/v1/chat/sess_123/approve'); + final body = jsonDecode(request.body) as Map; + expect(body['job_id'], 'job_999'); + expect(body['approved'], true); + return http.Response(jsonEncode({'status': 'approved'}), 200); + }); + + final client = SuperDocsClient(httpClient: mockClient); + await expectLater( + client.approve(sessionId: 'sess_123', jobId: 'job_999', approved: true), + completes, + ); + }); + + test('export parses filename from Content-Disposition with both filename forms', () async { + // Real header carries both forms on one line, verified live + // (2026-08-17): filename="..." AND filename*=UTF-8''... together. A + // naive split on "filename=" corrupts the parsed name by picking up + // the second form's junk too -- this exercises that exact case. + final mockClient = MockClient((request) async { + expect(request.url.path, '/v1/documents/export'); + return http.Response.bytes( + [80, 75, 3, 4], + 200, + headers: { + 'content-disposition': + 'attachment; filename="edited.docx"; filename*=UTF-8\'\'edited.docx', + }, + ); + }); + + final client = SuperDocsClient(httpClient: mockClient); + final result = await client.export(sessionId: 'sess_123', format: 'docx'); + + expect(result.fileBytes, [80, 75, 3, 4]); + expect(result.filename, 'edited.docx'); + }); + }); +} diff --git a/use-cases/naxer-12/battlecard-generator/.gitignore b/use-cases/naxer-12/battlecard-generator/.gitignore new file mode 100644 index 00000000..92fdc8f8 --- /dev/null +++ b/use-cases/naxer-12/battlecard-generator/.gitignore @@ -0,0 +1,10 @@ +venv/ +__pycache__/ +*.pyc +.pytest_cache/ +output/*.docx +output/*.pdf +output/*.html +output/*.md +output/*.txt +!output/.gitkeep diff --git a/use-cases/naxer-12/battlecard-generator/README.md b/use-cases/naxer-12/battlecard-generator/README.md new file mode 100644 index 00000000..a41f91eb --- /dev/null +++ b/use-cases/naxer-12/battlecard-generator/README.md @@ -0,0 +1,97 @@ +# Sales Battlecard Generator + +Built for SuperDocs' Task 2, assigned build 2. Feed it a fictional competitor's name plus a few +structured inputs (their likely pitch, where we win, where we don't), and it produces a +consistent, exportable one-page battlecard on a fixed template — a fast, rep-facing answer to "why +not them," not a quarterly win-loss retrospective. + +## What it does + +1. Builds a one-page battlecard from a fixed template: title, then four sections — **Their + Pitch**, **Where We Win**, **Where We Don't**, **Talk Track**. +2. Fills in everything except the last section directly, with plain text substitution. No AI call + needed for text the caller already handed over verbatim. +3. Uploads that partially-filled document to a fresh SuperDocs session and asks it to write the + one thing that actually needs judgment: a single talk-track sentence synthesizing the win/loss + points into something a rep could say out loud. +4. Polls the edit to completion, exports the finished `.docx`, and saves it. + +## Why it's split that way (a real design change, not the original plan) + +The first version asked SuperDocs to fill in all four sections in one instruction. It worked, then +on the very next run with the identical instruction it only filled in one of the four sections and +left three as literal placeholder text. Confirmed directly: two calls, same input, different +completeness. That's a real characteristic of the live model, not a bug in this code — verified by +inspecting the raw job response from both runs. + +The fix wasn't a retry loop. It was noticing that three of the four sections don't need an AI at +all — "Their Pitch," "Where We Win," and "Where We Don't" are just the caller's own text, copied +into place. Only "Talk Track" requires synthesizing two things into one new sentence. So that's the +only part that goes through SuperDocs now. Fewer things asked of the model, less for it to +inconsistently skip, and it's a more honest use of what SuperDocs is actually good at. + +## Running it + +```bash +python3 -m venv venv +./venv/bin/pip install -r requirements.txt + +./venv/bin/python cli.py \ + --api-key sk_... \ + --competitor "Zephyr Analytics" \ + --pitch "Fastest dashboard to set up, no engineering needed." \ + --win "Our reports stay accurate automatically when the data model changes; theirs silently drift." \ + --dont-win "Their onboarding really is faster for a first, simple dashboard." +``` + +Or set `SUPERDOCS_API_KEY` instead of passing `--api-key`. Output lands in `output/` by default +(`--output-dir` to change it), named `battlecard-.docx`. + +## Running the tests + +```bash +./venv/bin/python -m pytest -v +``` + +15 tests, all keyless — the HTTP layer is mocked with `responses`, but every mocked shape matches +what the real API actually returns (verified live against a real account, not guessed from docs; +see `superdocs_client.py`'s module docstring for the specific gotchas that came out of that: +`session_id` isn't auto-generated, `document_changes` is nested under `result`, `approve` needs +`job_id`, a job can auto-complete with no approval step, and `Content-Disposition` can carry two +filename forms on one line). + +## Proof it satisfies the brief's own bar + +> "Two different fictional competitor inputs produce battlecards that read as genuinely different +> documents rather than one paragraph with a name swapped, and every card exports cleanly from the +> same template." + +Ran it twice for real against the live API, with two unrelated fictional competitors (Zephyr +Analytics, a dashboard tool; Brightfield Governance, a compliance-focused platform). Both exported +cleanly. The talk-track sentences are genuinely different, each grounded in that competitor's own +win/loss points, not a template with a name swapped in: + +- *Zephyr*: "While their onboarding is faster for simple dashboards, Zephyr Analytics keeps your + reports accurate automatically when data models change, so you never have to worry about silent + drift before a board meeting." +- *Brightfield*: "While they may have more extensive compliance certifications, we deliver new + integrations in days instead of quarters and guarantee same-day support responses." + +## Known limitations + +- The AI-generated talk track is only lightly guarded against inventing claims beyond the given + win/loss points — the instruction asks it not to, but nothing downstream verifies that + mechanically. For a real deployment, a grounding check similar to Task 1's (does the talk track's + claim actually trace back to the win/don't-win input) would be the next real improvement, not a + cosmetic one. +- Export format is whatever SuperDocs' `/v1/documents/export` supports (`docx`, `pdf`, `html`, + `markdown`, `txt`) — no format-specific post-processing is done here. +- One competitor per run. Batch generation from a CSV of competitors would be a small, real + addition, not built here since it wasn't asked for. + +## Where this goes + +Per the brief, this build's finished work belongs in a pull request to the public +`github.com/superdocsapp/superdocs-builds` repository, `use-cases/` folder — this repo +(`doctask-jainam-shah`) is Task 1's private repo. This folder is laid out to match that destination +folder name directly so moving it later is a straight copy, not a restructure. diff --git a/use-cases/naxer-12/battlecard-generator/battlecard/__init__.py b/use-cases/naxer-12/battlecard-generator/battlecard/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/use-cases/naxer-12/battlecard-generator/battlecard/generator.py b/use-cases/naxer-12/battlecard-generator/battlecard/generator.py new file mode 100644 index 00000000..fb39b64f --- /dev/null +++ b/use-cases/naxer-12/battlecard-generator/battlecard/generator.py @@ -0,0 +1,74 @@ +"""Orchestrates one battlecard: fill the known fields locally, upload, +ask SuperDocs for the one sentence that needs real synthesis, export. + +Deliberately a plain function, not a class with state -- each call is a +fresh session against a fresh copy of the template, so two competitors run +back to back can never bleed content into each other. +""" + +from dataclasses import dataclass +from pathlib import Path + +from .superdocs_client import SuperDocsClient +from .template import build_filled_template_bytes + + +@dataclass +class CompetitorInput: + name: str + likely_pitch: str + where_we_win: str + where_we_dont: str + + +def _build_instruction(competitor: CompetitorInput) -> str: + """Scoped to exactly one section on purpose -- see template.py's module + docstring for why the other three fields are filled in locally instead + of asked of the model.""" + return ( + f'Replace the placeholder text under the "Talk Track" heading with a single short ' + f"sentence a sales rep could say out loud when a prospect brings up {competitor.name}. " + f"Base it only on these two points, don't invent new claims: " + f'we win because "{competitor.where_we_win}"; they\'re stronger because ' + f'"{competitor.where_we_dont}". Do not change any other section, heading, or the title.' + ) + + +def generate_battlecard( + client: SuperDocsClient, + competitor: CompetitorInput, + output_dir: Path, + export_format: str = "docx", +) -> Path: + """Runs one competitor through the full real flow and writes the + exported file to output_dir. Returns the path written.""" + template_bytes = build_filled_template_bytes( + competitor_name=competitor.name, + likely_pitch=competitor.likely_pitch, + where_we_win=competitor.where_we_win, + where_we_dont=competitor.where_we_dont, + ) + + upload = client.upload_document( + file_bytes=template_bytes, + filename=f"battlecard-{competitor.name}.docx", + ) + + instruction = _build_instruction(competitor) + job = client.start_edit(session_id=upload.session_id, instruction=instruction) + status = client.wait_for_completion(job.job_id) + + if status.status == "awaiting_approval": + client.approve(session_id=upload.session_id, job_id=job.job_id, approved=True) + elif status.status == "failed": + raise RuntimeError(f"edit failed for {competitor.name}: {status.raw}") + # status.status == "completed" already -- calling approve() here would + # 400 (verified live against the real API on a different SuperDocs build). + + export = client.export(session_id=upload.session_id, format=export_format) + + output_dir.mkdir(parents=True, exist_ok=True) + safe_name = "".join(c if c.isalnum() or c in "-_" else "_" for c in competitor.name) + out_path = output_dir / f"battlecard-{safe_name}.{export_format}" + out_path.write_bytes(export.file_bytes) + return out_path diff --git a/use-cases/naxer-12/battlecard-generator/battlecard/superdocs_client.py b/use-cases/naxer-12/battlecard-generator/battlecard/superdocs_client.py new file mode 100644 index 00000000..8987d0c7 --- /dev/null +++ b/use-cases/naxer-12/battlecard-generator/battlecard/superdocs_client.py @@ -0,0 +1,170 @@ +"""Thin client for the four SuperDocs REST calls this tool needs. + +Every request/response shape here was verified against the real API (not +guessed from docs) while building a different SuperDocs build earlier in +this project: upload-base64 needs a client-generated session_id or it +never persists; chat/async's job result is nested under result.document_changes, +not top-level; approve requires job_id, not just approved; a job can go +straight from pending to completed with no awaiting_approval step; and +Content-Disposition can carry both filename="..." and filename*=UTF-8''... +on the same line, so only the quoted form should be parsed. +""" + +from __future__ import annotations + +import base64 +import re +import time +import uuid +from dataclasses import dataclass +from typing import Optional + +import requests + +DEFAULT_BASE_URL = "https://api.superdocs.app" + + +class SuperDocsError(Exception): + """Raised on any non-2xx response, with the real status/body attached.""" + + def __init__(self, message: str, status_code: int, body: str): + super().__init__(message) + self.status_code = status_code + self.body = body + + +@dataclass +class UploadResult: + session_id: str + filename: str + persisted: bool + chunks_count: Optional[int] = None + version_id: Optional[str] = None + + +@dataclass +class ChatJobResult: + job_id: str + session_id: str + status: str + + +@dataclass +class JobStatusResult: + status: str + updated_html: Optional[str] + raw: dict + + +@dataclass +class ExportResult: + file_bytes: bytes + filename: str + content_type: str + + +class SuperDocsClient: + def __init__(self, api_key: str, base_url: str = DEFAULT_BASE_URL, session: Optional[requests.Session] = None): + self.api_key = api_key + self.base_url = base_url.rstrip("/") + self._session = session or requests.Session() + + def _headers(self) -> dict: + return {"Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json"} + + def _raise_for_status(self, resp: requests.Response) -> None: + if not (200 <= resp.status_code < 300): + raise SuperDocsError( + f"{resp.request.method} {resp.request.url} failed with {resp.status_code}", + resp.status_code, + resp.text, + ) + + def upload_document(self, file_bytes: bytes, filename: str, session_id: Optional[str] = None) -> UploadResult: + """Uploads a file and persists it under a session. session_id is + generated here if not supplied -- the real API does NOT auto-generate + one, and a call without it never persists (verified live).""" + sid = session_id or str(uuid.uuid4()) + resp = self._session.post( + f"{self.base_url}/v1/documents/upload-base64", + headers=self._headers(), + json={ + "filename": filename, + "file_base64": base64.b64encode(file_bytes).decode("ascii"), + "session_id": sid, + "return_html": True, + }, + ) + self._raise_for_status(resp) + data = resp.json() + return UploadResult( + session_id=data.get("session_id", sid), + filename=data.get("filename", filename), + persisted=data.get("persisted", False), + chunks_count=data.get("chunks_count"), + version_id=data.get("version_id"), + ) + + def start_edit(self, session_id: str, instruction: str) -> ChatJobResult: + resp = self._session.post( + f"{self.base_url}/v1/chat/async", + headers=self._headers(), + json={"message": instruction, "session_id": session_id}, + ) + self._raise_for_status(resp) + data = resp.json() + return ChatJobResult(job_id=data["job_id"], session_id=data["session_id"], status=data["status"]) + + def get_job_status(self, job_id: str) -> JobStatusResult: + resp = self._session.get(f"{self.base_url}/v1/jobs/{job_id}", headers=self._headers()) + self._raise_for_status(resp) + data = resp.json() + result = data.get("result") or {} + document_changes = result.get("document_changes") or {} + return JobStatusResult( + status=data["status"], + updated_html=document_changes.get("updated_html"), + raw=data, + ) + + def wait_for_completion(self, job_id: str, timeout_s: float = 120.0, poll_interval_s: float = 2.0) -> JobStatusResult: + deadline = time.monotonic() + timeout_s + terminal = {"awaiting_approval", "completed", "failed", "cancelled"} + while True: + status = self.get_job_status(job_id) + if status.status in terminal: + return status + if time.monotonic() > deadline: + raise TimeoutError(f"job {job_id} did not finish within {timeout_s}s (last status: {status.status})") + time.sleep(poll_interval_s) + + def approve(self, session_id: str, job_id: str, approved: bool) -> None: + """No-op if the job already auto-completed -- calling approve() on an + already-completed job returns a real 400 (verified live), so callers + should check status before calling this, not call it unconditionally.""" + resp = self._session.post( + f"{self.base_url}/v1/chat/{session_id}/approve", + headers=self._headers(), + json={"job_id": job_id, "approved": approved}, + ) + self._raise_for_status(resp) + + def export(self, session_id: str, format: str = "docx") -> ExportResult: + resp = self._session.post( + f"{self.base_url}/v1/documents/export", + headers=self._headers(), + json={"session_id": session_id, "format": format}, + ) + self._raise_for_status(resp) + + filename = f"exported_document.{format}" + disposition = resp.headers.get("content-disposition", "") + match = re.search(r'filename="([^"]+)"', disposition) + if match: + filename = match.group(1) + + return ExportResult( + file_bytes=resp.content, + filename=filename, + content_type=resp.headers.get("content-type", "application/octet-stream"), + ) diff --git a/use-cases/naxer-12/battlecard-generator/battlecard/template.py b/use-cases/naxer-12/battlecard-generator/battlecard/template.py new file mode 100644 index 00000000..543849c9 --- /dev/null +++ b/use-cases/naxer-12/battlecard-generator/battlecard/template.py @@ -0,0 +1,60 @@ +"""Builds the fixed one-page battlecard every competitor's card is generated +from. The brief's own bar is that two different competitor inputs must read +as genuinely different documents, not one paragraph with a name swapped -- +satisfied here by keeping the structure (these four sections, in this +order) fixed while the content underneath changes per competitor. + +Design note, found by testing this live rather than assumed: asking the AI +to copy three pieces of already-known text into three placeholders, in the +same call, was unreliable in practice -- two runs of the identical +instruction produced different numbers of sections actually filled in. The +three fields the caller already knows verbatim (the title, "Their Pitch", +"Where We Win", "Where We Don't") are filled in here directly, with plain +text substitution -- no model call, no chance of it skipping one. Only +"Talk Track" -- the one field that genuinely requires synthesizing the +win/don't-win points into a single sentence -- is left as a real AI edit, +in generator.py. That's a better division of labor anyway: SuperDocs' real +value here is the one sentence that needs judgment, not retyping text the +caller already handed over. +""" + +from io import BytesIO + +import docx + +# (heading, ai_placeholder) -- ai_placeholder is only used for the one +# section still filled in by the model. +SECTIONS = [ + ("Their Pitch", None), + ("Where We Win", None), + ("Where We Don't", None), + ("Talk Track", "PLACEHOLDER: One line a rep can say out loud when this competitor comes up."), +] + + +def build_filled_template_bytes(competitor_name: str, likely_pitch: str, where_we_win: str, where_we_dont: str) -> bytes: + """Returns the template as real .docx bytes with everything except the + Talk Track section already filled in deterministically -- no AI call + involved in getting these four fields right.""" + document = docx.Document() + + document.add_heading(f"{competitor_name} — Battlecard", level=1) + subtitle = document.add_paragraph(f"Internal sales reference. {competitor_name}, for demo purposes only.") + subtitle.runs[0].italic = True + + field_values = { + "Their Pitch": likely_pitch, + "Where We Win": where_we_win, + "Where We Don't": where_we_dont, + } + + for heading, ai_placeholder in SECTIONS: + document.add_heading(heading, level=2) + if heading in field_values: + document.add_paragraph(field_values[heading]) + else: + document.add_paragraph(ai_placeholder) + + buffer = BytesIO() + document.save(buffer) + return buffer.getvalue() diff --git a/use-cases/naxer-12/battlecard-generator/cli.py b/use-cases/naxer-12/battlecard-generator/cli.py new file mode 100644 index 00000000..c1f7c578 --- /dev/null +++ b/use-cases/naxer-12/battlecard-generator/cli.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +"""CLI for generating one sales battlecard against a fictional competitor. + +Usage: + python cli.py \ + --api-key sk_... \ + --competitor "Zephyr Analytics" \ + --pitch "Sells itself as the fastest dashboard to set up, no engineering needed." \ + --win "Our reports stay accurate when the underlying data model changes; theirs silently drift." \ + --dont-win "Their onboarding is genuinely faster for a first dashboard out of the box." + +The API key can also be set via the SUPERDOCS_API_KEY environment variable. +""" + +import argparse +import os +import sys +from pathlib import Path + +from battlecard.generator import CompetitorInput, generate_battlecard +from battlecard.superdocs_client import SuperDocsClient, SuperDocsError + +DEFAULT_OUTPUT_DIR = Path(__file__).parent / "output" + + +def main() -> int: + parser = argparse.ArgumentParser(description="Generate a one-page sales battlecard against a fictional competitor.") + parser.add_argument("--competitor", required=True, help="Fictional competitor name") + parser.add_argument("--pitch", required=True, help="What they likely tell a prospect about themselves") + parser.add_argument("--win", required=True, help="Where we win against them") + parser.add_argument("--dont-win", required=True, help="Honest gaps where they're stronger") + parser.add_argument("--api-key", default=os.getenv("SUPERDOCS_API_KEY"), help="SuperDocs API key (or set SUPERDOCS_API_KEY)") + parser.add_argument("--format", default="docx", choices=["docx", "pdf", "html", "markdown", "txt"], help="Export format") + parser.add_argument("--output-dir", default=str(DEFAULT_OUTPUT_DIR), help="Directory to write the exported card into") + args = parser.parse_args() + + if not args.api_key: + print("error: no API key. Pass --api-key or set SUPERDOCS_API_KEY.", file=sys.stderr) + return 1 + + client = SuperDocsClient(api_key=args.api_key) + competitor = CompetitorInput( + name=args.competitor, + likely_pitch=args.pitch, + where_we_win=args.win, + where_we_dont=args.dont_win, + ) + + try: + out_path = generate_battlecard(client, competitor, Path(args.output_dir), export_format=args.format) + except SuperDocsError as exc: + print(f"error: SuperDocs API call failed ({exc.status_code}): {exc.body}", file=sys.stderr) + return 1 + except TimeoutError as exc: + print(f"error: {exc}", file=sys.stderr) + return 1 + + print(f"wrote {out_path}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/use-cases/naxer-12/battlecard-generator/output/.gitkeep b/use-cases/naxer-12/battlecard-generator/output/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/use-cases/naxer-12/battlecard-generator/requirements.txt b/use-cases/naxer-12/battlecard-generator/requirements.txt new file mode 100644 index 00000000..bab55c0c --- /dev/null +++ b/use-cases/naxer-12/battlecard-generator/requirements.txt @@ -0,0 +1,4 @@ +requests>=2.31 +python-docx>=1.1 +pytest>=8.0 +responses>=0.25 diff --git a/use-cases/naxer-12/battlecard-generator/tests/__init__.py b/use-cases/naxer-12/battlecard-generator/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/use-cases/naxer-12/battlecard-generator/tests/test_generator.py b/use-cases/naxer-12/battlecard-generator/tests/test_generator.py new file mode 100644 index 00000000..e9bb78ca --- /dev/null +++ b/use-cases/naxer-12/battlecard-generator/tests/test_generator.py @@ -0,0 +1,102 @@ +"""Tests for the orchestration logic in generator.py, against a fake client +(not real HTTP) -- these are testing OUR logic: does it call approve() only +when needed, does it name the output file correctly, does it scope the AI +instruction to only the one section that needs real synthesis. +""" + +import pytest + +from battlecard.generator import CompetitorInput, _build_instruction, generate_battlecard +from battlecard.superdocs_client import ChatJobResult, ExportResult, JobStatusResult, UploadResult + + +class FakeClient: + """Records every call so tests can assert on exactly what happened, + without touching the network.""" + + def __init__(self, job_status: str): + self.job_status = job_status + self.approve_calls = [] + self.uploaded_filenames = [] + self.instructions = [] + + def upload_document(self, file_bytes, filename): + self.uploaded_filenames.append(filename) + return UploadResult(session_id="sess_fake", filename=filename, persisted=True) + + def start_edit(self, session_id, instruction): + self.instructions.append(instruction) + return ChatJobResult(job_id="job_fake", session_id=session_id, status="pending") + + def wait_for_completion(self, job_id, **kwargs): + return JobStatusResult(status=self.job_status, updated_html="

done

", raw={}) + + def approve(self, session_id, job_id, approved): + self.approve_calls.append((session_id, job_id, approved)) + + def export(self, session_id, format="docx"): + return ExportResult(file_bytes=b"fake-exported-bytes", filename="whatever.docx", content_type="application/octet-stream") + + +ZEPHYR = CompetitorInput( + name="Zephyr Analytics", + likely_pitch="Fastest dashboard to set up, no engineering needed.", + where_we_win="Reports stay accurate when the data model changes.", + where_we_dont="Their onboarding is genuinely faster out of the box.", +) + +BRIGHTFIELD = CompetitorInput( + name="Brightfield", + likely_pitch="Enterprise-grade governance and audit trails.", + where_we_win="We ship new integrations in days, not quarters.", + where_we_dont="Their compliance certifications are more extensive.", +) + + +def test_skips_approve_when_job_already_auto_completed(tmp_path): + """Real bug found live on a different SuperDocs build: calling approve() + on an already-completed job 400s. The generator must check status first.""" + client = FakeClient(job_status="completed") + generate_battlecard(client, ZEPHYR, tmp_path) + assert client.approve_calls == [] + + +def test_calls_approve_when_job_is_awaiting_approval(tmp_path): + client = FakeClient(job_status="awaiting_approval") + generate_battlecard(client, ZEPHYR, tmp_path) + assert client.approve_calls == [("sess_fake", "job_fake", True)] + + +def test_raises_on_failed_job(tmp_path): + client = FakeClient(job_status="failed") + with pytest.raises(RuntimeError): + generate_battlecard(client, ZEPHYR, tmp_path) + + +def test_writes_output_file_named_after_competitor(tmp_path): + client = FakeClient(job_status="completed") + out_path = generate_battlecard(client, ZEPHYR, tmp_path, export_format="docx") + assert out_path.name == "battlecard-Zephyr_Analytics.docx" + assert out_path.read_bytes() == b"fake-exported-bytes" + + +def test_ai_instruction_is_scoped_to_only_the_talk_track_section(): + """Found by testing live: asking the model to fill three known fields + plus synthesize one, in a single call, was unreliable across identical + runs. The known fields are templated locally (see test_template.py); + the AI is only asked for the one sentence that needs real judgment.""" + instruction = _build_instruction(ZEPHYR) + assert "Talk Track" in instruction + assert "Their Pitch" not in instruction + assert "Where We Win" not in instruction + assert ZEPHYR.where_we_win in instruction + assert ZEPHYR.where_we_dont in instruction + assert "Do not change any other section" in instruction + + +def test_two_competitors_produce_different_instructions(): + zephyr_instruction = _build_instruction(ZEPHYR) + brightfield_instruction = _build_instruction(BRIGHTFIELD) + assert zephyr_instruction != brightfield_instruction + assert ZEPHYR.where_we_win in zephyr_instruction + assert ZEPHYR.where_we_win not in brightfield_instruction diff --git a/use-cases/naxer-12/battlecard-generator/tests/test_superdocs_client.py b/use-cases/naxer-12/battlecard-generator/tests/test_superdocs_client.py new file mode 100644 index 00000000..3587d8dc --- /dev/null +++ b/use-cases/naxer-12/battlecard-generator/tests/test_superdocs_client.py @@ -0,0 +1,135 @@ +"""Keyless tests for SuperDocsClient -- the HTTP layer is mocked with +`responses`, but every mocked response shape here matches what the real API +actually returns, verified live against a real account while building a +different SuperDocs tool earlier in this project. These are not guessed +shapes. +""" + +import responses + +from battlecard.superdocs_client import SuperDocsClient, SuperDocsError + +BASE = "https://api.superdocs.app" + + +@responses.activate +def test_upload_document_generates_and_sends_a_session_id(): + """The real API does not auto-generate a session_id -- omitting it means + the upload never persists and no session_id comes back at all. The + client must always generate and send one.""" + captured = {} + + def handler(request): + import json + body = json.loads(request.body) + captured.update(body) + return (200, {}, json.dumps({ + "html": None, + "session_id": body["session_id"], + "filename": body["filename"], + "chunks_count": 5, + "version_id": "v1", + "persisted": True, + })) + + responses.add_callback( + responses.POST, f"{BASE}/v1/documents/upload-base64", + callback=handler, content_type="application/json", + ) + + client = SuperDocsClient(api_key="sk_test") + result = client.upload_document(file_bytes=b"fake docx bytes", filename="template.docx") + + assert captured["filename"] == "template.docx" + assert captured["file_base64"] + assert captured["session_id"] + assert result.session_id == captured["session_id"] + assert result.persisted is True + + +@responses.activate +def test_start_edit_posts_session_id_and_message(): + responses.add( + responses.POST, f"{BASE}/v1/chat/async", + json={"job_id": "job_123", "session_id": "sess_1", "status": "pending"}, + status=200, + ) + client = SuperDocsClient(api_key="sk_test") + result = client.start_edit(session_id="sess_1", instruction="fill in the sections") + assert result.job_id == "job_123" + assert result.status == "pending" + + +@responses.activate +def test_get_job_status_reads_document_changes_from_nested_result(): + """Real shape, verified live: document_changes is nested under + result.document_changes, not top-level.""" + responses.add( + responses.GET, f"{BASE}/v1/jobs/job_123", + json={ + "status": "completed", + "result": { + "document_changes": {"updated_html": "

filled in

"}, + }, + }, + status=200, + ) + client = SuperDocsClient(api_key="sk_test") + result = client.get_job_status("job_123") + assert result.status == "completed" + assert result.updated_html == "

filled in

" + + +@responses.activate +def test_approve_sends_job_id_and_approval_flag(): + """job_id is required by the real API -- a bare {approved} 422s for real.""" + captured = {} + + def handler(request): + import json + captured.update(json.loads(request.body)) + return (200, {}, json.dumps({"status": "approved"})) + + responses.add_callback( + responses.POST, f"{BASE}/v1/chat/sess_1/approve", + callback=handler, content_type="application/json", + ) + client = SuperDocsClient(api_key="sk_test") + client.approve(session_id="sess_1", job_id="job_123", approved=True) + assert captured == {"job_id": "job_123", "approved": True} + + +@responses.activate +def test_export_parses_filename_from_dual_form_content_disposition(): + """Real header carries both filename="..." and filename*=UTF-8''... on + one line -- a naive split on 'filename=' picks up junk from the second + form. Only the quoted value should be used.""" + responses.add( + responses.POST, f"{BASE}/v1/documents/export", + body=b"PK\x03\x04fakezip", + status=200, + headers={ + "content-disposition": 'attachment; filename="battlecard.docx"; filename*=UTF-8\'\'battlecard.docx', + "content-type": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + }, + ) + client = SuperDocsClient(api_key="sk_test") + result = client.export(session_id="sess_1", format="docx") + assert result.filename == "battlecard.docx" + assert result.file_bytes == b"PK\x03\x04fakezip" + + +@responses.activate +def test_non_2xx_response_raises_superdocs_error_with_status_and_body(): + responses.add( + responses.POST, f"{BASE}/v1/chat/sess_1/approve", + json={"detail": "Job is not awaiting approval (status: completed)"}, + status=400, + ) + client = SuperDocsClient(api_key="sk_test") + try: + client.approve(session_id="sess_1", job_id="job_123", approved=True) + assert False, "expected SuperDocsError" + except SuperDocsError as exc: + assert exc.status_code == 400 + assert "not awaiting approval" in exc.body diff --git a/use-cases/naxer-12/battlecard-generator/tests/test_template.py b/use-cases/naxer-12/battlecard-generator/tests/test_template.py new file mode 100644 index 00000000..398db380 --- /dev/null +++ b/use-cases/naxer-12/battlecard-generator/tests/test_template.py @@ -0,0 +1,42 @@ +from io import BytesIO + +import docx + +from battlecard.template import build_filled_template_bytes + + +def test_deterministic_fields_are_filled_in_with_no_ai_call_needed(): + """The title and three given fields are plain text substitution -- this + test proves that without touching the network, which is the whole point + of doing it this way.""" + raw = build_filled_template_bytes( + competitor_name="Zephyr Analytics", + likely_pitch="Fastest setup, no engineering needed.", + where_we_win="Our reports stay accurate automatically.", + where_we_dont="Their onboarding really is faster.", + ) + document = docx.Document(BytesIO(raw)) + all_text = "\n".join(p.text for p in document.paragraphs) + + assert "Zephyr Analytics — Battlecard" in all_text + assert "Fastest setup, no engineering needed." in all_text + assert "Our reports stay accurate automatically." in all_text + assert "Their onboarding really is faster." in all_text + assert "[COMPETITOR NAME]" not in all_text + + +def test_talk_track_is_left_as_a_placeholder_for_the_ai(): + raw = build_filled_template_bytes( + competitor_name="Zephyr Analytics", + likely_pitch="x", where_we_win="y", where_we_dont="z", + ) + document = docx.Document(BytesIO(raw)) + all_text = "\n".join(p.text for p in document.paragraphs) + assert "PLACEHOLDER" in all_text + assert "Talk Track" in all_text + + +def test_two_competitors_produce_different_filled_templates(): + zephyr = build_filled_template_bytes("Zephyr Analytics", "pitch A", "win A", "dont A") + brightfield = build_filled_template_bytes("Brightfield", "pitch B", "win B", "dont B") + assert zephyr != brightfield