diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 0b0d227..e958c00 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -119,6 +119,19 @@
android:exported="false"
android:foregroundServiceType="dataSync|microphone"/>
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/drawable-v31/ic_widget_clipboard.xml b/android/app/src/main/res/drawable-v31/ic_widget_clipboard.xml
new file mode 100644
index 0000000..699d217
--- /dev/null
+++ b/android/app/src/main/res/drawable-v31/ic_widget_clipboard.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
diff --git a/android/app/src/main/res/drawable-v31/ic_widget_mic.xml b/android/app/src/main/res/drawable-v31/ic_widget_mic.xml
new file mode 100644
index 0000000..2c46624
--- /dev/null
+++ b/android/app/src/main/res/drawable-v31/ic_widget_mic.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
diff --git a/android/app/src/main/res/drawable-v31/ic_widget_mic_accent.xml b/android/app/src/main/res/drawable-v31/ic_widget_mic_accent.xml
new file mode 100644
index 0000000..f43d1aa
--- /dev/null
+++ b/android/app/src/main/res/drawable-v31/ic_widget_mic_accent.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
diff --git a/android/app/src/main/res/drawable-v31/ic_widget_photos.xml b/android/app/src/main/res/drawable-v31/ic_widget_photos.xml
new file mode 100644
index 0000000..0c0eb38
--- /dev/null
+++ b/android/app/src/main/res/drawable-v31/ic_widget_photos.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
diff --git a/android/app/src/main/res/drawable-v31/ic_widget_waveform.xml b/android/app/src/main/res/drawable-v31/ic_widget_waveform.xml
new file mode 100644
index 0000000..7cd19ac
--- /dev/null
+++ b/android/app/src/main/res/drawable-v31/ic_widget_waveform.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
diff --git a/android/app/src/main/res/drawable-v31/widget_background.xml b/android/app/src/main/res/drawable-v31/widget_background.xml
new file mode 100644
index 0000000..7b3ea2d
--- /dev/null
+++ b/android/app/src/main/res/drawable-v31/widget_background.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/drawable-v31/widget_button_circle.xml b/android/app/src/main/res/drawable-v31/widget_button_circle.xml
new file mode 100644
index 0000000..fcd6a6f
--- /dev/null
+++ b/android/app/src/main/res/drawable-v31/widget_button_circle.xml
@@ -0,0 +1,11 @@
+
+
+
+ -
+
+
+
+
+
+
diff --git a/android/app/src/main/res/drawable-v31/widget_button_mic.xml b/android/app/src/main/res/drawable-v31/widget_button_mic.xml
new file mode 100644
index 0000000..7406ccc
--- /dev/null
+++ b/android/app/src/main/res/drawable-v31/widget_button_mic.xml
@@ -0,0 +1,11 @@
+
+
+
+ -
+
+
+
+
+
+
diff --git a/android/app/src/main/res/drawable-v31/widget_button_pill.xml b/android/app/src/main/res/drawable-v31/widget_button_pill.xml
new file mode 100644
index 0000000..ed12d5b
--- /dev/null
+++ b/android/app/src/main/res/drawable-v31/widget_button_pill.xml
@@ -0,0 +1,12 @@
+
+
+
+ -
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/drawable-v31/widget_button_primary.xml b/android/app/src/main/res/drawable-v31/widget_button_primary.xml
new file mode 100644
index 0000000..bd3a856
--- /dev/null
+++ b/android/app/src/main/res/drawable-v31/widget_button_primary.xml
@@ -0,0 +1,12 @@
+
+
+
+ -
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/drawable-v31/widget_button_secondary.xml b/android/app/src/main/res/drawable-v31/widget_button_secondary.xml
new file mode 100644
index 0000000..4d5da13
--- /dev/null
+++ b/android/app/src/main/res/drawable-v31/widget_button_secondary.xml
@@ -0,0 +1,12 @@
+
+
+
+ -
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/drawable/ic_hub.xml b/android/app/src/main/res/drawable/ic_hub.xml
new file mode 100644
index 0000000..f9edfb9
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_hub.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
diff --git a/android/app/src/main/res/drawable/ic_sparkle.xml b/android/app/src/main/res/drawable/ic_sparkle.xml
deleted file mode 100644
index 7700658..0000000
--- a/android/app/src/main/res/drawable/ic_sparkle.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
diff --git a/android/app/src/main/res/drawable/ic_widget_camera.xml b/android/app/src/main/res/drawable/ic_widget_camera.xml
new file mode 100644
index 0000000..7c3bfe4
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_widget_camera.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
diff --git a/android/app/src/main/res/drawable/ic_widget_clipboard.xml b/android/app/src/main/res/drawable/ic_widget_clipboard.xml
new file mode 100644
index 0000000..c218dfe
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_widget_clipboard.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
diff --git a/android/app/src/main/res/drawable/ic_widget_mic.xml b/android/app/src/main/res/drawable/ic_widget_mic.xml
new file mode 100644
index 0000000..606827e
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_widget_mic.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
diff --git a/android/app/src/main/res/drawable/ic_widget_mic_accent.xml b/android/app/src/main/res/drawable/ic_widget_mic_accent.xml
new file mode 100644
index 0000000..a67a4e1
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_widget_mic_accent.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
diff --git a/android/app/src/main/res/drawable/ic_widget_photos.xml b/android/app/src/main/res/drawable/ic_widget_photos.xml
new file mode 100644
index 0000000..374226b
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_widget_photos.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
diff --git a/android/app/src/main/res/drawable/ic_widget_waveform.xml b/android/app/src/main/res/drawable/ic_widget_waveform.xml
new file mode 100644
index 0000000..bd11201
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_widget_waveform.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
diff --git a/android/app/src/main/res/drawable/widget_background.xml b/android/app/src/main/res/drawable/widget_background.xml
new file mode 100644
index 0000000..0fc15eb
--- /dev/null
+++ b/android/app/src/main/res/drawable/widget_background.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/drawable/widget_button_circle.xml b/android/app/src/main/res/drawable/widget_button_circle.xml
new file mode 100644
index 0000000..a8e1010
--- /dev/null
+++ b/android/app/src/main/res/drawable/widget_button_circle.xml
@@ -0,0 +1,11 @@
+
+
+
+ -
+
+
+
+
+
+
diff --git a/android/app/src/main/res/drawable/widget_button_mic.xml b/android/app/src/main/res/drawable/widget_button_mic.xml
new file mode 100644
index 0000000..b9caa78
--- /dev/null
+++ b/android/app/src/main/res/drawable/widget_button_mic.xml
@@ -0,0 +1,11 @@
+
+
+
+ -
+
+
+
+
+
+
diff --git a/android/app/src/main/res/drawable/widget_button_pill.xml b/android/app/src/main/res/drawable/widget_button_pill.xml
new file mode 100644
index 0000000..0f9edc9
--- /dev/null
+++ b/android/app/src/main/res/drawable/widget_button_pill.xml
@@ -0,0 +1,12 @@
+
+
+
+ -
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/drawable/widget_button_primary.xml b/android/app/src/main/res/drawable/widget_button_primary.xml
new file mode 100644
index 0000000..08becdf
--- /dev/null
+++ b/android/app/src/main/res/drawable/widget_button_primary.xml
@@ -0,0 +1,12 @@
+
+
+
+ -
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/drawable/widget_button_secondary.xml b/android/app/src/main/res/drawable/widget_button_secondary.xml
new file mode 100644
index 0000000..192210e
--- /dev/null
+++ b/android/app/src/main/res/drawable/widget_button_secondary.xml
@@ -0,0 +1,12 @@
+
+
+
+ -
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/drawable/widget_preview.xml b/android/app/src/main/res/drawable/widget_preview.xml
new file mode 100644
index 0000000..792160e
--- /dev/null
+++ b/android/app/src/main/res/drawable/widget_preview.xml
@@ -0,0 +1,54 @@
+
+
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/layout/conduit_widget.xml b/android/app/src/main/res/layout/conduit_widget.xml
new file mode 100644
index 0000000..1bae06a
--- /dev/null
+++ b/android/app/src/main/res/layout/conduit_widget.xml
@@ -0,0 +1,130 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/values-night/colors.xml b/android/app/src/main/res/values-night/colors.xml
new file mode 100644
index 0000000..24212ae
--- /dev/null
+++ b/android/app/src/main/res/values-night/colors.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+ @android:color/system_accent1_200
+ @android:color/system_accent1_400
+ @android:color/system_accent1_800
+
+
+ @android:color/system_accent1_200
+ @android:color/system_accent1_800
+
+
+ @android:color/system_accent2_700
+ @android:color/system_accent2_100
+
+
+ @android:color/system_neutral1_900
+ @android:color/system_neutral2_700
+
+
+ #D0BCFF
+ #9A82DB
+ #D0BCFF
+ #4A3880
+ #4A4458
+ #E8DEF8
+ #1C1B1F
+
+
diff --git a/android/app/src/main/res/values-night/dimens.xml b/android/app/src/main/res/values-night/dimens.xml
new file mode 100644
index 0000000..81b2448
--- /dev/null
+++ b/android/app/src/main/res/values-night/dimens.xml
@@ -0,0 +1,10 @@
+
+
+
+ 12dp
+ 6dp
+ 24dp
+ 16dp
+ 12dp
+
+
diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..5c0c84a
--- /dev/null
+++ b/android/app/src/main/res/values/colors.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+ @android:color/system_accent1_600
+ @android:color/system_accent1_800
+ @android:color/white
+
+
+ @android:color/system_accent1_100
+ @android:color/system_accent1_700
+
+
+ @android:color/system_accent2_100
+ @android:color/system_accent1_700
+
+
+ @android:color/system_neutral1_10
+ @android:color/system_neutral2_100
+
+
+ #6750A4
+ #4A3880
+ #E8DEF8
+ #6750A4
+ #E8DEF8
+ #1D192B
+ #FFFBFE
+
+
diff --git a/android/app/src/main/res/values/dimens.xml b/android/app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..cfc92cc
--- /dev/null
+++ b/android/app/src/main/res/values/dimens.xml
@@ -0,0 +1,10 @@
+
+
+
+ 12dp
+ 6dp
+ 24dp
+ 16dp
+ 12dp
+
+
diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..34b5320
--- /dev/null
+++ b/android/app/src/main/res/values/strings.xml
@@ -0,0 +1,14 @@
+
+
+ Conduit
+
+
+ Conduit
+ Quick access to Conduit chat with camera, photos, and clipboard shortcuts
+ Ask Conduit
+ Camera
+ Photos
+ Clipboard
+ Voice
+
+
diff --git a/android/app/src/main/res/xml/conduit_widget_info.xml b/android/app/src/main/res/xml/conduit_widget_info.xml
new file mode 100644
index 0000000..b28dfbb
--- /dev/null
+++ b/android/app/src/main/res/xml/conduit_widget_info.xml
@@ -0,0 +1,18 @@
+
+
+
diff --git a/assets/Screenshot_2025-12-07_at_2.33.10_PM-32423289-c69e-46b7-b4e9-78a19a092179.png b/assets/Screenshot_2025-12-07_at_2.33.10_PM-32423289-c69e-46b7-b4e9-78a19a092179.png
new file mode 100644
index 0000000..e69de29
diff --git a/assets/image-8935b981-4568-4aa2-9e64-756e03aa6aaf.png b/assets/image-8935b981-4568-4aa2-9e64-756e03aa6aaf.png
new file mode 100644
index 0000000..e69de29
diff --git a/assets/share_1567790370950729575-d9c39cb9-8c69-4f4d-9c5b-8308d6beaa9d.png b/assets/share_1567790370950729575-d9c39cb9-8c69-4f4d-9c5b-8308d6beaa9d.png
new file mode 100644
index 0000000..e69de29
diff --git a/docs/android-widget-setup.md b/docs/android-widget-setup.md
new file mode 100644
index 0000000..99f0a4f
--- /dev/null
+++ b/docs/android-widget-setup.md
@@ -0,0 +1,133 @@
+# Android Widget Setup
+
+The Android home screen widget is automatically included in the app build. This document describes the implementation details.
+
+## Overview
+
+The widget uses **Material 3 / Material You** design with dynamic colors on Android 12+ (API 31+).
+
+## Widget Design (Native Android)
+
+```
+┌─────────────────────────────────────┐
+│ ✨ Ask Conduit │ ← Primary color (dynamic)
+│ │
+├───────────┬───────────┬─────────────┤
+│ 📷 │ 🖼️ │ 📋 │
+│ Camera │ Photos │ Clipboard │ ← Secondary container
+└───────────┴───────────┴─────────────┘
+```
+
+### Material You (Android 12+)
+
+- **Primary button**: System accent color (`system_accent1_600`)
+- **Secondary buttons**: Secondary container color (`system_accent2_100`)
+- **Background**: Neutral surface color (`system_neutral1_10`)
+- **Icons**: Tinted with `system_accent1_700`
+
+### Fallback (Android 11 and below)
+
+- **Primary button**: Material 3 default purple (`#6750A4`)
+- **Secondary buttons**: Light purple container (`#E8DEF8`)
+- **Background**: Near-white surface (`#FFFBFE`)
+
+## Files Structure
+
+```
+android/app/src/main/
+├── kotlin/.../ConduitWidgetProvider.kt # Widget logic
+├── res/
+│ ├── layout/
+│ │ └── conduit_widget.xml # Widget layout
+│ ├── drawable/
+│ │ ├── widget_background.xml # Surface background
+│ │ ├── widget_button_primary.xml # Primary button
+│ │ ├── widget_button_secondary.xml # Secondary buttons
+│ │ ├── ic_widget_camera.xml # Camera icon
+│ │ ├── ic_widget_photos.xml # Photos icon
+│ │ ├── ic_widget_clipboard.xml # Clipboard icon
+│ │ └── widget_preview.xml # Widget picker preview
+│ ├── drawable-v31/ # Material You overrides
+│ │ └── (same files with dynamic colors)
+│ ├── values/
+│ │ ├── colors.xml # Light mode colors
+│ │ ├── dimens.xml # Widget dimensions
+│ │ └── strings.xml # Widget strings
+│ ├── values-night/
+│ │ └── colors.xml # Dark mode colors
+│ └── xml/
+│ └── conduit_widget_info.xml # Widget metadata
+└── AndroidManifest.xml # Widget receiver registration
+```
+
+## Deep Link Handling
+
+The widget uses `homewidget://` URL scheme:
+
+| Action | URL |
+|--------|-----|
+| New Chat | `homewidget://new_chat` |
+| Camera | `homewidget://camera` |
+| Photos | `homewidget://photos` |
+| Clipboard | `homewidget://clipboard` |
+
+## Widget Configuration
+
+The widget is configured in `res/xml/conduit_widget_info.xml`:
+
+- **Min size**: 250x110dp (3x2 cells)
+- **Resizable**: Horizontal and vertical
+- **Category**: Home screen
+- **Update period**: Never (static widget)
+
+## Testing
+
+1. Build and install the debug APK:
+ ```bash
+ flutter build apk --debug
+ flutter install
+ ```
+
+2. Long press on home screen
+3. Tap "Widgets"
+4. Search for "Conduit"
+5. Drag widget to home screen
+
+## Customization
+
+### Changing the accent color
+
+The widget automatically picks up the system's Material You palette on Android 12+. On older versions, modify the fallback colors in `values/colors.xml`:
+
+```xml
+#YOUR_COLOR
+```
+
+### Changing widget size
+
+Modify `res/xml/conduit_widget_info.xml`:
+
+```xml
+
+```
+
+## Troubleshooting
+
+### Widget not appearing
+
+- Ensure the app was installed (not just built)
+- Try restarting the home launcher
+- Check that `ConduitWidgetProvider` is registered in AndroidManifest.xml
+
+### Colors not updating on theme change
+
+- Widget colors are set at creation time
+- User needs to re-add widget after theme change
+- Or trigger update via `HomeWidget.updateWidget()` from Flutter
+
diff --git a/docs/ios-widget-setup.md b/docs/ios-widget-setup.md
new file mode 100644
index 0000000..8c8bf35
--- /dev/null
+++ b/docs/ios-widget-setup.md
@@ -0,0 +1,120 @@
+# iOS Widget Extension Setup
+
+This document describes how the ConduitWidget extension was added and how to configure it.
+
+## Overview
+
+The widget extension was created via Xcode and provides quick access buttons:
+
+- **Ask Conduit** - Opens app with new chat, focuses composer
+- **Camera** - Opens app, creates new chat, launches camera
+- **Photos** - Opens app, creates new chat, opens photo picker
+- **Clipboard** - Opens app with clipboard contents as prompt
+
+## Files Structure
+
+```
+ios/ConduitWidget/
+├── Assets.xcassets/ # Asset catalog
+│ ├── AccentColor.colorset/ # Theme accent color
+│ ├── AppIcon.appiconset/ # Widget icon (uses app icon)
+│ └── WidgetBackground.colorset/ # Light/dark backgrounds
+├── ConduitWidget.entitlements # App group for data sharing
+├── ConduitWidget.swift # Main widget implementation
+├── ConduitWidgetBundle.swift # Widget bundle entry point
+└── Info.plist # Extension configuration
+```
+
+## App Group Configuration
+
+Both the main app and widget share data via the app group `group.app.cogwheel.conduit`.
+
+**Important:** Ensure both targets have this app group in their capabilities:
+
+1. Select the **Runner** target → Signing & Capabilities → App Groups
+2. Verify `group.app.cogwheel.conduit` is listed
+
+3. Select the **ConduitWidget** target → Signing & Capabilities → App Groups
+4. Verify `group.app.cogwheel.conduit` is listed
+
+## Deep Link Handling
+
+The widget uses `homewidget://` URL scheme to communicate with the Flutter app:
+
+| Action | URL |
+|--------|-----|
+| New Chat | `homewidget://new_chat` |
+| Camera | `homewidget://camera` |
+| Photos | `homewidget://photos` |
+| Clipboard | `homewidget://clipboard` |
+
+These are handled by `HomeWidgetCoordinator` in the Flutter code.
+
+## Widget Design (Native iOS)
+
+```
+┌─────────────────────────────────────┐
+│ ✨ Ask Conduit │ ← System tint color
+│ │
+├───────────┬───────────┬─────────────┤
+│ 📷 │ 🖼️ │ 📋 │
+│ Camera │ Photos │ Clipboard │ ← Secondary system bg
+└───────────┴───────────┴─────────────┘
+```
+
+- **Size**: Medium widget (systemMedium)
+- **Primary button**: System tint color (follows app accent)
+- **Secondary buttons**: `secondarySystemGroupedBackground`
+- **Icons**: SF Symbols with hierarchical rendering
+- **Typography**: SF Rounded font
+- **Supports**: Light/dark mode, Dynamic Type
+
+## Building
+
+The widget extension is built automatically when you build the main app:
+
+```bash
+flutter build ios
+```
+
+Or build from Xcode:
+
+1. Open `ios/Runner.xcworkspace`
+2. Select the **Runner** scheme
+3. Build (⌘B)
+
+## Testing
+
+1. Build and run the main app on a device/simulator
+2. Go to home screen
+3. Long press → tap **+** to add widgets
+4. Search for "Conduit"
+5. Add the medium widget
+
+## Troubleshooting
+
+### Widget not appearing in picker
+
+- Ensure the widget extension builds without errors
+- Check deployment target is iOS 17.0+
+- Clean build folder (⇧⌘K) and rebuild
+
+### Widget actions don't work
+
+- Verify the `homewidget://` URL scheme is handled
+- Check `HomeWidgetCoordinator` is initialized in app startup
+- Ensure app group is configured on both targets
+
+### Widget doesn't update
+
+- The widget uses `.never` refresh policy (static content)
+- Call `HomeWidget.updateWidget()` from Flutter to trigger refresh
+
+## Adding to a Fresh Clone
+
+If cloning the repo fresh, the widget extension should already be configured in the Xcode project. Just ensure:
+
+1. Team/signing is set for both targets
+2. App groups capability is enabled
+3. Pod install has been run
+
diff --git a/ios/ConduitWidget/Assets.xcassets/AccentColor.colorset/Contents.json b/ios/ConduitWidget/Assets.xcassets/AccentColor.colorset/Contents.json
new file mode 100644
index 0000000..e1e799d
--- /dev/null
+++ b/ios/ConduitWidget/Assets.xcassets/AccentColor.colorset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0.929",
+ "green" : "0.227",
+ "red" : "0.486"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
+
diff --git a/ios/ConduitWidget/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/ConduitWidget/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..2305880
--- /dev/null
+++ b/ios/ConduitWidget/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,35 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "platform" : "ios",
+ "size" : "1024x1024"
+ },
+ {
+ "appearances" : [
+ {
+ "appearance" : "luminosity",
+ "value" : "dark"
+ }
+ ],
+ "idiom" : "universal",
+ "platform" : "ios",
+ "size" : "1024x1024"
+ },
+ {
+ "appearances" : [
+ {
+ "appearance" : "luminosity",
+ "value" : "tinted"
+ }
+ ],
+ "idiom" : "universal",
+ "platform" : "ios",
+ "size" : "1024x1024"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/ConduitWidget/Assets.xcassets/Contents.json b/ios/ConduitWidget/Assets.xcassets/Contents.json
new file mode 100644
index 0000000..6cc1226
--- /dev/null
+++ b/ios/ConduitWidget/Assets.xcassets/Contents.json
@@ -0,0 +1,7 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
+
diff --git a/ios/ConduitWidget/Assets.xcassets/HubIcon.imageset/Contents.json b/ios/ConduitWidget/Assets.xcassets/HubIcon.imageset/Contents.json
new file mode 100644
index 0000000..546f556
--- /dev/null
+++ b/ios/ConduitWidget/Assets.xcassets/HubIcon.imageset/Contents.json
@@ -0,0 +1,17 @@
+{
+ "images" : [
+ {
+ "filename" : "hub.svg",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "preserves-vector-representation" : true,
+ "template-rendering-intent" : "template"
+ }
+}
+
diff --git a/ios/ConduitWidget/Assets.xcassets/HubIcon.imageset/hub.svg b/ios/ConduitWidget/Assets.xcassets/HubIcon.imageset/hub.svg
new file mode 100644
index 0000000..349ae5b
--- /dev/null
+++ b/ios/ConduitWidget/Assets.xcassets/HubIcon.imageset/hub.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/ios/ConduitWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json b/ios/ConduitWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json
new file mode 100644
index 0000000..e9810e6
--- /dev/null
+++ b/ios/ConduitWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json
@@ -0,0 +1,39 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0.961",
+ "green" : "0.961",
+ "red" : "0.961"
+ }
+ },
+ "idiom" : "universal"
+ },
+ {
+ "appearances" : [
+ {
+ "appearance" : "luminosity",
+ "value" : "dark"
+ }
+ ],
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0.118",
+ "green" : "0.118",
+ "red" : "0.118"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
+
diff --git a/ios/ConduitWidget/ConduitWidget.entitlements b/ios/ConduitWidget/ConduitWidget.entitlements
new file mode 100644
index 0000000..3d1b11d
--- /dev/null
+++ b/ios/ConduitWidget/ConduitWidget.entitlements
@@ -0,0 +1,11 @@
+
+
+
+
+ com.apple.security.application-groups
+
+ group.app.cogwheel.conduit
+
+
+
+
diff --git a/ios/ConduitWidget/ConduitWidget.swift b/ios/ConduitWidget/ConduitWidget.swift
new file mode 100644
index 0000000..5c97c16
--- /dev/null
+++ b/ios/ConduitWidget/ConduitWidget.swift
@@ -0,0 +1,129 @@
+//
+// ConduitWidget.swift
+// ConduitWidget
+//
+// Created by cogwheel on 07/12/25.
+//
+
+import WidgetKit
+import SwiftUI
+
+// MARK: - Timeline Entry
+
+struct ConduitEntry: TimelineEntry {
+ let date: Date
+}
+
+// MARK: - Timeline Provider
+
+struct ConduitProvider: TimelineProvider {
+ func placeholder(in context: Context) -> ConduitEntry {
+ ConduitEntry(date: Date())
+ }
+
+ func getSnapshot(in context: Context, completion: @escaping (ConduitEntry) -> Void) {
+ let entry = ConduitEntry(date: Date())
+ completion(entry)
+ }
+
+ func getTimeline(in context: Context, completion: @escaping (Timeline) -> Void) {
+ let entry = ConduitEntry(date: Date())
+ let timeline = Timeline(entries: [entry], policy: .never)
+ completion(timeline)
+ }
+}
+
+// MARK: - Widget View
+
+struct ConduitWidgetEntryView: View {
+ var entry: ConduitProvider.Entry
+ @Environment(\.widgetFamily) var family
+ @Environment(\.colorScheme) var colorScheme
+
+ var body: some View {
+ VStack(spacing: 12) {
+ // Main "Ask Conduit" pill - ChatGPT style
+ Link(destination: URL(string: "homewidget://new_chat")!) {
+ HStack(spacing: 12) {
+ Image("HubIcon")
+ .renderingMode(.template)
+ .resizable()
+ .aspectRatio(contentMode: .fit)
+ .frame(width: 28, height: 28)
+ .foregroundStyle(.white.opacity(0.9))
+ Text("Ask Conduit")
+ .font(.system(size: 18, weight: .medium, design: .rounded))
+ .foregroundStyle(.white.opacity(0.9))
+ Spacer()
+ }
+ .padding(.horizontal, 20)
+ .frame(maxWidth: .infinity)
+ .frame(height: 56)
+ .background(
+ Capsule()
+ .fill(.white.opacity(0.15))
+ )
+ }
+ .buttonStyle(.plain)
+
+ // 4 circular icon buttons - ChatGPT style, fill width
+ HStack(spacing: 8) {
+ CircularIconButton(symbol: "camera", url: "homewidget://camera")
+ CircularIconButton(symbol: "photo.on.rectangle.angled", url: "homewidget://photos")
+ CircularIconButton(symbol: "waveform", url: "homewidget://mic")
+ CircularIconButton(symbol: "doc.on.clipboard", url: "homewidget://clipboard")
+ }
+ }
+ .padding(16)
+ }
+}
+
+// MARK: - Circular Icon Button (ChatGPT Style)
+
+struct CircularIconButton: View {
+ let symbol: String
+ let url: String
+
+ var body: some View {
+ Link(destination: URL(string: url)!) {
+ Image(systemName: symbol)
+ .font(.system(size: 24, weight: .medium))
+ .foregroundStyle(.white.opacity(0.9))
+ .frame(maxWidth: .infinity, maxHeight: .infinity)
+ .background(
+ RoundedRectangle(cornerRadius: 16, style: .continuous)
+ .fill(.white.opacity(0.15))
+ )
+ }
+ .buttonStyle(.plain)
+ }
+}
+
+// MARK: - Widget Configuration
+
+struct ConduitWidget: Widget {
+ let kind: String = "ConduitWidget"
+
+ var body: some WidgetConfiguration {
+ StaticConfiguration(kind: kind, provider: ConduitProvider()) { entry in
+ if #available(iOS 17.0, *) {
+ ConduitWidgetEntryView(entry: entry)
+ .containerBackground(.clear, for: .widget)
+ } else {
+ ConduitWidgetEntryView(entry: entry)
+ }
+ }
+ .configurationDisplayName("Conduit")
+ .description("Quick access to chat, camera, photos, and voice.")
+ .supportedFamilies([.systemMedium])
+ .contentMarginsDisabled()
+ }
+}
+
+// MARK: - Preview
+
+#Preview(as: .systemMedium) {
+ ConduitWidget()
+} timeline: {
+ ConduitEntry(date: .now)
+}
diff --git a/ios/ConduitWidget/ConduitWidgetBundle.swift b/ios/ConduitWidget/ConduitWidgetBundle.swift
new file mode 100644
index 0000000..f17aace
--- /dev/null
+++ b/ios/ConduitWidget/ConduitWidgetBundle.swift
@@ -0,0 +1,16 @@
+//
+// ConduitWidgetBundle.swift
+// ConduitWidget
+//
+// Created by cogwheel on 07/12/25.
+//
+
+import WidgetKit
+import SwiftUI
+
+@main
+struct ConduitWidgetBundle: WidgetBundle {
+ var body: some Widget {
+ ConduitWidget()
+ }
+}
diff --git a/ios/ConduitWidget/Info.plist b/ios/ConduitWidget/Info.plist
new file mode 100644
index 0000000..3d2e9f2
--- /dev/null
+++ b/ios/ConduitWidget/Info.plist
@@ -0,0 +1,12 @@
+
+
+
+
+ NSExtension
+
+ NSExtensionPointIdentifier
+ com.apple.widgetkit-extension
+
+
+
+
diff --git a/ios/ConduitWidgetExtension.entitlements b/ios/ConduitWidgetExtension.entitlements
new file mode 100644
index 0000000..69c95a5
--- /dev/null
+++ b/ios/ConduitWidgetExtension.entitlements
@@ -0,0 +1,10 @@
+
+
+
+
+ com.apple.security.application-groups
+
+ group.app.cogwheel.conduit
+
+
+
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index aa8f151..4b3aae7 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -54,6 +54,8 @@ PODS:
- Flutter
- flutter_tts (0.0.1):
- Flutter
+ - home_widget (0.0.1):
+ - Flutter
- image_picker_ios (0.0.1):
- Flutter
- onnxruntime-c (1.22.0)
@@ -115,6 +117,7 @@ DEPENDENCIES:
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
- flutter_tts (from `.symlinks/plugins/flutter_tts/ios`)
+ - home_widget (from `.symlinks/plugins/home_widget/ios`)
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
@@ -162,6 +165,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/flutter_secure_storage/ios"
flutter_tts:
:path: ".symlinks/plugins/flutter_tts/ios"
+ home_widget:
+ :path: ".symlinks/plugins/home_widget/ios"
image_picker_ios:
:path: ".symlinks/plugins/image_picker_ios/ios"
package_info_plus:
@@ -208,6 +213,7 @@ SPEC CHECKSUMS:
flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13
flutter_tts: b88dbc8655d3dc961bc4a796e4e16a4cc1795833
+ home_widget: f169fc41fd807b4d46ab6615dc44d62adbf9f64f
image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326
onnxruntime-c: 7f778680e96145956c0a31945f260321eed2611a
onnxruntime-objc: 83d28b87525bd971259a66e153ea32b5d023de19
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index ec2ab04..8a95238 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -15,6 +15,9 @@
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 */; };
+ F15AFEC42EE5499D00A1FABB /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F15AFEC32EE5499D00A1FABB /* WidgetKit.framework */; };
+ F15AFEC62EE5499D00A1FABB /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F15AFEC52EE5499D00A1FABB /* SwiftUI.framework */; };
+ F15AFED12EE5499E00A1FABB /* ConduitWidgetExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = F15AFEC22EE5499D00A1FABB /* ConduitWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
F1DBCF1D2E601A39004C2540 /* ShareExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = F1DBCF132E601A39004C2540 /* ShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
F1E401255BF7F4649BBEC0E4 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3268AF100A34CCB865515F5F /* Pods_RunnerTests.framework */; };
/* End PBXBuildFile section */
@@ -27,6 +30,13 @@
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
remoteInfo = Runner;
};
+ F15AFECF2EE5499E00A1FABB /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 97C146E61CF9000F007C117D /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = F15AFEC12EE5499D00A1FABB;
+ remoteInfo = ConduitWidgetExtension;
+ };
F1DBCF1B2E601A39004C2540 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
@@ -54,6 +64,7 @@
dstSubfolderSpec = 13;
files = (
F1DBCF1D2E601A39004C2540 /* ShareExtension.appex in Embed Foundation Extensions */,
+ F15AFED12EE5499E00A1FABB /* ConduitWidgetExtension.appex in Embed Foundation Extensions */,
);
name = "Embed Foundation Extensions";
runOnlyForDeploymentPostprocessing = 0;
@@ -88,11 +99,22 @@
A2C5C28DAB99348B62D0E111 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
C334EBA4AE824079ECAEE9EE /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; };
CF1093DCAFB438AD6653A379 /* Pods-ShareExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShareExtension.release.xcconfig"; path = "Target Support Files/Pods-ShareExtension/Pods-ShareExtension.release.xcconfig"; sourceTree = ""; };
+ F15AFEC22EE5499D00A1FABB /* ConduitWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ConduitWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
+ F15AFEC32EE5499D00A1FABB /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; };
+ F15AFEC52EE5499D00A1FABB /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
+ F15AFED82EE549B700A1FABB /* ConduitWidgetExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ConduitWidgetExtension.entitlements; sourceTree = ""; };
F1DBCF132E601A39004C2540 /* ShareExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ShareExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
F1DBCF242E601A7C004C2540 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
+ F15AFED52EE5499E00A1FABB /* Exceptions for "ConduitWidget" folder in "ConduitWidgetExtension" target */ = {
+ isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
+ membershipExceptions = (
+ Info.plist,
+ );
+ target = F15AFEC12EE5499D00A1FABB /* ConduitWidgetExtension */;
+ };
F1DBCF222E601A39004C2540 /* Exceptions for "ShareExtension" folder in "ShareExtension" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
@@ -103,6 +125,18 @@
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
+ F15AFEC72EE5499D00A1FABB /* ConduitWidget */ = {
+ isa = PBXFileSystemSynchronizedRootGroup;
+ exceptions = (
+ F15AFED52EE5499E00A1FABB /* Exceptions for "ConduitWidget" folder in "ConduitWidgetExtension" target */,
+ );
+ explicitFileTypes = {
+ };
+ explicitFolders = (
+ );
+ path = ConduitWidget;
+ sourceTree = "";
+ };
F1DBCF142E601A39004C2540 /* ShareExtension */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
@@ -134,6 +168,15 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ F15AFEBF2EE5499D00A1FABB /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ F15AFEC62EE5499D00A1FABB /* SwiftUI.framework in Frameworks */,
+ F15AFEC42EE5499D00A1FABB /* WidgetKit.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
F1DBCF102E601A39004C2540 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@@ -159,6 +202,8 @@
A2C5C28DAB99348B62D0E111 /* Pods_Runner.framework */,
3268AF100A34CCB865515F5F /* Pods_RunnerTests.framework */,
1D444AAE6AC78C530C74E51F /* Pods_ShareExtension.framework */,
+ F15AFEC32EE5499D00A1FABB /* WidgetKit.framework */,
+ F15AFEC52EE5499D00A1FABB /* SwiftUI.framework */,
);
name = Frameworks;
sourceTree = "";
@@ -193,9 +238,11 @@
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
+ F15AFED82EE549B700A1FABB /* ConduitWidgetExtension.entitlements */,
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
F1DBCF142E601A39004C2540 /* ShareExtension */,
+ F15AFEC72EE5499D00A1FABB /* ConduitWidget */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
8C43905FA2E52A883F49D605 /* Pods */,
@@ -209,6 +256,7 @@
97C146EE1CF9000F007C117D /* Runner.app */,
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
F1DBCF132E601A39004C2540 /* ShareExtension.appex */,
+ F15AFEC22EE5499D00A1FABB /* ConduitWidgetExtension.appex */,
);
name = Products;
sourceTree = "";
@@ -269,12 +317,33 @@
);
dependencies = (
F1DBCF1C2E601A39004C2540 /* PBXTargetDependency */,
+ F15AFED02EE5499E00A1FABB /* PBXTargetDependency */,
);
name = Runner;
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
+ F15AFEC12EE5499D00A1FABB /* ConduitWidgetExtension */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = F15AFED62EE5499E00A1FABB /* Build configuration list for PBXNativeTarget "ConduitWidgetExtension" */;
+ buildPhases = (
+ F15AFEBE2EE5499D00A1FABB /* Sources */,
+ F15AFEBF2EE5499D00A1FABB /* Frameworks */,
+ F15AFEC02EE5499D00A1FABB /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ fileSystemSynchronizedGroups = (
+ F15AFEC72EE5499D00A1FABB /* ConduitWidget */,
+ );
+ name = ConduitWidgetExtension;
+ productName = ConduitWidgetExtension;
+ productReference = F15AFEC22EE5499D00A1FABB /* ConduitWidgetExtension.appex */;
+ productType = "com.apple.product-type.app-extension";
+ };
F1DBCF122E601A39004C2540 /* ShareExtension */ = {
isa = PBXNativeTarget;
buildConfigurationList = F1DBCF232E601A39004C2540 /* Build configuration list for PBXNativeTarget "ShareExtension" */;
@@ -303,7 +372,7 @@
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
- LastSwiftUpdateCheck = 1640;
+ LastSwiftUpdateCheck = 2610;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
@@ -315,6 +384,9 @@
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
};
+ F15AFEC12EE5499D00A1FABB = {
+ CreatedOnToolsVersion = 26.1.1;
+ };
F1DBCF122E601A39004C2540 = {
CreatedOnToolsVersion = 16.4;
};
@@ -336,6 +408,7 @@
97C146ED1CF9000F007C117D /* Runner */,
331C8080294A63A400263BE5 /* RunnerTests */,
F1DBCF122E601A39004C2540 /* ShareExtension */,
+ F15AFEC12EE5499D00A1FABB /* ConduitWidgetExtension */,
);
};
/* End PBXProject section */
@@ -358,6 +431,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ F15AFEC02EE5499D00A1FABB /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
F1DBCF112E601A39004C2540 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@@ -500,6 +580,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ F15AFEBE2EE5499D00A1FABB /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
F1DBCF0F2E601A39004C2540 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@@ -515,6 +602,11 @@
target = 97C146ED1CF9000F007C117D /* Runner */;
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
};
+ F15AFED02EE5499E00A1FABB /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = F15AFEC12EE5499D00A1FABB /* ConduitWidgetExtension */;
+ targetProxy = F15AFECF2EE5499E00A1FABB /* PBXContainerItemProxy */;
+ };
F1DBCF1C2E601A39004C2540 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = F1DBCF122E601A39004C2540 /* ShareExtension */;
@@ -855,6 +947,138 @@
};
name = Release;
};
+ F15AFED22EE5499E00A1FABB /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_ENTITLEMENTS = ConduitWidgetExtension.entitlements;
+ CODE_SIGN_IDENTITY = "Apple Development";
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_TEAM = X2662V5DT2;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_FILE = ConduitWidget/Info.plist;
+ INFOPLIST_KEY_CFBundleDisplayName = ConduitWidget;
+ INFOPLIST_KEY_NSHumanReadableCopyright = "";
+ IPHONEOS_DEPLOYMENT_TARGET = 26.1;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@executable_path/../../Frameworks",
+ );
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MARKETING_VERSION = 1.0;
+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+ MTL_FAST_MATH = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = app.cogwheel.conduit.debug.ConduitWidget;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SKIP_INSTALL = YES;
+ STRING_CATALOG_GENERATE_SYMBOLS = YES;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
+ SWIFT_APPROACHABLE_CONCURRENCY = YES;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ F15AFED32EE5499E00A1FABB /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_ENTITLEMENTS = ConduitWidgetExtension.entitlements;
+ CODE_SIGN_IDENTITY = "Apple Development";
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_TEAM = X2662V5DT2;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_FILE = ConduitWidget/Info.plist;
+ INFOPLIST_KEY_CFBundleDisplayName = ConduitWidget;
+ INFOPLIST_KEY_NSHumanReadableCopyright = "";
+ IPHONEOS_DEPLOYMENT_TARGET = 26.1;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@executable_path/../../Frameworks",
+ );
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MARKETING_VERSION = 1.0;
+ MTL_FAST_MATH = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = app.cogwheel.conduit.ConduitWidget;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SKIP_INSTALL = YES;
+ STRING_CATALOG_GENERATE_SYMBOLS = YES;
+ SWIFT_APPROACHABLE_CONCURRENCY = YES;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Release;
+ };
+ F15AFED42EE5499E00A1FABB /* Profile */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_ENTITLEMENTS = ConduitWidgetExtension.entitlements;
+ CODE_SIGN_IDENTITY = "Apple Development";
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_TEAM = X2662V5DT2;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_FILE = ConduitWidget/Info.plist;
+ INFOPLIST_KEY_CFBundleDisplayName = ConduitWidget;
+ INFOPLIST_KEY_NSHumanReadableCopyright = "";
+ IPHONEOS_DEPLOYMENT_TARGET = 26.1;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@executable_path/../../Frameworks",
+ );
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MARKETING_VERSION = 1.0;
+ MTL_FAST_MATH = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = app.cogwheel.conduit.ConduitWidget;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SKIP_INSTALL = YES;
+ STRING_CATALOG_GENERATE_SYMBOLS = YES;
+ SWIFT_APPROACHABLE_CONCURRENCY = YES;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Profile;
+ };
F1DBCF1F2E601A39004C2540 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 81AA439A1413A5BB88E56615 /* Pods-ShareExtension.debug.xcconfig */;
@@ -1023,6 +1247,16 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
+ F15AFED62EE5499E00A1FABB /* Build configuration list for PBXNativeTarget "ConduitWidgetExtension" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ F15AFED22EE5499E00A1FABB /* Debug */,
+ F15AFED32EE5499E00A1FABB /* Release */,
+ F15AFED42EE5499E00A1FABB /* Profile */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
F1DBCF232E601A39004C2540 /* Build configuration list for PBXNativeTarget "ShareExtension" */ = {
isa = XCConfigurationList;
buildConfigurations = (
diff --git a/lib/core/providers/app_startup_providers.dart b/lib/core/providers/app_startup_providers.dart
index 4ac5f66..62923b4 100644
--- a/lib/core/providers/app_startup_providers.dart
+++ b/lib/core/providers/app_startup_providers.dart
@@ -10,6 +10,7 @@ import '../providers/app_providers.dart';
import '../../features/auth/providers/unified_auth_providers.dart';
import '../services/navigation_service.dart';
import '../services/app_intents_service.dart';
+import '../services/home_widget_service.dart';
import '../services/quick_actions_service.dart';
import '../models/conversation.dart';
import '../services/background_streaming_handler.dart';
@@ -172,6 +173,7 @@ class AppStartupFlow extends _$AppStartupFlow {
keepAlive(silentLoginCoordinatorProvider);
keepAlive(appIntentCoordinatorProvider);
keepAlive(quickActionsCoordinatorProvider);
+ keepAlive(homeWidgetCoordinatorProvider);
// Kick background model loading flow (non-blocking)
Future.delayed(const Duration(milliseconds: 120), () {
diff --git a/lib/core/services/home_widget_service.dart b/lib/core/services/home_widget_service.dart
new file mode 100644
index 0000000..2479998
--- /dev/null
+++ b/lib/core/services/home_widget_service.dart
@@ -0,0 +1,409 @@
+import 'dart:async';
+import 'dart:io';
+
+import 'package:flutter/foundation.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter/widgets.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:home_widget/home_widget.dart';
+import 'package:image_picker/image_picker.dart';
+import 'package:path/path.dart' as path;
+import 'package:riverpod_annotation/riverpod_annotation.dart';
+
+import '../../features/auth/providers/unified_auth_providers.dart';
+import '../../features/chat/providers/chat_providers.dart';
+import '../../features/chat/services/file_attachment_service.dart';
+import '../../shared/services/tasks/task_queue.dart';
+import '../providers/app_providers.dart';
+import '../utils/debug_logger.dart';
+import 'app_intents_service.dart';
+import 'navigation_service.dart';
+
+part 'home_widget_service.g.dart';
+
+/// Widget action identifiers matching native widget implementations.
+class WidgetActions {
+ static const String newChat = 'new_chat';
+ static const String mic = 'mic';
+ static const String camera = 'camera';
+ static const String photos = 'photos';
+ static const String clipboard = 'clipboard';
+}
+
+/// App group identifier for iOS widget data sharing.
+const String _appGroupId = 'group.app.cogwheel.conduit';
+
+/// Android widget provider class name.
+const String _androidWidgetName = 'ConduitWidgetProvider';
+
+/// iOS widget kind identifier.
+const String _iOSWidgetKind = 'ConduitWidget';
+
+/// Handles home screen widget interactions for Android and iOS.
+///
+/// The widget provides quick actions:
+/// - New Chat: Start a fresh conversation
+/// - Camera: Take a photo and attach to chat
+/// - Photos: Pick from gallery and attach to chat
+/// - Clipboard: Paste clipboard content as prompt
+@Riverpod(keepAlive: true)
+class HomeWidgetCoordinator extends _$HomeWidgetCoordinator {
+ StreamSubscription? _widgetClickSubscription;
+ Uri? _pendingWidgetAction;
+
+ @override
+ FutureOr build() async {
+ if (kIsWeb) return;
+ if (!Platform.isIOS && !Platform.isAndroid) return;
+
+ await _initialize();
+
+ ref.onDispose(() {
+ _widgetClickSubscription?.cancel();
+ });
+ }
+
+ Future _initialize() async {
+ try {
+ // Set app group for iOS data sharing
+ if (Platform.isIOS) {
+ await HomeWidget.setAppGroupId(_appGroupId);
+ }
+
+ // Handle widget clicks
+ _widgetClickSubscription = HomeWidget.widgetClicked.listen(
+ _handleWidgetClick,
+ onError: (error) {
+ DebugLogger.error(
+ 'home-widget-stream',
+ scope: 'widget',
+ error: error,
+ );
+ },
+ );
+
+ // Check for initial launch from widget
+ final initialUri = await HomeWidget.initiallyLaunchedFromHomeWidget();
+ if (initialUri != null) {
+ DebugLogger.log(
+ 'Widget: Initial launch URI: $initialUri',
+ scope: 'widget',
+ );
+ // Store for later processing once app is ready
+ _pendingWidgetAction = initialUri;
+ // Try to process after a delay to allow router to initialize
+ _processInitialWidgetAction();
+ }
+
+ DebugLogger.log('Home widget service initialized', scope: 'widget');
+ } catch (error, stackTrace) {
+ DebugLogger.error(
+ 'home-widget-init',
+ scope: 'widget',
+ error: error,
+ stackTrace: stackTrace,
+ );
+ }
+ }
+
+ /// Process initial widget action after ensuring router is ready.
+ Future _processInitialWidgetAction() async {
+ if (_pendingWidgetAction == null) return;
+
+ // Wait for router to be attached and app to be ready
+ for (var i = 0; i < 50; i++) {
+ // Try for up to 5 seconds
+ await Future.delayed(const Duration(milliseconds: 100));
+
+ // Check if router is available
+ if (NavigationService.currentRoute != null) {
+ DebugLogger.log(
+ 'Widget: Router ready, processing pending action',
+ scope: 'widget',
+ );
+ final uri = _pendingWidgetAction;
+ _pendingWidgetAction = null;
+ await _handleWidgetClick(uri);
+ return;
+ }
+ }
+
+ DebugLogger.log(
+ 'Widget: Timeout waiting for router, clearing pending action',
+ scope: 'widget',
+ );
+ _pendingWidgetAction = null;
+ }
+
+ Future _handleWidgetClick(Uri? uri) async {
+ if (uri == null) return;
+
+ // If router isn't ready yet, store for later
+ if (NavigationService.currentRoute == null) {
+ DebugLogger.log(
+ 'Widget: Router not ready, storing action for later',
+ scope: 'widget',
+ );
+ _pendingWidgetAction = uri;
+ _processInitialWidgetAction();
+ return;
+ }
+
+ final action = uri.host.isNotEmpty
+ ? uri.host
+ : uri.pathSegments.firstOrNull;
+ if (action == null || action.isEmpty) {
+ // Default action: open new chat
+ await _handleNewChat();
+ return;
+ }
+
+ DebugLogger.log('Widget action: $action', scope: 'widget');
+
+ switch (action) {
+ case WidgetActions.newChat:
+ await _handleNewChat();
+ break;
+ case WidgetActions.mic:
+ await _handleMic();
+ break;
+ case WidgetActions.camera:
+ await _handleCamera();
+ break;
+ case WidgetActions.photos:
+ await _handlePhotos();
+ break;
+ case WidgetActions.clipboard:
+ await _handleClipboard();
+ break;
+ default:
+ DebugLogger.log('Unknown widget action: $action', scope: 'widget');
+ await _handleNewChat();
+ }
+ }
+
+ Future _handleNewChat() async {
+ DebugLogger.log('Widget: Starting new chat', scope: 'widget');
+ await _waitForNavigation();
+ await ref
+ .read(appIntentCoordinatorProvider.notifier)
+ .openChatFromExternal(focusComposer: true, resetChat: true);
+ }
+
+ Future _handleMic() async {
+ DebugLogger.log('Widget: Starting voice call', scope: 'widget');
+ await _waitForNavigation();
+ try {
+ await ref
+ .read(appIntentCoordinatorProvider.notifier)
+ .startVoiceCallFromExternal();
+ } catch (error, stackTrace) {
+ DebugLogger.error(
+ 'home-widget-mic',
+ scope: 'widget',
+ error: error,
+ stackTrace: stackTrace,
+ );
+ // Fall back to opening chat with focus
+ await ref
+ .read(appIntentCoordinatorProvider.notifier)
+ .openChatFromExternal(focusComposer: true, resetChat: true);
+ }
+ }
+
+ Future _handleCamera() async {
+ DebugLogger.log('Widget: Opening camera', scope: 'widget');
+ await _waitForNavigation();
+
+ // Navigate to chat first
+ await ref
+ .read(appIntentCoordinatorProvider.notifier)
+ .openChatFromExternal(focusComposer: false, resetChat: true);
+
+ // Wait for navigation to settle
+ await Future.delayed(const Duration(milliseconds: 100));
+
+ // Check auth state
+ final navState = ref.read(authNavigationStateProvider);
+ if (navState != AuthNavigationState.authenticated) {
+ DebugLogger.log('Widget: Not authenticated for camera', scope: 'widget');
+ return;
+ }
+
+ try {
+ final picker = ImagePicker();
+ final image = await picker.pickImage(
+ source: ImageSource.camera,
+ imageQuality: 85,
+ );
+
+ if (image != null) {
+ await _attachFile(File(image.path));
+ }
+ } catch (error, stackTrace) {
+ DebugLogger.error(
+ 'home-widget-camera',
+ scope: 'widget',
+ error: error,
+ stackTrace: stackTrace,
+ );
+ }
+ }
+
+ Future _handlePhotos() async {
+ DebugLogger.log('Widget: Opening photo picker', scope: 'widget');
+ await _waitForNavigation();
+
+ // Navigate to chat first
+ await ref
+ .read(appIntentCoordinatorProvider.notifier)
+ .openChatFromExternal(focusComposer: false, resetChat: true);
+
+ // Wait for navigation to settle
+ await Future.delayed(const Duration(milliseconds: 100));
+
+ // Check auth state
+ final navState = ref.read(authNavigationStateProvider);
+ if (navState != AuthNavigationState.authenticated) {
+ DebugLogger.log('Widget: Not authenticated for photos', scope: 'widget');
+ return;
+ }
+
+ try {
+ final picker = ImagePicker();
+ final images = await picker.pickMultiImage(imageQuality: 85);
+
+ if (images.isNotEmpty) {
+ for (final image in images) {
+ await _attachFile(File(image.path));
+ }
+ }
+ } catch (error, stackTrace) {
+ DebugLogger.error(
+ 'home-widget-photos',
+ scope: 'widget',
+ error: error,
+ stackTrace: stackTrace,
+ );
+ }
+ }
+
+ Future _handleClipboard() async {
+ DebugLogger.log('Widget: Pasting from clipboard', scope: 'widget');
+ await _waitForNavigation();
+
+ try {
+ final clipboardData = await Clipboard.getData(Clipboard.kTextPlain);
+ final text = clipboardData?.text?.trim();
+
+ if (text == null || text.isEmpty) {
+ DebugLogger.log('Widget: Clipboard is empty', scope: 'widget');
+ // Still open chat even if clipboard is empty
+ await ref
+ .read(appIntentCoordinatorProvider.notifier)
+ .openChatFromExternal(focusComposer: true, resetChat: true);
+ return;
+ }
+
+ await ref
+ .read(appIntentCoordinatorProvider.notifier)
+ .openChatFromExternal(
+ prompt: text,
+ focusComposer: true,
+ resetChat: true,
+ );
+ } catch (error, stackTrace) {
+ DebugLogger.error(
+ 'home-widget-clipboard',
+ scope: 'widget',
+ error: error,
+ stackTrace: stackTrace,
+ );
+ // Fall back to just opening chat
+ await ref
+ .read(appIntentCoordinatorProvider.notifier)
+ .openChatFromExternal(focusComposer: true, resetChat: true);
+ }
+ }
+
+ /// Wait for the navigation system to be ready.
+ Future _waitForNavigation() async {
+ // Wait for bindings to be initialized
+ WidgetsBinding.instance.addPostFrameCallback((_) {});
+ await Future.delayed(const Duration(milliseconds: 50));
+ }
+
+ Future _attachFile(File file) async {
+ if (!ref.mounted) return;
+
+ // Warm the attachment service
+ final _ = ref.read(fileAttachmentServiceProvider);
+ final notifier = ref.read(attachedFilesProvider.notifier);
+ final taskQueue = ref.read(taskQueueProvider.notifier);
+ final activeConv = ref.read(activeConversationProvider);
+
+ final attachment = LocalAttachment(
+ file: file,
+ displayName: path.basename(file.path),
+ );
+
+ notifier.addFiles([attachment]);
+
+ try {
+ await taskQueue.enqueueUploadMedia(
+ conversationId: activeConv?.id,
+ filePath: file.path,
+ fileName: attachment.displayName,
+ fileSize: await file.length(),
+ );
+ } catch (error, stackTrace) {
+ DebugLogger.error(
+ 'home-widget-upload',
+ scope: 'widget',
+ error: error,
+ stackTrace: stackTrace,
+ );
+ }
+
+ // Focus the composer after attaching
+ final tick = ref.read(inputFocusTriggerProvider);
+ ref.read(inputFocusTriggerProvider.notifier).set(tick + 1);
+ }
+
+ /// Update widget data displayed on home screen.
+ ///
+ /// Call this when app state changes that should be reflected in widget.
+ Future updateWidgetData() async {
+ if (kIsWeb) return;
+ if (!Platform.isIOS && !Platform.isAndroid) return;
+
+ try {
+ // For now, we just trigger a widget update
+ // In the future, we could pass data like recent conversations
+
+ if (Platform.isAndroid) {
+ await HomeWidget.updateWidget(androidName: _androidWidgetName);
+ } else if (Platform.isIOS) {
+ await HomeWidget.updateWidget(iOSName: _iOSWidgetKind);
+ }
+
+ DebugLogger.log('Widget data updated', scope: 'widget');
+ } catch (error, stackTrace) {
+ DebugLogger.error(
+ 'home-widget-update',
+ scope: 'widget',
+ error: error,
+ stackTrace: stackTrace,
+ );
+ }
+ }
+}
+
+/// Provider to trigger home widget initialization at app startup.
+final homeWidgetInitializerProvider = Provider((ref) {
+ if (kIsWeb) return;
+ if (!Platform.isIOS && !Platform.isAndroid) return;
+
+ // Initialize the coordinator which sets up widget click handling
+ ref.watch(homeWidgetCoordinatorProvider);
+});
diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb
index 80674a6..30001dd 100644
--- a/lib/l10n/app_en.arb
+++ b/lib/l10n/app_en.arb
@@ -1787,6 +1787,26 @@
}
}
},
+ "widgetAskConduit": "Ask Conduit",
+ "@widgetAskConduit": {
+ "description": "Main button text on the home screen widget."
+ },
+ "widgetCamera": "Camera",
+ "@widgetCamera": {
+ "description": "Camera button label on the home screen widget."
+ },
+ "widgetPhotos": "Photos",
+ "@widgetPhotos": {
+ "description": "Photos button label on the home screen widget."
+ },
+ "widgetClipboard": "Clipboard",
+ "@widgetClipboard": {
+ "description": "Clipboard button label on the home screen widget."
+ },
+ "widgetDescription": "Quick access to Conduit chat with camera, photos, and clipboard shortcuts",
+ "@widgetDescription": {
+ "description": "Description shown in the widget picker when adding the widget."
+ },
"mermaidPreviewUnavailable": "Mermaid preview is not available on this platform.",
"@mermaidPreviewUnavailable": {
"description": "Shown when Mermaid diagrams cannot be rendered on the current platform."
diff --git a/pubspec.lock b/pubspec.lock
index 37bc987..e0366c0 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -725,6 +725,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.3.3"
+ home_widget:
+ dependency: "direct main"
+ description:
+ name: home_widget
+ sha256: "908d033514a981f829fd98213909e11a428104327be3b422718aa643ac9d084a"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.8.1"
hotreloader:
dependency: transitive
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index d2fc7e9..8a64222 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -77,6 +77,7 @@ dependencies:
quick_actions: 1.1.0
flutter_svg: ^2.2.3
html_unescape: ^2.0.0
+ home_widget: ^0.8.1
# Clipboard functionality is available through flutter/services (part of Flutter SDK)