├── .github ├── .cspell │ └── names_dictionary.txt ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.yml ├── assets │ ├── build_phases.png │ ├── demo_android.png │ ├── demo_ios.png │ ├── target.png │ ├── target_membership.png │ └── widget_extension.png ├── cspell.yaml ├── pull_request_template.md └── workflows │ ├── flutter-beta.yml │ ├── main.yml │ ├── release-prepare.yml │ ├── release-publish.yml │ ├── release-tag.yml │ ├── spellcheck.yml │ └── title-validator.yml ├── .gitignore ├── CHANGELOG.md ├── README.md ├── docs.json ├── docs ├── android-xml │ ├── detect-clicks.mdx │ ├── interactive-widgets.mdx │ ├── overview.mdx │ ├── render-flutter-widget.mdx │ └── setup.mdx ├── assets │ ├── configurable │ │ └── ios │ │ │ ├── app-group.webp │ │ │ ├── app-intent-target-membership.webp │ │ │ ├── configuration-in-flutter.webp │ │ │ ├── configured-widgets.webp │ │ │ ├── create-widget-extension-ios-with-configuration.webp │ │ │ ├── intent-configuration.webp │ │ │ ├── simple-parameter.webp │ │ │ ├── siri-configuration-in-flutter.webp │ │ │ ├── siri-configured-example.webp │ │ │ ├── siri-created-type.webp │ │ │ ├── siri-extension-dynamic-option.webp │ │ │ ├── siri-extension-target-membership.webp │ │ │ ├── siri-intent-file.webp │ │ │ ├── siri-intents-extension.webp │ │ │ ├── siri-new-type.webp │ │ │ ├── siri-select-type.webp │ │ │ ├── widget-with-name-configuration.webp │ │ │ └── world-to-flutter.mp4 │ ├── create-widget-extension-ios.webp │ ├── lockscreen.webp │ └── pin-widget.gif ├── features │ ├── analytics.mdx │ ├── background-updates.mdx │ ├── configurable-widgets.mdx │ ├── detect-clicks.mdx │ ├── interactive-widgets.mdx │ ├── ios-lock-screen.mdx │ ├── pin-widget.mdx │ └── render-flutter-widgets.mdx ├── index.mdx ├── setup │ ├── android.mdx │ └── ios.mdx └── usage │ ├── sync-data.mdx │ └── update-widget.mdx ├── examples └── configurable_widget │ ├── .gitignore │ ├── .metadata │ ├── README.md │ ├── analysis_options.yaml │ ├── ios │ ├── .gitignore │ ├── ConfigurableWidget │ │ ├── AppIntent.swift │ │ ├── Assets.xcassets │ │ │ ├── AccentColor.colorset │ │ │ │ └── Contents.json │ │ │ ├── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ └── WidgetBackground.colorset │ │ │ │ └── Contents.json │ │ ├── ConfigurableWidget.swift │ │ ├── ConfigurableWidgetBundle.swift │ │ ├── Info.plist │ │ └── Intents.intentdefinition │ ├── ConfigurableWidgetExtension.entitlements │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Podfile │ ├── PunctuationIntent │ │ ├── Info.plist │ │ ├── IntentHandler.swift │ │ └── PunctuationIntent.entitlements │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ └── xcshareddata │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ ├── Runner-Bridging-Header.h │ │ └── Runner.entitlements │ ├── lib │ └── main.dart │ └── pubspec.yaml ├── melos.yaml ├── packages └── home_widget │ ├── .gitignore │ ├── .metadata │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── analysis_options.yaml │ ├── android │ ├── .gitignore │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ ├── settings.gradle │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ └── kotlin │ │ └── es │ │ └── antonborri │ │ └── home_widget │ │ ├── HomeWidgetBackgroundReceiver.kt │ │ ├── HomeWidgetBackgroundService.kt │ │ ├── HomeWidgetGlanceState.kt │ │ ├── HomeWidgetGlanceWidgetReceiver.kt │ │ ├── HomeWidgetIntent.kt │ │ ├── HomeWidgetPlugin.kt │ │ └── HomeWidgetProvider.kt │ ├── dart_test.yaml │ ├── example │ ├── .gitignore │ ├── .metadata │ ├── README.md │ ├── analysis_options.yaml │ ├── android │ │ ├── .gitignore │ │ ├── app │ │ │ ├── build.gradle │ │ │ ├── proguard-rules.pro │ │ │ └── src │ │ │ │ ├── debug │ │ │ │ └── AndroidManifest.xml │ │ │ │ ├── main │ │ │ │ ├── AndroidManifest.xml │ │ │ │ ├── kotlin │ │ │ │ │ └── es │ │ │ │ │ │ └── antonborri │ │ │ │ │ │ └── home_widget_example │ │ │ │ │ │ ├── HomeWidgetExampleProvider.kt │ │ │ │ │ │ ├── MainActivity.kt │ │ │ │ │ │ └── glance │ │ │ │ │ │ ├── HomeWidgetGlanceAppWidget.kt │ │ │ │ │ │ └── HomeWidgetReceiver.kt │ │ │ │ └── res │ │ │ │ │ ├── drawable │ │ │ │ │ ├── launch_background.xml │ │ │ │ │ └── widget_background.xml │ │ │ │ │ ├── layout │ │ │ │ │ └── example_layout.xml │ │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── values │ │ │ │ │ └── styles.xml │ │ │ │ │ └── xml │ │ │ │ │ ├── home_widget_example.xml │ │ │ │ │ └── home_widget_glance_example.xml │ │ │ │ └── profile │ │ │ │ └── AndroidManifest.xml │ │ ├── build.gradle │ │ ├── gradle.properties │ │ ├── gradle │ │ │ └── wrapper │ │ │ │ └── gradle-wrapper.properties │ │ └── settings.gradle │ ├── integration_test │ │ ├── android_test.dart │ │ └── ios_test.dart │ ├── ios │ │ ├── .gitignore │ │ ├── Flutter │ │ │ ├── AppFrameworkInfo.plist │ │ │ ├── Debug.xcconfig │ │ │ └── Release.xcconfig │ │ ├── HomeWidgetExample │ │ │ ├── Assets.xcassets │ │ │ │ ├── AccentColor.colorset │ │ │ │ │ └── Contents.json │ │ │ │ ├── AppIcon.appiconset │ │ │ │ │ └── Contents.json │ │ │ │ ├── Contents.json │ │ │ │ └── WidgetBackground.colorset │ │ │ │ │ └── Contents.json │ │ │ ├── HomeWidgetExample.swift │ │ │ └── Info.plist │ │ ├── HomeWidgetExampleExtension.entitlements │ │ ├── Podfile │ │ ├── Runner.xcodeproj │ │ │ ├── project.pbxproj │ │ │ ├── project.xcworkspace │ │ │ │ └── xcshareddata │ │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ │ └── WorkspaceSettings.xcsettings │ │ │ └── xcshareddata │ │ │ │ └── xcschemes │ │ │ │ └── Runner.xcscheme │ │ ├── Runner.xcworkspace │ │ │ └── xcshareddata │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ ├── Runner │ │ │ ├── AppDelegate.swift │ │ │ ├── Assets.xcassets │ │ │ │ ├── AppIcon.appiconset │ │ │ │ │ ├── Contents.json │ │ │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ │ │ ├── Icon-App-20x20@1x.png │ │ │ │ │ ├── Icon-App-20x20@2x.png │ │ │ │ │ ├── Icon-App-20x20@3x.png │ │ │ │ │ ├── Icon-App-29x29@1x.png │ │ │ │ │ ├── Icon-App-29x29@2x.png │ │ │ │ │ ├── Icon-App-29x29@3x.png │ │ │ │ │ ├── Icon-App-40x40@1x.png │ │ │ │ │ ├── Icon-App-40x40@2x.png │ │ │ │ │ ├── Icon-App-40x40@3x.png │ │ │ │ │ ├── Icon-App-60x60@2x.png │ │ │ │ │ ├── Icon-App-60x60@3x.png │ │ │ │ │ ├── Icon-App-76x76@1x.png │ │ │ │ │ ├── Icon-App-76x76@2x.png │ │ │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ │ │ └── LaunchImage.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ ├── LaunchImage.png │ │ │ │ │ ├── LaunchImage@2x.png │ │ │ │ │ ├── LaunchImage@3x.png │ │ │ │ │ └── README.md │ │ │ ├── BackgroundIntent.swift │ │ │ ├── Base.lproj │ │ │ │ ├── LaunchScreen.storyboard │ │ │ │ └── Main.storyboard │ │ │ ├── Info.plist │ │ │ ├── Runner-Bridging-Header.h │ │ │ └── Runner.entitlements │ │ └── RunnerTests │ │ │ └── RunnerTests.swift │ ├── lib │ │ └── main.dart │ ├── pubspec.yaml │ └── test_driver │ │ └── integration_test.dart │ ├── ios │ ├── .gitignore │ ├── Assets │ │ └── .gitkeep │ ├── Classes │ │ ├── HomeWidgetBackgroundWorker.swift │ │ └── HomeWidgetPlugin.swift │ └── home_widget.podspec │ ├── lib │ ├── home_widget.dart │ └── src │ │ ├── home_widget.dart │ │ ├── home_widget_callback_dispatcher.dart │ │ └── home_widget_info.dart │ ├── pub │ └── screenshots │ │ ├── android.png │ │ └── ios-counter.gif │ ├── pubspec.yaml │ └── test │ ├── background_test.dart │ ├── goldens │ └── render-flutter-widget.png │ ├── home_widget_info_test.dart │ ├── home_widget_test.dart │ └── mocks.dart └── pubspec.yaml /.github/.cspell/names_dictionary.txt: -------------------------------------------------------------------------------- 1 | # specific people's names and/or usernames 2 | mchudy # github.com/mchudy 3 | milindgoel15 # github.com/milindgoel15 4 | mattrltrent # github.com/mattrltrent 5 | eliasto # github.com/eliasto 6 | leighajarett # github.com/leighajarett 7 | ColinSchmale # github.com/ColinSchmale 8 | linziyou # github.com/linziyou0601 9 | roly # github.com/roly151 10 | aaronkelton # github.com/aaronkelton 11 | hadysata # github.com/hadysata 12 | Verbeeck # github.com/NicolaVerbeeck 13 | ronnieeeeee # github.com/ronnieeeeee 14 | josepedromonteiro # github.com/josepedromonteiro 15 | stepushchik # github.com/stepushchik-denis-gismart 16 | gismart # github.com/stepushchik-denis-gismart 17 | aljkor # github.com/aljkor 18 | mgonzalezc # github.com/mgonzalezc 19 | subosito # github.com/subosito 20 | bluefireteam # github.com/bluefireteam -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: abausg -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: You discovered a Bug in your Homescreen Widget / in the home_widget Plugin 3 | labels: ["bug"] 4 | 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | When reporting a bug, please read this complete template and fill in all the questions in order to get a better response! Before filing a bug please make sure you read the documentation, check for related issues and discussions. 10 | - type: textarea 11 | id: what-happened 12 | attributes: 13 | label: What happened? 14 | description: Tell us, what happened? 15 | validations: 16 | required: true 17 | 18 | - type: textarea 19 | id: expectation 20 | attributes: 21 | label: What do you expect? 22 | description: Also tell us, what behavior did you expect? 23 | validations: 24 | required: true 25 | 26 | - type: textarea 27 | id: logs 28 | attributes: 29 | label: Relevant log output 30 | description: If you have any debug / error logging, please fill it here within the code block below 31 | render: shell 32 | 33 | - type: textarea 34 | attributes: 35 | label: Execute in a terminal and put output into the code block below 36 | value: 'Output of: flutter doctor -v' 37 | 38 | - type: dropdown 39 | id: affected-platforms 40 | attributes: 41 | label: On which Platform do you experience this issue? 42 | multiple: true 43 | options: 44 | - Android 45 | - iOS 46 | validations: 47 | required: true 48 | 49 | - type: textarea 50 | id: other-information 51 | attributes: 52 | label: Other information 53 | description: Do you have any other useful information about this bug report? Please write it down here 54 | 55 | - type: checkboxes 56 | id: terms 57 | attributes: 58 | label: Are you interested in working on a PR for this? 59 | options: 60 | - label: I want to work on this 61 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: I need a help with building my Widget. 4 | url: https://github.com/ABausG/home_widget/discussions 5 | about: Have a look at the discussions, maybe your question is already answered. Or the community can help you! -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Suggest a new feature for home_widget. 3 | labels: [ "enhancement" ] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | When suggesting a feature, please read this complete form and fill in all the questions in order to get a better response! 9 | - type: textarea 10 | id: problem-to-solve 11 | attributes: 12 | label: Problem to solve 13 | description: Which problem would be solved with this feature? 14 | validations: 15 | required: true 16 | - type: textarea 17 | id: proposal 18 | attributes: 19 | label: Proposal 20 | description: What do you propose as a solution? Add as much information as you can! 21 | validations: 22 | required: true 23 | - type: textarea 24 | id: more-information 25 | attributes: 26 | label: More information 27 | description: Do you have any other useful information about this feature report? Please write it down here. Possible helpful information are references to other sites/repositories. 28 | - type: dropdown 29 | id: affected-platforms 30 | attributes: 31 | label: Which Platform would be improved? 32 | multiple: true 33 | options: 34 | - Android 35 | - iOS 36 | - type: checkboxes 37 | id: other 38 | attributes: 39 | label: Other 40 | options: 41 | - label: Are you interested in working on a PR for this? -------------------------------------------------------------------------------- /.github/assets/build_phases.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/.github/assets/build_phases.png -------------------------------------------------------------------------------- /.github/assets/demo_android.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/.github/assets/demo_android.png -------------------------------------------------------------------------------- /.github/assets/demo_ios.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/.github/assets/demo_ios.png -------------------------------------------------------------------------------- /.github/assets/target.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/.github/assets/target.png -------------------------------------------------------------------------------- /.github/assets/target_membership.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/.github/assets/target_membership.png -------------------------------------------------------------------------------- /.github/assets/widget_extension.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/.github/assets/widget_extension.png -------------------------------------------------------------------------------- /.github/cspell.yaml: -------------------------------------------------------------------------------- 1 | version: "0.2" 2 | ignorePaths: 3 | [ 4 | "**/build/**", 5 | "**/coverage", 6 | "**/*.xcscheme", 7 | "**/*.storyboard", 8 | "**/GeneratedPluginRegistrant.m", 9 | "**/GeneratedPluginRegistrant.java", 10 | "**/*.pbxproj", 11 | "**/*.xcconfig", 12 | "**/**/*.podspec", 13 | "**/Info.plist", 14 | "**/*.xcassets", 15 | "**/*.xcworkspace", 16 | "**/*.xcodeproj", 17 | "**/Pods/Target Support Files/", 18 | "**/Pods/Local Podspecs", 19 | "**/gradlew", 20 | "**/gradlew.bat", 21 | "**/gradle-wrapper.properties", 22 | "**/AppFrameworkInfo.plist", 23 | "**/gradle.properties", 24 | "cspell.yaml", 25 | "**/build.gradle", 26 | "**/proguard-rules.pro", 27 | "**/.github/**", 28 | "**/.gitignore", 29 | "**/*.intentdefinition" 30 | ] 31 | dictionaries: 32 | - names 33 | dictionaryDefinitions: 34 | - name: names 35 | path: .cspell/names_dictionary.txt 36 | words: 37 | - antonborries 38 | - Borries 39 | - abausg 40 | - antonborri 41 | - mocktail 42 | - Goldens 43 | - cupertino 44 | - workmanager 45 | - cocoapods 46 | - renderview 47 | - appex 48 | - pinability 49 | - chào 50 | - titleclicked 51 | - NSURL 52 | - xcconfig 53 | - podhelper 54 | - podfile 55 | - realpath 56 | - appwidget 57 | - Millis # abbreviation for Milliseconds 58 | - androidx 59 | - Jetpack 60 | - kotlinx 61 | - lateinit 62 | - prefs # short for preferences 63 | - mipmap # android image format 64 | - nullsafety 65 | - Deeplinks 66 | - Deque 67 | - ktfmt 68 | - libexec 69 | - SRCROOT 70 | - Codelab 71 | - pubspec 72 | - Siri 73 | - objc 74 | - typealias 75 | - xcodeproj 76 | - pbxproj 77 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 7 | 8 | # Description 9 | 15 | Replace this text. 16 | 17 | ## Checklist 18 | 22 | 23 | - [ ] I have updated/added tests for ALL new/updated/fixed functionality. 24 | - [ ] I have updated/added relevant documentation and added code (documentation) comments where necessary. 25 | - [ ] I have updated/added relevant examples in `example` or documentation. 26 | 27 | 28 | ## Breaking Change? 29 | 36 | 37 | - [ ] Yes, this PR is a breaking change. 38 | - [ ] No, this PR is not a breaking change. 39 | 40 | 46 | 47 | 48 | ## Related Issues 49 | 54 | 55 | 56 | [Conventional Commit]: https://conventionalcommits.org 57 | [CHANGELOG]: https://github.com/abausg/home_widget/blob/main/CHANGELOG.md 58 | -------------------------------------------------------------------------------- /.github/workflows/flutter-beta.yml: -------------------------------------------------------------------------------- 1 | name: Build Flutter Beta 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - flutter-beta 8 | pull_request: 9 | branches: 10 | - flutter-beta 11 | 12 | concurrency: 13 | group: ${{ github.workflow }}-${{ github.ref }} 14 | cancel-in-progress: true 15 | 16 | jobs: 17 | quality: 18 | name: Quality Checks 19 | runs-on: macos-14 20 | defaults: 21 | run: 22 | working-directory: packages/home_widget 23 | 24 | steps: 25 | - uses: actions/checkout@v2 26 | 27 | - uses: subosito/flutter-action@v1 28 | with: 29 | channel: beta 30 | - uses: bluefireteam/melos-action@v3 31 | - name: Analyze 32 | run: melos analyze 33 | - name: Install Formatters 34 | run: brew install swift-format ktfmt 35 | - name: Format 36 | run: melos format:all 37 | - name: Publishability 38 | run: flutter pub publish --dry-run 39 | - name: Test 40 | run: flutter test --coverage 41 | - name: Archieve Golden Failures 42 | if: failure() 43 | uses: actions/upload-artifact@v3 44 | with: 45 | name: Golden failures 46 | retention-days: 2 47 | path: | 48 | **/test/**/failures/**/*.* 49 | - name: Upload coverage to Codecov 50 | uses: codecov/codecov-action@v1 51 | with: 52 | token: ${{ secrets.CODECOV_TOKEN }} 53 | path: ./packages/home_widget/coverage/lcov.info 54 | 55 | android: 56 | name: Android Integration Tests 57 | runs-on: ubuntu-latest 58 | 59 | steps: 60 | - uses: actions/checkout@v2 61 | - uses: subosito/flutter-action@v1 62 | with: 63 | channel: beta 64 | - uses: bluefireteam/melos-action@v3 65 | - name: Enable KVM 66 | run: | 67 | echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules 68 | sudo udevadm control --reload-rules 69 | sudo udevadm trigger --name-match=kvm 70 | - name: Set up JDK 11 71 | uses: actions/setup-java@v4 72 | with: 73 | distribution: 'temurin' 74 | java-version: '11' 75 | - name: Run Android Integration Tests 76 | uses: reactivecircus/android-emulator-runner@v2 77 | with: 78 | api-level: 29 79 | script: flutter test integration_test/android_test.dart -d emulator-5554 80 | working-directory: packages/home_widget/example 81 | 82 | # iOS Test based on https://medium.com/flutter-community/run-flutter-driver-tests-on-github-actions-13c639c7e4ab 83 | # by @kate_sheremet 84 | ios: 85 | name: iOS Integration Tests 86 | strategy: 87 | matrix: 88 | device: 89 | - "iPhone 14" 90 | fail-fast: false 91 | runs-on: macos-14 92 | defaults: 93 | run: 94 | working-directory: packages/home_widget/example 95 | steps: 96 | - uses: actions/checkout@v2 97 | - uses: maxim-lobanov/setup-xcode@v1 98 | with: 99 | xcode-version: latest 100 | - uses: subosito/flutter-action@v1 101 | with: 102 | channel: beta 103 | - uses: bluefireteam/melos-action@v3 104 | - uses: futureware-tech/simulator-action@v1 105 | id: simulator 106 | with: 107 | model: ${{ matrix.device }} 108 | - name: "Run iOS integration tests" 109 | run: flutter test integration_test/ios_test.dart -d ${{steps.simulator.outputs.udid}} -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - main 8 | - dev 9 | pull_request: 10 | branches: 11 | - main 12 | - dev 13 | 14 | concurrency: 15 | group: ${{ github.workflow }}-${{ github.ref }} 16 | cancel-in-progress: true 17 | 18 | jobs: 19 | quality: 20 | name: Quality Checks 21 | runs-on: macos-15 22 | defaults: 23 | run: 24 | working-directory: packages/home_widget 25 | 26 | steps: 27 | - uses: actions/checkout@v2 28 | 29 | - uses: subosito/flutter-action@v1 30 | with: 31 | channel: stable 32 | - uses: bluefireteam/melos-action@v3 33 | - name: Analyze 34 | run: melos analyze 35 | - name: Install Formatters 36 | run: brew install swift-format ktfmt 37 | - name: Format 38 | run: melos format:all 39 | - name: Publishability 40 | run: flutter pub publish --dry-run 41 | - name: Test 42 | run: flutter test --coverage 43 | - name: Archieve Golden Failures 44 | if: failure() 45 | uses: actions/upload-artifact@v4 46 | with: 47 | name: Golden failures 48 | retention-days: 2 49 | path: | 50 | **/test/**/failures/**/*.* 51 | - name: Upload coverage to Codecov 52 | uses: codecov/codecov-action@v1 53 | with: 54 | token: ${{ secrets.CODECOV_TOKEN }} 55 | path: ./packages/home_widget/coverage/lcov.info 56 | 57 | android: 58 | name: Android Integration Tests 59 | runs-on: ubuntu-latest 60 | 61 | steps: 62 | - uses: actions/checkout@v2 63 | - uses: subosito/flutter-action@v1 64 | with: 65 | channel: stable 66 | - uses: bluefireteam/melos-action@v3 67 | - name: Enable KVM 68 | run: | 69 | echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules 70 | sudo udevadm control --reload-rules 71 | sudo udevadm trigger --name-match=kvm 72 | - name: Set up JDK 21 73 | uses: actions/setup-java@v4 74 | with: 75 | distribution: 'temurin' 76 | java-version: '21' 77 | - name: Run Android Integration Tests 78 | uses: reactivecircus/android-emulator-runner@v2 79 | with: 80 | api-level: 29 81 | script: flutter test integration_test/android_test.dart -d emulator-5554 82 | working-directory: packages/home_widget/example 83 | 84 | # iOS Test based on https://medium.com/flutter-community/run-flutter-driver-tests-on-github-actions-13c639c7e4ab 85 | # by @kate_sheremet 86 | ios: 87 | name: iOS Integration Tests 88 | strategy: 89 | matrix: 90 | device: 91 | - "iPhone 15" 92 | fail-fast: false 93 | runs-on: macos-15 94 | defaults: 95 | run: 96 | working-directory: packages/home_widget/example 97 | steps: 98 | - uses: actions/checkout@v2 99 | - uses: maxim-lobanov/setup-xcode@v1 100 | with: 101 | xcode-version: latest 102 | - uses: subosito/flutter-action@v1 103 | with: 104 | channel: stable 105 | - uses: bluefireteam/melos-action@v3 106 | - uses: futureware-tech/simulator-action@v1 107 | id: simulator 108 | with: 109 | model: ${{ matrix.device }} 110 | - name: "Run iOS integration tests" 111 | run: flutter test integration_test/ios_test.dart -d ${{steps.simulator.outputs.udid}} -------------------------------------------------------------------------------- /.github/workflows/release-prepare.yml: -------------------------------------------------------------------------------- 1 | name: Prepare release 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | prerelease: 6 | description: 'Version as prerelease' 7 | required: false 8 | default: false 9 | type: boolean 10 | 11 | jobs: 12 | prepare-release: 13 | name: Prepare release 14 | permissions: 15 | contents: write 16 | pull-requests: write 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v4 20 | with: 21 | fetch-depth: 0 22 | - uses: subosito/flutter-action@v2 23 | - uses: bluefireteam/melos-action@v3 24 | with: 25 | run-versioning: ${{ inputs.prerelease == false }} 26 | run-versioning-prerelease: ${{ inputs.prerelease == true }} 27 | publish-dry-run: true 28 | create-pr: true 29 | git-email: github@antonborri.es 30 | git-name: Melos Action -------------------------------------------------------------------------------- /.github/workflows/release-publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish packages 2 | on: 3 | push: 4 | tags: 5 | - 'home_widget-v*' 6 | workflow_dispatch: 7 | 8 | jobs: 9 | publish-packages: 10 | name: Publish packages 11 | permissions: 12 | contents: write 13 | id-token: write 14 | runs-on: [ ubuntu-latest ] 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: subosito/flutter-action@v2 18 | - uses: bluefireteam/melos-action@v3 19 | with: 20 | publish: true 21 | git-email: github@antonborri.es 22 | git-name: Melos Action -------------------------------------------------------------------------------- /.github/workflows/release-tag.yml: -------------------------------------------------------------------------------- 1 | name: Tag release 2 | on: 3 | push: 4 | branches: [main] 5 | 6 | jobs: 7 | publish-packages: 8 | name: Create tag for a release 9 | permissions: 10 | contents: write 11 | runs-on: [ ubuntu-latest ] 12 | if: contains(github.event.head_commit.message, 'chore(release)') 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: subosito/flutter-action@v2 16 | - uses: bluefireteam/melos-action@v3 17 | with: 18 | tag: true -------------------------------------------------------------------------------- /.github/workflows/spellcheck.yml: -------------------------------------------------------------------------------- 1 | name: Spellcheck 2 | 3 | on: 4 | pull_request: 5 | 6 | concurrency: 7 | group: ${{ github.workflow }}-${{ github.ref }} 8 | cancel-in-progress: true 9 | 10 | jobs: 11 | test: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: streetsidesoftware/cspell-action@v6 16 | with: 17 | config: .github/cspell.yaml -------------------------------------------------------------------------------- /.github/workflows/title-validator.yml: -------------------------------------------------------------------------------- 1 | # See https://github.com/amannn/action-semantic-pull-request 2 | name: 'PR Title is Conventional' 3 | 4 | on: 5 | pull_request_target: 6 | types: 7 | - opened 8 | - edited 9 | - synchronize 10 | 11 | jobs: 12 | main: 13 | name: Validate PR title 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: amannn/action-semantic-pull-request@v5 17 | env: 18 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 19 | with: 20 | types: | 21 | build 22 | chore 23 | ci 24 | docs 25 | feat 26 | fix 27 | perf 28 | refactor 29 | revert 30 | style 31 | test 32 | subjectPattern: ^[A-Z].+$ 33 | subjectPatternError: | 34 | The subject of the PR must begin with an uppercase letter. -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | 4 | .packages 5 | .pub/ 6 | 7 | build/ 8 | .idea 9 | 10 | # Miscellaneous 11 | *.class 12 | *.lock 13 | *.log 14 | *.pyc 15 | *.swp 16 | .DS_Store 17 | .atom/ 18 | .buildlog/ 19 | .history 20 | .svn/ 21 | 22 | # IntelliJ related 23 | *.iml 24 | *.ipr 25 | *.iws 26 | .idea/ 27 | 28 | # Visual Studio Code related 29 | .classpath 30 | .project 31 | .settings/ 32 | .vscode/ 33 | 34 | # Flutter repo-specific 35 | /bin/cache/ 36 | /bin/mingit/ 37 | /dev/benchmarks/mega_gallery/ 38 | /dev/bots/.recipe_deps 39 | /dev/bots/android_tools/ 40 | /dev/devicelab/ABresults*.json 41 | /dev/docs/doc/ 42 | /dev/docs/flutter.docs.zip 43 | /dev/docs/lib/ 44 | /dev/docs/pubspec.yaml 45 | /dev/integration_tests/**/xcuserdata 46 | /dev/integration_tests/**/Pods 47 | /packages/flutter/coverage/ 48 | version 49 | analysis_benchmark.json 50 | 51 | # packages file containing multi-root paths 52 | .packages.generated 53 | 54 | # Flutter/Dart/Pub related 55 | **/doc/api/ 56 | .dart_tool/ 57 | .flutter-plugins 58 | .flutter-plugins-dependencies 59 | **/generated_plugin_registrant.dart 60 | .packages 61 | .pub-cache/ 62 | .pub/ 63 | build/ 64 | flutter_*.png 65 | linked_*.ds 66 | unlinked.ds 67 | unlinked_spec.ds 68 | 69 | # Android related 70 | **/android/**/gradle-wrapper.jar 71 | **/android/.gradle 72 | **/android/captures/ 73 | **/android/gradlew 74 | **/android/gradlew.bat 75 | **/android/local.properties 76 | **/android/**/GeneratedPluginRegistrant.java 77 | **/android/key.properties 78 | *.jks 79 | 80 | # iOS/XCode related 81 | **/ios/**/*.mode1v3 82 | **/ios/**/*.mode2v3 83 | **/ios/**/*.moved-aside 84 | **/ios/**/*.pbxuser 85 | **/ios/**/*.perspectivev3 86 | **/ios/**/*sync/ 87 | **/ios/**/.sconsign.dblite 88 | **/ios/**/.tags* 89 | **/ios/**/.vagrant/ 90 | **/ios/**/DerivedData/ 91 | **/ios/**/Icon? 92 | **/ios/**/Pods/ 93 | **/ios/**/.symlinks/ 94 | **/ios/**/profile 95 | **/ios/**/xcuserdata 96 | **/ios/.generated/ 97 | **/ios/Flutter/.last_build_id 98 | **/ios/Flutter/App.framework 99 | **/ios/Flutter/Flutter.framework 100 | **/ios/Flutter/Flutter.podspec 101 | **/ios/Flutter/Generated.xcconfig 102 | **/ios/Flutter/app.flx 103 | **/ios/Flutter/app.zip 104 | **/ios/Flutter/flutter_assets/ 105 | **/ios/Flutter/flutter_export_environment.sh 106 | **/ios/ServiceDefinitions.json 107 | **/ios/Runner/GeneratedPluginRegistrant.* 108 | 109 | # macOS 110 | **/macos/Flutter/GeneratedPluginRegistrant.swift 111 | **/macos/Flutter/Flutter-Debug.xcconfig 112 | **/macos/Flutter/Flutter-Release.xcconfig 113 | **/macos/Flutter/Flutter-Profile.xcconfig 114 | 115 | # Coverage 116 | coverage/ 117 | 118 | # Symbols 119 | app.*.symbols 120 | 121 | # Exceptions to above rules. 122 | !**/ios/**/default.mode1v3 123 | !**/ios/**/default.mode2v3 124 | !**/ios/**/default.pbxuser 125 | !**/ios/**/default.perspectivev3 126 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 127 | !/dev/ci/**/Gemfile.lock 128 | .gradle 129 | *.xcworkspacedata 130 | *.jar 131 | 132 | # don't check in golden failure output 133 | **/failures/*.png 134 | pubspec_overrides.yaml 135 | .cxx 136 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## 2025-05-25 7 | 8 | ### Changes 9 | 10 | --- 11 | 12 | Packages with breaking changes: 13 | 14 | - [`home_widget` - `v0.8.0`](#home_widget---v080) 15 | 16 | Packages with other changes: 17 | 18 | - There are no other changes in this release. 19 | 20 | --- 21 | 22 | #### `home_widget` - `v0.8.0` 23 | 24 | - **FEAT**: Configurable Widgets support for iOS ([#348](https://github.com/abausg/home_widget/issues/348)). ([e8809d89](https://github.com/abausg/home_widget/commit/e8809d89c15348cb3ded7769278add51ce4b2379)) 25 | - **FEAT**: Add triggeredFromHomeWidget flag to updateWidget on Android ([#315](https://github.com/abausg/home_widget/issues/315)). ([dc2b9302](https://github.com/abausg/home_widget/commit/dc2b9302c30e6690f1f084e4fad2b1041a1d8c88)) 26 | - **BREAKING** **FEAT**: Default to the device pixel ratio ([#304](https://github.com/abausg/home_widget/issues/304)). ([90522de3](https://github.com/abausg/home_widget/commit/90522de374d5411842e84031453756eeec25ac9e)) 27 | - **BREAKING** **CHORE**: Enable strong language analyzer ([#305](https://github.com/abausg/home_widget/issues/305)). ([1b5df0b3](https://github.com/abausg/home_widget/commit/1b5df0b36e0ccf0c0ffef234faf0ed8731f9ade4)) 28 | 29 | 30 | ## 2025-02-06 31 | 32 | ### Changes 33 | 34 | --- 35 | 36 | Packages with breaking changes: 37 | 38 | - There are no breaking changes in this release. 39 | 40 | Packages with other changes: 41 | 42 | - [`home_widget` - `v0.7.0+1`](#home_widget---v0701) 43 | 44 | --- 45 | 46 | #### `home_widget` - `v0.7.0+1` 47 | 48 | - **FIX**: Runtime error when starting App from Widget on Android 15 ([#330](https://github.com/abausg/home_widget/issues/330)). ([64a38eb3](https://github.com/abausg/home_widget/commit/64a38eb39fb6ef20342ac2a5eaf5c9bedf2e6c75)) 49 | - **DOCS**: Move Documentation to docs.page ([#287](https://github.com/abausg/home_widget/issues/287)). ([52ee746a](https://github.com/abausg/home_widget/commit/52ee746ad1d1dd9ef2aa9f1c61e482825f73d9d9)) 50 | - **DOCS**: Improve pubspec metadata ([#283](https://github.com/abausg/home_widget/issues/283)). ([f23c63e8](https://github.com/abausg/home_widget/commit/f23c63e8d393708aaf197ccb54b391d81a765a19)) 51 | 52 | 53 | ## 2024-08-28 54 | 55 | ### Changes 56 | 57 | --- 58 | 59 | Packages with breaking changes: 60 | 61 | - There are no breaking changes in this release. 62 | 63 | Packages with other changes: 64 | 65 | - [`home_widget` - `v0.7.0`](#home_widget---v070) 66 | 67 | --- 68 | 69 | #### `home_widget` - `v0.7.0` 70 | 71 | - **DOCS**: Move Documentation to docs.page ([#287](https://github.com/abausg/home_widget/issues/287)). ([52ee746a](https://github.com/abausg/home_widget/commit/52ee746ad1d1dd9ef2aa9f1c61e482825f73d9d9)) 72 | - **DOCS**: Improve pubspec metadata ([#283](https://github.com/abausg/home_widget/issues/283)). ([f23c63e8](https://github.com/abausg/home_widget/commit/f23c63e8d393708aaf197ccb54b391d81a765a19)) 73 | 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | packages/home_widget/README.md -------------------------------------------------------------------------------- /docs.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "home_widget", 3 | "theme": "#567acb", 4 | "content": { 5 | "headerDepth": 4 6 | }, 7 | "social": { 8 | "x": "@abausg", 9 | "github": "abausg", 10 | "linkedin": "/in/abausg" 11 | }, 12 | "sidebar": [ 13 | [ 14 | "Overview", 15 | "/" 16 | ], 17 | [ 18 | "Usage", 19 | [ 20 | [ 21 | "Sync Data", 22 | "/usage/sync-data" 23 | ], 24 | [ 25 | "Update Widget", 26 | "/usage/update-widget" 27 | ] 28 | ] 29 | ], 30 | [ 31 | "Platform Setup", 32 | [ 33 | [ 34 | "iOS", 35 | "/setup/ios" 36 | ], 37 | [ 38 | "Android", 39 | "/setup/android" 40 | ] 41 | ] 42 | ], 43 | [ 44 | "Features", 45 | [ 46 | [ 47 | "Render Flutter Widgets", 48 | "/features/render-flutter-widgets" 49 | ], 50 | [ 51 | "Interactive Widgets", 52 | "/features/interactive-widgets" 53 | ], 54 | [ 55 | "Detect Clicks", 56 | "/features/detect-clicks" 57 | ], 58 | [ 59 | "Analytics", 60 | "/features/analytics" 61 | ], 62 | [ 63 | "Configurable Widgets", 64 | "/features/configurable-widgets" 65 | ], 66 | [ 67 | "iOS Lock Screen Widgets", 68 | "/features/ios-lock-screen" 69 | ], 70 | [ 71 | "Pin Widget (Android)", 72 | "/features/pin-widget" 73 | ], 74 | [ 75 | "Background Updates", 76 | "/features/background-updates" 77 | ] 78 | ] 79 | ], 80 | [ 81 | "Android XML", 82 | [ 83 | [ 84 | "Overview", 85 | "/android-xml/overview" 86 | ], 87 | [ 88 | "Setup", 89 | "/android-xml/setup" 90 | ], 91 | [ 92 | "Render Flutter Widgets", 93 | "/android-xml/render-flutter-widget" 94 | ], 95 | [ 96 | "Interactive Widgets", 97 | "/android-xml/interactive-widgets" 98 | ], 99 | [ 100 | "Detect Clicks", 101 | "/android-xml/detect-clicks" 102 | ] 103 | ] 104 | ] 105 | ] 106 | } 107 | -------------------------------------------------------------------------------- /docs/android-xml/detect-clicks.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Detect Clicks 3 | description: Detecting Clicks on Android Widgets 4 | --- 5 | 6 | # Detect Clicks on with Android XML Widgets 7 | 8 | Add an `IntentFilter` to the `Activity` Section in your `AndroidManifest` 9 | ``` 10 | 11 | 12 | 13 | ``` 14 | 15 | In your WidgetProvider add a PendingIntent to your View using `HomeWidgetLaunchIntent.getActivity` 16 | ```kotlin 17 | val pendingIntentWithData = HomeWidgetLaunchIntent.getActivity( 18 | context, 19 | MainActivity::class.java, 20 | Uri.parse("homeWidgetExample://message?message=$message")) 21 | setOnClickPendingIntent(R.id.widget_message, pendingIntentWithData) 22 | ``` -------------------------------------------------------------------------------- /docs/android-xml/interactive-widgets.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Interactive Widgets 3 | description: How to create interactive Widgets with home_widget and Android XML Widgets 4 | --- 5 | 6 | # Interactive Widgets 7 | 8 | Follow the necessary steps to set up everything on the Flutter Side as described [here](/features/interactive-widgets#flutter-setup). 9 | 10 | 1. Add the necessary Receiver and Service to your `AndroidManifest.xml` file 11 | ``` 12 | 13 | 14 | 15 | 16 | 17 | 19 | ``` 20 | 2. Add a `HomeWidgetBackgroundIntent.getBroadcast` PendingIntent to the View you want to add a click listener to 21 | ```kotlin 22 | val backgroundIntent = HomeWidgetBackgroundIntent.getBroadcast( 23 | context, 24 | Uri.parse("homeWidgetExample://titleClicked") 25 | ) 26 | setOnClickPendingIntent(R.id.widget_title, backgroundIntent) 27 | ``` 28 | -------------------------------------------------------------------------------- /docs/android-xml/overview.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Android XML 3 | description: home_widget and Android XML Widgets 4 | --- 5 | 6 | # Android XML Support 7 | 8 | home_widget supports Android Widgets created with XML. This allows you to create Widgets for your Android App using the home_widget Plugin. 9 | However going forward it is recommended to use the [Jetpack Glance](/setup/android) way to create Widgets home_widget. 10 | 11 | In the following pages you can still find information on how to use respective Features of home_widget with Android XML Widgets. 12 | 13 | -------------------------------------------------------------------------------- /docs/android-xml/render-flutter-widget.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Render Flutter Widget 3 | description: Render Flutter Widgets in Android XML Widgets 4 | --- 5 | 6 | # Render Flutter Widgets 7 | 8 | 1. Add an image UI element to your xml file: 9 | ```xml 10 | 29 | ``` 30 | 2. Update your Kotlin code to get the chart image and put it into the widget, if it exists. 31 | ```kotlin 32 | class NewsWidget : AppWidgetProvider() { 33 | override fun onUpdate( 34 | context: Context, 35 | appWidgetManager: AppWidgetManager, 36 | appWidgetIds: IntArray, 37 | ) { 38 | for (appWidgetId in appWidgetIds) { 39 | // Get reference to SharedPreferences 40 | val widgetData = HomeWidgetPlugin.getData(context) 41 | val views = RemoteViews(context.packageName, R.layout.news_widget).apply { 42 | // Get chart image and put it in the widget, if it exists 43 | val imagePath = widgetData.getString("lineChart", null) 44 | val imageFile = File(imagePath) 45 | val imageExists = imageFile.exists() 46 | if (imageExists) { 47 | val myBitmap: Bitmap = BitmapFactory.decodeFile(imageFile.absolutePath) 48 | setImageViewBitmap(R.id.widget_image, myBitmap) 49 | } else { 50 | println("image not found!, looked @: $imagePath") 51 | } 52 | // End new code 53 | } 54 | appWidgetManager.updateAppWidget(appWidgetId, views) 55 | } 56 | } 57 | } 58 | ``` -------------------------------------------------------------------------------- /docs/android-xml/setup.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Android XML Setup 3 | description: Setup home_widget and Widgets with Android XML 4 | --- 5 | 6 | # Android XML Setup 7 | 8 | ## Necessary Files 9 | 10 | ### Widget Configuration 11 | In `android/app/src/main/res/xml` you need to create a configuration file. 12 | In here you can configure properties used for things like size constraints and preview layouts. 13 | ```xml 14 | 20 | 21 | ``` 22 | For more Information on the possible contents of this File check the official Android Documentation [here](https://developer.android.com/develop/ui/views/appwidgets#AppWidgetProviderInfo) 23 | 24 | 25 | ### Widget Layout 26 | Create Widget Layout inside `android/app/src/main/res/layout` 27 | 28 | This file contains the Layout of your Widget. Note that this can only use RemoteViews to build the Layout. 29 | 30 | ```xml 31 | 32 | 40 | 47 | 48 | ``` 49 | 50 | ### WidgetProvider 51 | The `WidgetProvider` is used to bind Data to the Layout. 52 | For convenience, you can extend from `HomeWidgetProvider` which gives you access to a SharedPreferences Object with the Data in the `onUpdate` method. 53 | 54 | ```kotlin 55 | // Remember to set a package in order for home_widget to find the Provider 56 | package es.antonborri.home_widget_counter 57 | 58 | class CounterWidgetProvider : HomeWidgetProvider() { 59 | override fun onUpdate( 60 | context: Context, 61 | appWidgetManager: AppWidgetManager, 62 | appWidgetIds: IntArray, 63 | widgetData: SharedPreferences) { 64 | appWidgetIds.forEach { widgetId -> 65 | val views = RemoteViews(context.packageName, R.layout.counter_widget).apply { 66 | val count = widgetData.getInt("counter", 0) 67 | setTextViewText(R.id.text_counter, count.toString()) 68 | } 69 | // This line is important to trigger the update 70 | appWidgetManager.updateAppWidget(widgetId, views) 71 | } 72 | } 73 | } 74 | ``` 75 | 76 | In case you don't want to use the convenience Method you can access the Data using 77 | ```kotlin 78 | import es.antonborri.home_widget.HomeWidgetPlugin 79 | ... 80 | HomeWidgetPlugin.getData(context) 81 | ``` 82 | which will give you access to the same SharedPreferences 83 | 84 | ### Add WidgetReceiver to AndroidManifest 85 | ```xml 86 | 87 | 88 | 89 | 90 | 92 | 93 | ``` 94 | 95 | ## More Information 96 | For more Information on how to create and configure Android Widgets, check out [this guide](https://developer.android.com/develop/ui/views/appwidgets) on the Android Developers Page. 97 | -------------------------------------------------------------------------------- /docs/assets/configurable/ios/app-group.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/docs/assets/configurable/ios/app-group.webp -------------------------------------------------------------------------------- /docs/assets/configurable/ios/app-intent-target-membership.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/docs/assets/configurable/ios/app-intent-target-membership.webp -------------------------------------------------------------------------------- /docs/assets/configurable/ios/configuration-in-flutter.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/docs/assets/configurable/ios/configuration-in-flutter.webp -------------------------------------------------------------------------------- /docs/assets/configurable/ios/configured-widgets.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/docs/assets/configurable/ios/configured-widgets.webp -------------------------------------------------------------------------------- /docs/assets/configurable/ios/create-widget-extension-ios-with-configuration.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/docs/assets/configurable/ios/create-widget-extension-ios-with-configuration.webp -------------------------------------------------------------------------------- /docs/assets/configurable/ios/intent-configuration.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/docs/assets/configurable/ios/intent-configuration.webp -------------------------------------------------------------------------------- /docs/assets/configurable/ios/simple-parameter.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/docs/assets/configurable/ios/simple-parameter.webp -------------------------------------------------------------------------------- /docs/assets/configurable/ios/siri-configuration-in-flutter.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/docs/assets/configurable/ios/siri-configuration-in-flutter.webp -------------------------------------------------------------------------------- /docs/assets/configurable/ios/siri-configured-example.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/docs/assets/configurable/ios/siri-configured-example.webp -------------------------------------------------------------------------------- /docs/assets/configurable/ios/siri-created-type.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/docs/assets/configurable/ios/siri-created-type.webp -------------------------------------------------------------------------------- /docs/assets/configurable/ios/siri-extension-dynamic-option.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/docs/assets/configurable/ios/siri-extension-dynamic-option.webp -------------------------------------------------------------------------------- /docs/assets/configurable/ios/siri-extension-target-membership.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/docs/assets/configurable/ios/siri-extension-target-membership.webp -------------------------------------------------------------------------------- /docs/assets/configurable/ios/siri-intent-file.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/docs/assets/configurable/ios/siri-intent-file.webp -------------------------------------------------------------------------------- /docs/assets/configurable/ios/siri-intents-extension.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/docs/assets/configurable/ios/siri-intents-extension.webp -------------------------------------------------------------------------------- /docs/assets/configurable/ios/siri-new-type.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/docs/assets/configurable/ios/siri-new-type.webp -------------------------------------------------------------------------------- /docs/assets/configurable/ios/siri-select-type.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/docs/assets/configurable/ios/siri-select-type.webp -------------------------------------------------------------------------------- /docs/assets/configurable/ios/widget-with-name-configuration.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/docs/assets/configurable/ios/widget-with-name-configuration.webp -------------------------------------------------------------------------------- /docs/assets/configurable/ios/world-to-flutter.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/docs/assets/configurable/ios/world-to-flutter.mp4 -------------------------------------------------------------------------------- /docs/assets/create-widget-extension-ios.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/docs/assets/create-widget-extension-ios.webp -------------------------------------------------------------------------------- /docs/assets/lockscreen.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/docs/assets/lockscreen.webp -------------------------------------------------------------------------------- /docs/assets/pin-widget.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/docs/assets/pin-widget.gif -------------------------------------------------------------------------------- /docs/features/analytics.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Analytics 3 | description: Check which Widgets Users have currently installed 4 | --- 5 | 6 | # Analytics 7 | 8 | Sometimes it can be useful to know which Widgets your Users have currently installed. 9 | 10 | Using home_widget you can check which Widgets a User currently has added to their HomeScreen 11 | 12 | ```dart 13 | final List info = await HomeWidget.getInstalledWidgets(); 14 | ``` 15 | 16 | This will give you respective Information about the Widgets a User has currently installed with respective platform specific Widget Information filled. 17 | 18 | Note that each combination of `iOSFamily`(Size) and `iOSKind`(Widget Type) will only appear once in the list due to limitations of iOS. 19 | -------------------------------------------------------------------------------- /docs/features/background-updates.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Background Update 3 | description: Update your Widget while the App is in the Background 4 | --- 5 | 6 | # Background Update 7 | As the methods of HomeWidget are static it is possible to use HomeWidget in the background to update the Widget even when the App is in the background. 8 | 9 | The example App is using the [flutter_workmanager](https://pub.dev/packages/workmanager) plugin to achieve this. 10 | Please follow the Setup Instructions for flutter_workmanager (or your preferred background code execution plugin). Most notably make sure that Plugins get registered in iOS in order to be able to communicate with the HomeWidget Plugin. 11 | In case of flutter_workmanager this achieved by adding: 12 | ```swift 13 | WorkmanagerPlugin.setPluginRegistrantCallback { registry in 14 | GeneratedPluginRegistrant.register(with: registry) 15 | } 16 | ``` 17 | to [AppDelegate.swift](example/ios/Runner/AppDelegate.swift) -------------------------------------------------------------------------------- /docs/features/detect-clicks.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Detect Clicks 3 | description: Detect Clicks on Widgets from inside your App 4 | --- 5 | 6 | # Detect Clicks 7 | 8 | home_widget offers functionality to check if your App was launched by clicking on an element in the HomeScreen Widget. 9 | 10 | 11 | Note that this is not for Interactive Widgets. For this please check the relevant documentation [here](/features/interactive-widgets). 12 | 13 | 14 | To detect this in your App there are two methods: 15 | 16 | For when your App is started from the very background check: 17 | ```dart 18 | HomeWidget.initiallyLaunchedFromHomeWidget(); 19 | ``` 20 | this will return a `Future` with the Uri that was used to start the App. 21 | 22 | When your App is already running all Widget clicks are broadcast to a Stream provided via: 23 | 24 | ```dart 25 | HomeWidget.widgetClicked; 26 | ```` 27 | which is a `Stream` that you can listen to to detect App Launches from the Widget.title 28 | 29 | 30 | ## Platform Setup 31 | 32 | In the following section you can learn how to add the necessary configurations to your native Widgets in order for clicks to bet detectable by home_widget.title 33 | 34 | ### iOS 35 | 36 | Add `.widgetUrl` to your WidgetComponent 37 | ```swift 38 | Text(entry.message) 39 | .font(.body) 40 | .widgetURL(URL(string: "homeWidgetExample://message?message=\(entry.message)&homeWidget")) 41 | ``` 42 | In order to only detect Widget Links you need to add the queryParameter`homeWidget` to the URL 43 | 44 | ### Android 45 | 46 | 47 | Add an `IntentFilter` to the `Activity` Section in your `AndroidManifest` 48 | ``` 49 | 50 | 51 | 52 | ``` 53 | 54 | Add the following modifier to your Widget (import from HomeWidget) 55 | ```kotlin 56 | Text( 57 | message, 58 | style = TextStyle(fontSize = 18.sp), 59 | modifier = GlanceModifier.clickable( 60 | onClick = actionStartActivity( 61 | context, 62 | Uri.parse("homeWidgetExample://message?message=$message") 63 | ) 64 | ) 65 | ) 66 | ``` -------------------------------------------------------------------------------- /docs/features/ios-lock-screen.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: iOS Lock Screen Widgets 3 | description: Create Widgets for the iOS Lock Screen 4 | --- 5 | 6 | # iOS Lock Screen Widgets 7 | 8 | On iOS, Lock Screen Widgets are a great way to show information to your users without them having to unlock their device. 9 | 10 | 11 |
12 | 13 |
14 | 15 | 16 | ## Supported Families 17 | Technically, this works by adding another `family` to your widget's configuration. 18 | 19 | ```swift 20 | var body: some WidgetConfiguration { 21 | ... 22 | .description("Lock Screen Widgets") 23 | .supportedFamilies([ 24 | .systemSmall, 25 | .systemMedium, 26 | // Accessory Widgets are available on the Lock Screen only 27 | .accessoryCircular 28 | ]) 29 | ``` 30 | 31 | ## Adjust Layout 32 | 33 | To adjust the layout of the widget, you can check which family/size is currently requested in the widget's layout code. 34 | 35 | ```swift 36 | struct LockScreenWidgetView: View { 37 | var entry: Provider.Entry 38 | 39 | // Detect the current Family 40 | @Environment(\.widgetFamily) var family 41 | 42 | var body: some View { 43 | if family == .accessoryCircular { 44 | // Return Widget for Circular Lock Screen Widget 45 | } else { 46 | // Build Widget for other families 47 | } 48 | } 49 | } 50 | ``` 51 | 52 | For more information on accessory widgets, check out the [official Apple Documentation](https://developer.apple.com/design/human-interface-guidelines/widgets#Interface-design). 53 | 54 | A full guide on how to add a Lock Screen Widget using home_widget can also be found in this [article](https://medium.com/@ABausG/ios-lockscreen-widgets-with-flutter-and-home-widget-0dfecc18cfa0). -------------------------------------------------------------------------------- /docs/features/pin-widget.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Pin Widgets (Android) 3 | description: Use Pin Widget to add widgets directly to the HomeScreen on Android. 4 | --- 5 | 6 | # Pin Widgets 7 | 8 | On Android, you can initiate an action to pin a widget to the HomeScreen, allowing your users to directly add your widget to their HomeScreen. 9 | 10 | 11 |
12 | 13 |
14 | 15 | 16 | ```dart 17 | HomeWidget.requestPinWidget( 18 | name: 'HomeWidgetExampleProvider', 19 | androidName: 'HomeWidgetExampleProvider', 20 | qualifiedAndroidName: 'com.example.app.HomeWidgetExampleProvider', 21 | ); 22 | ``` 23 | 24 | This method is only supported on [Android API 26+](https://developer.android.com/develop/ui/views/appwidgets/configuration#pin). 25 | If you want to check whether it is supported on the current device, use: 26 | 27 | ```dart 28 | HomeWidget.isRequestPinWidgetSupported(); 29 | ``` -------------------------------------------------------------------------------- /docs/features/render-flutter-widgets.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Render Flutter Widgets 3 | description: User renderFlutterWidget to render Flutter Widgets to the Native Widgets 4 | --- 5 | 6 | # Render Flutter Widgets 7 | 8 | In some cases, you may not want to rewrite UI code in the native frameworks for your widgets. This works by generating a png file of the Flutter widget and save it to a shared container between your Flutter app and the home screen widget. 9 | 10 |
Screenshot 2023-06-07 at 12 57 28 PM
11 | 12 | 13 | Due to a limitation in `dart:ui` this method does not work when the App is fully in the background. In those cases you should try to build the UI natively. 14 | 15 | 16 | For example, say you have a chart in your Flutter app configured with `CustomPaint`: 17 | 18 | ```dart 19 | class LineChart extends StatelessWidget { 20 | const LineChart({ 21 | super.key, 22 | }); 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | return CustomPaint( 27 | painter: LineChartPainter(), 28 | child: const SizedBox( 29 | height: 200, 30 | width: 200, 31 | ), 32 | ); 33 | } 34 | } 35 | ``` 36 | 37 |
Screenshot 2023-06-07 at 12 33 44 PM
38 | 39 | ## Flutter 40 | 41 | To render a Flutter widget to an image, use the `renderFlutterWidget` method: 42 | 43 | ```dart 44 | var path = await HomeWidget.renderFlutterWidget( 45 | const LineChart(), 46 | key: 'lineChart', 47 | logicalSize: const Size(400, 400), 48 | ); 49 | ``` 50 | - `LineChart()` is the widget that will be rendered as an image. 51 | - `key` is the key in the key/value storage on the device that stores the path of the file for easy retrieval on the native side 52 | 53 | ## iOS 54 | 55 | To retrieve the image and display it in a widget, you can use the following SwiftUI code: 56 | 57 | 1. In your `TimelineEntry` struct add a property to retrieve the path: 58 | ```swift 59 | struct MyEntry: TimelineEntry { 60 | … 61 | let lineChartPath: String 62 | } 63 | ``` 64 | 65 | 2. Get the path from the `UserDefaults` in `getSnapshot`: 66 | ```swift 67 | func getSnapshot( 68 | ... 69 | let lineChartPath = userDefaults?.string(forKey: "lineChart") ?? "No screenshot available" 70 | ``` 71 | 3. Create a `View` to display the chart and resize the image based on the `displaySize` of the widget: 72 | ```swift 73 | struct WidgetEntryView : View { 74 | … 75 | var ChartImage: some View { 76 | if let uiImage = UIImage(contentsOfFile: entry.lineChartPath) { 77 | let image = Image(uiImage: uiImage) 78 | .resizable() 79 | .frame(width: entry.displaySize.height*0.5, height: entry.displaySize.height*0.5, alignment: .center) 80 | return AnyView(image) 81 | } 82 | print("The image file could not be loaded") 83 | return AnyView(EmptyView()) 84 | } 85 | … 86 | } 87 | ``` 88 | 89 | 4. Display the chart in the body of the widget's `View`: 90 | ```swift 91 | VStack { 92 | Text(entry.title) 93 | Text(entry.description) 94 | ChartImage 95 | } 96 | ``` 97 | 98 | ## Android 99 | 100 | On Android use the following code in your Glance Widget to display the Screenshot of the Flutter Widget 101 | 102 | ```kotlin 103 | // Access data 104 | val data = currentState.preferences 105 | 106 | // Get Path 107 | val imagePath = data.getString("lineChart", null) 108 | 109 | // Add Image to Compose Tree 110 | imagePath?.let { 111 | val bitmap = BitmapFactory.decodeFile(it) 112 | Image(androidx.glance.ImageProvider(bitmap), null) 113 | } 114 | ``` -------------------------------------------------------------------------------- /docs/setup/android.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Android Setup 3 | description: Setup home_widget on Android for your Flutter App using Jetpack Glance 4 | previousTitle: iOS Setup 5 | previous: /setup/ios 6 | --- 7 | 8 | 9 | # Android Setup 10 | 11 | Learn how to setup home_widget on Android for your Flutter App using Jetpack Glance. 12 | 13 | 14 | If you are looking for support for Android XML Widgets, please refer to the [Android XML](/android-xml/overview) section. 15 | 16 | 17 | ## Dependencies 18 | 19 | ### Jetpack Glance 20 | 21 | Add Jetpack Glance as a dependency to you app's Gradle File 22 | ```groovy 23 | implementation 'androidx.glance:glance-appwidget:LATEST-VERSION' 24 | ``` 25 | 26 | ### Compose Support 27 | Enable Compose Support in your apps `build.gradle` 28 | ```groovy 29 | android { 30 | ... 31 | buildFeatures { 32 | compose true 33 | } 34 | } 35 | ``` 36 | 37 | ## Necessary Files 38 | 39 | For the correct setup of HomeScreenWidgets you need to create a series of files. 40 | 41 | ### Widget Configuration 42 | In `android/app/src/main/res/xml` you need to create a configuration file. 43 | In here you can configure properties used for things like size constraints and preview layouts. 44 | ```xml 45 | 51 | 52 | ``` 53 | For more Information on the possible contents of this File check the official Android Documentation [here](https://developer.android.com/develop/ui/views/appwidgets#AppWidgetProviderInfo) 54 | 55 | ### AppWidget 56 | 57 | The `GlanceAppWidget` is the file in which you define your Widget's layout. Should look something like this 58 | 59 | ```kotlin 60 | // Other imports... 61 | import HomeWidgetGlanceState 62 | import HomeWidgetGlanceStateDefinition 63 | 64 | class AppWidget : GlanceAppWidget() { 65 | 66 | override val stateDefinition: GlanceStateDefinition<*>? 67 | get() = HomeWidgetGlanceStateDefinition() 68 | 69 | override suspend fun provideGlance(context: Context, id: GlanceId) { 70 | provideContent { 71 | GlanceContent(context, currentState()) 72 | } 73 | } 74 | 75 | @Composable 76 | private fun GlanceContent(context: Context, currentState: HomeWidgetGlanceState) { 77 | val prefs = currentState.preferences 78 | val counter = prefs.getInt("counter", 0) 79 | Box(modifier = GlanceModifier.background(Color.White).padding(16.dp)) { 80 | Column() { 81 | Text( 82 | counter.toString() 83 | ) 84 | } 85 | } 86 | } 87 | } 88 | ``` 89 | 90 | Note the override for the `stateDefinition` this is what enables home_widget to update the Widget. 91 | ```kotlin 92 | override val stateDefinition: GlanceStateDefinition<*>? 93 | get() = HomeWidgetGlanceStateDefinition() 94 | ``` 95 | 96 | ### WidgetReceiver 97 | 98 | To get automatic Updates you should extend from [HomeWidgetGlanceWidgetReceiver](android/src/main/kotlin/es/antonborri/home_widget/HomeWidgetGlanceWidgetReceiver.kt) 99 | 100 | Your Receiver should then look like this, using the previously define `AppWidget` as the generic type. 101 | 102 | ```kotlin 103 | // Remember to set a package in order for home_widget to find the Receiver 104 | package es.antonborri.home_widget_example.glance 105 | 106 | import HomeWidgetGlanceWidgetReceiver 107 | 108 | class HomeWidgetReceiver : HomeWidgetGlanceWidgetReceiver() { 109 | override val glanceAppWidget = AppWidget() 110 | } 111 | ``` 112 | 113 | 114 | ### Register Widget in AndroidManifest.xml 115 | 116 | Tie everything together by registering the Widget in your `AndroidManifest.xml` 117 | ```xml 118 | 120 | 121 | 122 | 123 | 126 | 127 | ``` 128 | -------------------------------------------------------------------------------- /docs/usage/sync-data.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Read and write Data 3 | description: Read and write Data 4 | --- 5 | 6 | # Read and Write Data 7 | 8 | By default home_widget uses UserDefaults on iOS and SharedPreferences on Android to store data. 9 | In this section it is explained how to save and read data from the Flutter App as well as how to access data from your HomeScreen Widgets 10 | 11 | ## Save Data 12 | 13 | In order to save Data call 14 | ```dart 15 | HomeWidget.saveWidgetData('id', data); 16 | ``` 17 | 18 | ## Read Data 19 | 20 | To retrieve the current Data saved in the Widget call 21 | ```dart 22 | HomeWidget.getWidgetData('id', defaultValue: data); 23 | ``` 24 | 25 | Check out the platform documentation of 26 | [iOS](/setup/ios#timelineentry--timelineprovider) and [Android](/setup/android#appwidget) 27 | to see how to access this data in your widget. 28 | -------------------------------------------------------------------------------- /docs/usage/update-widget.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Update Widget 3 | description: How to Update a native Widget from Flutter 4 | nextTitle: iOS Setup 5 | next: /setup/android 6 | --- 7 | 8 | # Update Widget 9 | 10 | In order to initiate a reload of the HomeScreenWidget you need to call 11 | ```dart 12 | HomeWidget.updateWidget( 13 | name: 'HomeWidgetExampleProvider', 14 | androidName: 'HomeWidgetExampleProvider', 15 | iOSName: 'HomeWidgetExample', 16 | qualifiedAndroidName: 'com.example.app.HomeWidgetExampleProvider', 17 | ); 18 | ``` 19 | 20 | 21 | Not all the arguments are required. Depending on your setup you might need to call different arguments of the function. 22 | For iOS either `name` or `iOSName` must match the `kind` that is defined for the Widget 23 | For Android either `name` or `androidName` must the class Name of your Widget Receiver. Alternatively you can point to the receivers full class using `qualifiedAndroidName` 24 | 25 | 26 | 27 | #### Android Glance 28 | 29 | To ensure your Android Glance Widget is getting updated you need to add the following snippet to your Android Glance Widget. Please also make sure you read the full documentation on [Android Glance](/setup/android) to ensure you have the correct setup. 30 | 31 | ```kotlin 32 | override val stateDefinition: GlanceStateDefinition<*>? 33 | get() = HomeWidgetGlanceStateDefinition() 34 | ``` 35 | 36 | 37 | -------------------------------------------------------------------------------- /examples/configurable_widget/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | **/ios/Flutter/.last_build_id 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Symbolication related 35 | app.*.symbols 36 | 37 | # Obfuscation related 38 | app.*.map.json 39 | 40 | # Android Studio will place build artifacts here 41 | /android/app/debug 42 | /android/app/profile 43 | /android/app/release 44 | -------------------------------------------------------------------------------- /examples/configurable_widget/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: "b0850beeb25f6d5b10426284f506557f66181b36" 8 | channel: "stable" 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: b0850beeb25f6d5b10426284f506557f66181b36 17 | base_revision: b0850beeb25f6d5b10426284f506557f66181b36 18 | - platform: ios 19 | create_revision: b0850beeb25f6d5b10426284f506557f66181b36 20 | base_revision: b0850beeb25f6d5b10426284f506557f66181b36 21 | 22 | # User provided section 23 | 24 | # List of Local paths (relative to this file) that should be 25 | # ignored by the migrate tool. 26 | # 27 | # Files that are not part of the templates will be ignored by default. 28 | unmanaged_files: 29 | - 'lib/main.dart' 30 | - 'ios/Runner.xcodeproj/project.pbxproj' 31 | -------------------------------------------------------------------------------- /examples/configurable_widget/README.md: -------------------------------------------------------------------------------- 1 | # configurable_widget 2 | 3 | A new Flutter project. 4 | -------------------------------------------------------------------------------- /examples/configurable_widget/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | -------------------------------------------------------------------------------- /examples/configurable_widget/ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /examples/configurable_widget/ios/ConfigurableWidget/AppIntent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppIntent.swift 3 | // ConfigurableWidget 4 | // 5 | // Created by Anton Borries on 23.10.24. 6 | // 7 | 8 | import AppIntents 9 | import WidgetKit 10 | 11 | @available(iOS 17.0, *) 12 | struct ConfigurationAppIntent: WidgetConfigurationIntent { 13 | static var title: LocalizedStringResource = "Configuration" 14 | static var description = IntentDescription("This is an example widget.") 15 | 16 | // An example simple parameter 17 | @Parameter(title: "Name", default: "World") 18 | var name: String 19 | 20 | @Parameter(title: "Punctuation") 21 | var punctuation: PunctuationEntity 22 | } 23 | 24 | // Make Entity Codable so home_widget 25 | // That way home_widget can best extract the values from a configuration 26 | @available(iOS 17.0, *) 27 | struct PunctuationEntity: AppEntity, Codable { 28 | let id: String 29 | 30 | static var typeDisplayRepresentation: TypeDisplayRepresentation = "Punctuation" 31 | static var defaultQuery = PunctuationQuery() 32 | 33 | var displayRepresentation: DisplayRepresentation { 34 | DisplayRepresentation(title: "\(id)") 35 | } 36 | } 37 | 38 | @available(iOS 17.0, *) 39 | struct PunctuationQuery: EntityQuery { 40 | 41 | func punctuations() -> [PunctuationEntity] { 42 | let userDefaults = UserDefaults(suiteName: "group.es.antonborri.configurableWidget") 43 | 44 | do { 45 | let jsonPunctuations = (userDefaults?.string(forKey: "punctuations") ?? "[\"!\"]").data( 46 | using: .utf8)! 47 | let stringArray = try JSONDecoder().decode([String].self, from: jsonPunctuations) 48 | return stringArray.map { punctuation in 49 | PunctuationEntity(id: punctuation) 50 | 51 | } 52 | } catch { 53 | return [PunctuationEntity(id: "!")] 54 | } 55 | 56 | } 57 | 58 | func entities(for identifiers: [PunctuationEntity.ID]) async throws -> [PunctuationEntity] { 59 | let results = punctuations().filter { identifiers.contains($0.id) } 60 | return results 61 | } 62 | 63 | func suggestedEntities() async throws -> [PunctuationEntity] { 64 | return punctuations() 65 | } 66 | 67 | func defaultResult() async -> PunctuationEntity? { 68 | try? await suggestedEntities().first 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /examples/configurable_widget/ios/ConfigurableWidget/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/configurable_widget/ios/ConfigurableWidget/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | } 8 | ], 9 | "info" : { 10 | "author" : "xcode", 11 | "version" : 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/configurable_widget/ios/ConfigurableWidget/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /examples/configurable_widget/ios/ConfigurableWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/configurable_widget/ios/ConfigurableWidget/ConfigurableWidget.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConfigurableWidget.swift 3 | // ConfigurableWidget 4 | // 5 | 6 | import SwiftUI 7 | import WidgetKit 8 | 9 | @available(iOS 17.0, *) 10 | struct Provider: AppIntentTimelineProvider { 11 | func placeholder(in context: Context) -> SimpleEntry { 12 | SimpleEntry(date: Date(), name: "World", punctuation: "!") 13 | } 14 | 15 | func snapshot(for configuration: ConfigurationAppIntent, in context: Context) async -> SimpleEntry 16 | { 17 | SimpleEntry(date: Date(), name: configuration.name, punctuation: configuration.punctuation.id) 18 | } 19 | 20 | func timeline(for configuration: ConfigurationAppIntent, in context: Context) async -> Timeline< 21 | SimpleEntry 22 | > { 23 | var entries: [SimpleEntry] = [] 24 | 25 | // Generate a timeline consisting of five entries an hour apart, starting from the current date. 26 | let currentDate = Date() 27 | for hourOffset in 0..<5 { 28 | let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)! 29 | let entry = SimpleEntry( 30 | date: entryDate, name: configuration.name, punctuation: configuration.punctuation.id) 31 | entries.append(entry) 32 | } 33 | 34 | return Timeline(entries: entries, policy: .atEnd) 35 | } 36 | } 37 | 38 | struct IntentProvider: IntentTimelineProvider { 39 | typealias Entry = SimpleEntry 40 | 41 | typealias Intent = GreetingIntentIntent 42 | 43 | func placeholder(in context: Context) -> SimpleEntry { 44 | SimpleEntry(date: Date(), name: "World") 45 | } 46 | 47 | func getSnapshot( 48 | for configuration: GreetingIntentIntent, in context: Context, 49 | completion: @escaping (SimpleEntry) -> Void 50 | ) { 51 | completion(SimpleEntry(date: Date(), name: configuration.Name)) 52 | } 53 | 54 | func getTimeline( 55 | for configuration: GreetingIntentIntent, in context: Context, 56 | completion: @escaping (Timeline) -> Void 57 | ) { 58 | var entries: [SimpleEntry] = [] 59 | 60 | // Generate a timeline consisting of five entries an hour apart, starting from the current date. 61 | let currentDate = Date() 62 | for hourOffset in 0..<5 { 63 | let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)! 64 | let entry = SimpleEntry( 65 | date: entryDate, name: configuration.Name, 66 | punctuation: configuration.Punctuation?.identifier) 67 | entries.append(entry) 68 | } 69 | 70 | completion(Timeline(entries: entries, policy: .atEnd)) 71 | } 72 | } 73 | 74 | struct SimpleEntry: TimelineEntry { 75 | let date: Date 76 | let name: String? 77 | var punctuation: String? 78 | } 79 | 80 | struct ConfigurableWidgetEntryView: View { 81 | var entry: SimpleEntry 82 | 83 | var body: some View { 84 | VStack { 85 | Text("Hello") 86 | if let name = entry.name { 87 | Text(name) 88 | } 89 | if let punctuation = entry.punctuation { 90 | Text(punctuation) 91 | } 92 | } 93 | } 94 | } 95 | 96 | struct ConfigurableWidget: Widget { 97 | let kind: String = "ConfigurableWidget" 98 | 99 | var body: some WidgetConfiguration { 100 | if #available(iOS 17.0, *) { 101 | return AppIntentConfiguration( 102 | kind: kind, intent: ConfigurationAppIntent.self, provider: Provider() 103 | ) { 104 | entry in 105 | ConfigurableWidgetEntryView(entry: entry) 106 | .containerBackground(.fill.tertiary, for: .widget) 107 | } 108 | } else { 109 | return IntentConfiguration( 110 | kind: kind, 111 | intent: GreetingIntentIntent.self, 112 | provider: IntentProvider() 113 | ) { entry in 114 | ConfigurableWidgetEntryView(entry: entry) 115 | } 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /examples/configurable_widget/ios/ConfigurableWidget/ConfigurableWidgetBundle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConfigurableWidgetBundle.swift 3 | // ConfigurableWidget 4 | // 5 | // Created by Anton Borries on 23.10.24. 6 | // 7 | 8 | import SwiftUI 9 | import WidgetKit 10 | 11 | @main 12 | struct ConfigurableWidgetBundle: WidgetBundle { 13 | var body: some Widget { 14 | ConfigurableWidget() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/configurable_widget/ios/ConfigurableWidget/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSExtension 6 | 7 | NSExtensionPointIdentifier 8 | com.apple.widgetkit-extension 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/configurable_widget/ios/ConfigurableWidgetExtension.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.application-groups 6 | 7 | group.es.antonborri.configurableWidget 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /examples/configurable_widget/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 12.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /examples/configurable_widget/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /examples/configurable_widget/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /examples/configurable_widget/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '12.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | target 'RunnerTests' do 36 | inherit! :search_paths 37 | end 38 | end 39 | 40 | post_install do |installer| 41 | installer.pods_project.targets.each do |target| 42 | flutter_additional_ios_build_settings(target) 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /examples/configurable_widget/ios/PunctuationIntent/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSExtension 6 | 7 | NSExtensionAttributes 8 | 9 | IntentsRestrictedWhileLocked 10 | 11 | IntentsSupported 12 | 13 | GreetingIntentIntent 14 | 15 | 16 | NSExtensionPointIdentifier 17 | com.apple.intents-service 18 | NSExtensionPrincipalClass 19 | $(PRODUCT_MODULE_NAME).IntentHandler 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /examples/configurable_widget/ios/PunctuationIntent/IntentHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IntentHandler.swift 3 | // PunctuationIntent 4 | // 5 | // Created by Anton Borries on 16.02.25. 6 | // 7 | 8 | import Intents 9 | 10 | class IntentHandler: INExtension, GreetingIntentIntentHandling { 11 | 12 | func providePunctuationOptionsCollection(for intent: GreetingIntentIntent) async throws 13 | -> INObjectCollection 14 | { 15 | let userDefaults = UserDefaults(suiteName: "group.es.antonborri.configurableWidget") 16 | 17 | do { 18 | let jsonPunctuations = (userDefaults?.string(forKey: "punctuations") ?? "[\"!\"]").data( 19 | using: .utf8)! 20 | let stringArray = try JSONDecoder().decode([String].self, from: jsonPunctuations) 21 | let items = stringArray.map { punctuation in 22 | Punctuation(identifier: punctuation, display: punctuation) 23 | } 24 | return INObjectCollection(items: items) 25 | 26 | } catch { 27 | return INObjectCollection(items: [Punctuation(identifier: "!", display: "!")]) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /examples/configurable_widget/ios/PunctuationIntent/PunctuationIntent.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.application-groups 6 | 7 | group.es.antonborri.configurableWidget 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /examples/configurable_widget/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/configurable_widget/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/configurable_widget/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 43 | 49 | 50 | 51 | 52 | 53 | 63 | 65 | 71 | 72 | 73 | 74 | 80 | 82 | 88 | 89 | 90 | 91 | 93 | 94 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /examples/configurable_widget/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/configurable_widget/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/configurable_widget/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | import home_widget 4 | 5 | @main 6 | @objc class AppDelegate: FlutterAppDelegate { 7 | override func application( 8 | _ application: UIApplication, 9 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 10 | ) -> Bool { 11 | GeneratedPluginRegistrant.register(with: self) 12 | if #available(iOS 17.0, *) { 13 | HomeWidgetPlugin.setConfigurationLookup(to: [ 14 | "ConfigurableWidget": ConfigurationAppIntent.self 15 | ]) 16 | } 17 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/configurable_widget/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /examples/configurable_widget/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/examples/configurable_widget/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /examples/configurable_widget/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/examples/configurable_widget/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /examples/configurable_widget/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/examples/configurable_widget/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /examples/configurable_widget/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/examples/configurable_widget/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /examples/configurable_widget/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/examples/configurable_widget/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /examples/configurable_widget/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/examples/configurable_widget/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /examples/configurable_widget/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/examples/configurable_widget/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /examples/configurable_widget/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/examples/configurable_widget/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /examples/configurable_widget/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/examples/configurable_widget/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /examples/configurable_widget/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/examples/configurable_widget/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /examples/configurable_widget/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/examples/configurable_widget/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /examples/configurable_widget/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/examples/configurable_widget/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /examples/configurable_widget/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/examples/configurable_widget/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /examples/configurable_widget/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/examples/configurable_widget/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /examples/configurable_widget/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/examples/configurable_widget/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /examples/configurable_widget/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/configurable_widget/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/examples/configurable_widget/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /examples/configurable_widget/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/examples/configurable_widget/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /examples/configurable_widget/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/examples/configurable_widget/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /examples/configurable_widget/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | 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. -------------------------------------------------------------------------------- /examples/configurable_widget/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /examples/configurable_widget/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /examples/configurable_widget/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CADisableMinimumFrameDurationOnPhone 6 | 7 | CFBundleDevelopmentRegion 8 | $(DEVELOPMENT_LANGUAGE) 9 | CFBundleDisplayName 10 | Configurable Widget 11 | CFBundleExecutable 12 | $(EXECUTABLE_NAME) 13 | CFBundleIdentifier 14 | $(PRODUCT_BUNDLE_IDENTIFIER) 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | configurable_widget 19 | CFBundlePackageType 20 | APPL 21 | CFBundleShortVersionString 22 | $(FLUTTER_BUILD_NAME) 23 | CFBundleSignature 24 | ???? 25 | CFBundleVersion 26 | $(FLUTTER_BUILD_NUMBER) 27 | LSRequiresIPhoneOS 28 | 29 | NSUserActivityTypes 30 | 31 | GreetingIntentIntent 32 | 33 | UIApplicationSupportsIndirectInputEvents 34 | 35 | UILaunchStoryboardName 36 | LaunchScreen 37 | UIMainStoryboardFile 38 | Main 39 | UISupportedInterfaceOrientations 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationLandscapeLeft 43 | UIInterfaceOrientationLandscapeRight 44 | 45 | UISupportedInterfaceOrientations~ipad 46 | 47 | UIInterfaceOrientationPortrait 48 | UIInterfaceOrientationPortraitUpsideDown 49 | UIInterfaceOrientationLandscapeLeft 50 | UIInterfaceOrientationLandscapeRight 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /examples/configurable_widget/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /examples/configurable_widget/ios/Runner/Runner.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.application-groups 6 | 7 | group.es.antonborri.configurableWidget 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /examples/configurable_widget/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:home_widget/home_widget.dart'; 5 | 6 | void main() { 7 | WidgetsFlutterBinding.ensureInitialized(); 8 | _initPunctuations(); 9 | runApp(const MainApp()); 10 | } 11 | 12 | /// Send a List of possible punctuations to the widget. 13 | Future _initPunctuations() async { 14 | // Needed for communication between the app and the widget 15 | await HomeWidget.setAppGroupId('group.es.antonborri.configurableWidget'); 16 | final punctuations = [ 17 | '!', 18 | '!!!', 19 | '.', 20 | '?', 21 | // Wave Emoji 22 | '\u{1F44B}', 23 | ]; 24 | // Save the punctuations to the widget 25 | await HomeWidget.saveWidgetData( 26 | 'punctuations', 27 | jsonEncode(punctuations), 28 | ); 29 | } 30 | 31 | class MainApp extends StatefulWidget { 32 | const MainApp({super.key}); 33 | 34 | @override 35 | State createState() => _MainAppState(); 36 | } 37 | 38 | class _MainAppState extends State { 39 | List> _configurations = []; 40 | 41 | @override 42 | void initState() { 43 | super.initState(); 44 | _getInstalledWidgets(); 45 | } 46 | 47 | /// Get the list of installed widgets and their configurations. 48 | Future _getInstalledWidgets() async { 49 | final installedWidgets = await HomeWidget.getInstalledWidgets(); 50 | if (mounted) { 51 | setState(() { 52 | _configurations = installedWidgets 53 | .map((widget) => widget.configuration) 54 | .nonNulls 55 | .toList(); 56 | }); 57 | } 58 | } 59 | 60 | @override 61 | Widget build(BuildContext context) { 62 | return MaterialApp( 63 | home: Scaffold( 64 | appBar: AppBar( 65 | actions: [ 66 | IconButton( 67 | icon: const Icon(Icons.refresh), 68 | onPressed: _getInstalledWidgets, 69 | ) 70 | ], 71 | ), 72 | body: Column( 73 | mainAxisAlignment: MainAxisAlignment.center, 74 | crossAxisAlignment: CrossAxisAlignment.stretch, 75 | children: [ 76 | for (final configuration in _configurations) 77 | Text( 78 | configuration.toString(), 79 | textAlign: TextAlign.center, 80 | ) 81 | ], 82 | ), 83 | ), 84 | ); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /examples/configurable_widget/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: configurable_widget 2 | description: "A new Flutter project." 3 | publish_to: 'none' 4 | version: 0.1.0 5 | 6 | environment: 7 | sdk: '>=3.4.4 <4.0.0' 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | home_widget: 13 | path: ../../packages/home_widget 14 | 15 | dev_dependencies: 16 | flutter_test: 17 | sdk: flutter 18 | flutter_lints: ^3.0.0 19 | 20 | flutter: 21 | uses-material-design: true 22 | -------------------------------------------------------------------------------- /melos.yaml: -------------------------------------------------------------------------------- 1 | name: home_widget 2 | 3 | repository: https://github.com/abausg/home_widget 4 | 5 | packages: 6 | - packages/** 7 | - examples/** 8 | 9 | command: 10 | version: 11 | branch: main 12 | 13 | scripts: 14 | format:all: 15 | description: Format Dart, Kotlin, and Swift Files 16 | steps: 17 | - format:dart 18 | - format:kotlin 19 | - format:swift 20 | 21 | format:dart: 22 | description: Format Dart Files 23 | run: | 24 | dart format . --set-exit-if-changed 25 | 26 | format:kotlin: 27 | description: Format Kotlin Files 28 | run: ktfmt . --set-exit-if-changed 29 | 30 | format:swift: 31 | description: Format Swift Files 32 | run: | 33 | swift-format -r -i -p . 34 | swift-format lint -r -s . -------------------------------------------------------------------------------- /packages/home_widget/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | 4 | .packages 5 | .pub/ 6 | 7 | build/ 8 | .idea 9 | 10 | # Miscellaneous 11 | *.class 12 | *.lock 13 | *.log 14 | *.pyc 15 | *.swp 16 | .DS_Store 17 | .atom/ 18 | .buildlog/ 19 | .history 20 | .svn/ 21 | 22 | # IntelliJ related 23 | *.iml 24 | *.ipr 25 | *.iws 26 | .idea/ 27 | 28 | # Visual Studio Code related 29 | .classpath 30 | .project 31 | .settings/ 32 | .vscode/ 33 | 34 | # Flutter repo-specific 35 | /bin/cache/ 36 | /bin/mingit/ 37 | /dev/benchmarks/mega_gallery/ 38 | /dev/bots/.recipe_deps 39 | /dev/bots/android_tools/ 40 | /dev/devicelab/ABresults*.json 41 | /dev/docs/doc/ 42 | /dev/docs/flutter.docs.zip 43 | /dev/docs/lib/ 44 | /dev/docs/pubspec.yaml 45 | /dev/integration_tests/**/xcuserdata 46 | /dev/integration_tests/**/Pods 47 | /packages/flutter/coverage/ 48 | version 49 | analysis_benchmark.json 50 | 51 | # packages file containing multi-root paths 52 | .packages.generated 53 | 54 | # Flutter/Dart/Pub related 55 | **/doc/api/ 56 | .dart_tool/ 57 | .flutter-plugins 58 | .flutter-plugins-dependencies 59 | **/generated_plugin_registrant.dart 60 | .packages 61 | .pub-cache/ 62 | .pub/ 63 | build/ 64 | flutter_*.png 65 | linked_*.ds 66 | unlinked.ds 67 | unlinked_spec.ds 68 | 69 | # Android related 70 | **/android/**/gradle-wrapper.jar 71 | **/android/.gradle 72 | **/android/captures/ 73 | **/android/gradlew 74 | **/android/gradlew.bat 75 | **/android/local.properties 76 | **/android/**/GeneratedPluginRegistrant.java 77 | **/android/key.properties 78 | *.jks 79 | 80 | # iOS/XCode related 81 | **/ios/**/*.mode1v3 82 | **/ios/**/*.mode2v3 83 | **/ios/**/*.moved-aside 84 | **/ios/**/*.pbxuser 85 | **/ios/**/*.perspectivev3 86 | **/ios/**/*sync/ 87 | **/ios/**/.sconsign.dblite 88 | **/ios/**/.tags* 89 | **/ios/**/.vagrant/ 90 | **/ios/**/DerivedData/ 91 | **/ios/**/Icon? 92 | **/ios/**/Pods/ 93 | **/ios/**/.symlinks/ 94 | **/ios/**/profile 95 | **/ios/**/xcuserdata 96 | **/ios/.generated/ 97 | **/ios/Flutter/.last_build_id 98 | **/ios/Flutter/App.framework 99 | **/ios/Flutter/Flutter.framework 100 | **/ios/Flutter/Flutter.podspec 101 | **/ios/Flutter/Generated.xcconfig 102 | **/ios/Flutter/app.flx 103 | **/ios/Flutter/app.zip 104 | **/ios/Flutter/flutter_assets/ 105 | **/ios/Flutter/flutter_export_environment.sh 106 | **/ios/ServiceDefinitions.json 107 | **/ios/Runner/GeneratedPluginRegistrant.* 108 | 109 | # macOS 110 | **/macos/Flutter/GeneratedPluginRegistrant.swift 111 | **/macos/Flutter/Flutter-Debug.xcconfig 112 | **/macos/Flutter/Flutter-Release.xcconfig 113 | **/macos/Flutter/Flutter-Profile.xcconfig 114 | 115 | # Coverage 116 | coverage/ 117 | 118 | # Symbols 119 | app.*.symbols 120 | 121 | # Exceptions to above rules. 122 | !**/ios/**/default.mode1v3 123 | !**/ios/**/default.mode2v3 124 | !**/ios/**/default.pbxuser 125 | !**/ios/**/default.perspectivev3 126 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 127 | !/dev/ci/**/Gemfile.lock 128 | .gradle 129 | *.xcworkspacedata 130 | *.jar 131 | 132 | # don't check in golden failure output 133 | **/failures/*.png 134 | 135 | pubspec_overrides.yaml 136 | -------------------------------------------------------------------------------- /packages/home_widget/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 5a6dfa35caaf7bccb35488dc03677c150ebf2d97 8 | channel: dev 9 | 10 | project_type: plugin 11 | -------------------------------------------------------------------------------- /packages/home_widget/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020, Anton Borries 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of the copyright holder nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /packages/home_widget/README.md: -------------------------------------------------------------------------------- 1 | # Home Widget 2 | 3 | [![Pub](https://img.shields.io/pub/v/home_widget.svg)](https://pub.dartlang.org/packages/home_widget) 4 | [![likes](https://img.shields.io/pub/likes/home_widget)](https://pub.dev/packages/home_widget/score) 5 | [![downloads](https://img.shields.io/pub/dm/home_widget)](https://pub.dev/packages/home_widget/score) 6 | [![pub points](https://img.shields.io/pub/points/home_widget)](https://pub.dev/packages/home_widget/score) 7 | [![Build](https://github.com/abausg/home_widget/actions/workflows/main.yml/badge.svg?branch=main)](https://github.com/ABausG/home_widget/actions/workflows/main.yml?query=branch%3Amain) 8 | [![codecov](https://codecov.io/gh/ABausG/home_widget/branch/main/graph/badge.svg?token=ZXTZOL6KFO)](https://codecov.io/gh/ABausG/home_widget) 9 | [![GitHub-sponsors](https://img.shields.io/badge/Sponsor-30363D?style=flat&logo=GitHub-Sponsors&logoColor=#EA4AAA)](https://github.com/sponsors/abausg) 10 | 11 | HomeWidget is a plugin to make it easier to create HomeScreen Widgets on Android and iOS. 12 | HomeWidget does **not** allow writing Widgets with Flutter itself. It still requires writing the Widgets with native code. However, it provides a unified interface for sending data, retrieving data, and updating the Widgets. 13 | 14 | | iOS |  Android | 15 | |----------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------| 16 | | | | 17 | 18 | ## Features 19 | - Help setting up widgets on the native side 20 | - Send data from Flutter to the HomeScreen widget and update them 21 | - Render and display Flutter widgets on HomeScreen widgets 22 | - Interactive widgets that call Dart Code 23 | 24 | ## Documentation 25 | Check out the [documentation](https://docs.page/abausg/home_widget) to learn how to setup home_widget and HomeScreen Widgets on your desired Platforms. 26 | 27 | ## Usage 28 | Once you wrote your Widgets on the native side, it is super easy to send data to the Widget and update it. 29 | 30 | ### Save Data 31 | 32 | To save data, call: 33 | ```dart 34 | HomeWidget.saveWidgetData('id', data); 35 | ``` 36 | 37 | ### Update Widget 38 | 39 | To initiate a reload of the Home Screen Widget, you need to call: 40 | ```dart 41 | HomeWidget.updateWidget( 42 | name: 'HomeWidgetExampleProvider', 43 | ); 44 | ``` 45 | 46 | ## Contributing 47 | 48 | Contributions are welcome! 49 | Here is how you can help: 50 | - Report bugs and request features via [GitHub Issues](https://github.com/ABausG/home_widget/issues) 51 | - Engage in discussions and help users solve their problems/questions in the [Discussions](https://github.com/ABausG/home_widget/discussions) 52 | - Fix typos and grammar mistakes 53 | - Update the documentation 54 | - Implement new features by making a pull-request 55 | 56 | ## Show your Widgets 57 | 58 | Have you added HomeScreen widgets to your App? Feel free to share them in the [GitHub Discussions](https://github.com/ABausG/home_widget/discussions/categories/show-and-tell) 59 | 60 | ## Sponsors 61 | 62 | I develop this package in my free time. If you or your company benefits from home_widget, it would mean a lot to me if you considered supporting me on [GitHub Sponsors](https://github.com/sponsors/abausg) 63 |

64 | 65 | Github Sponsors of ABausG 66 | 67 |

68 | -------------------------------------------------------------------------------- /packages/home_widget/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | 3 | analyzer: 4 | language: 5 | strict-raw-types: true 6 | strict-inference: true 7 | strict-casts: true 8 | 9 | linter: 10 | rules: 11 | - public_member_api_docs 12 | - require_trailing_commas -------------------------------------------------------------------------------- /packages/home_widget/android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /packages/home_widget/android/build.gradle: -------------------------------------------------------------------------------- 1 | group 'es.antonborri.home_widget' 2 | version '1.0-SNAPSHOT' 3 | 4 | buildscript { 5 | ext.kotlin_version = '1.9.0' 6 | repositories { 7 | google() 8 | mavenCentral() 9 | } 10 | 11 | dependencies { 12 | classpath 'com.android.tools.build:gradle:8.1.2' 13 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 14 | } 15 | } 16 | 17 | rootProject.allprojects { 18 | repositories { 19 | google() 20 | mavenCentral() 21 | } 22 | } 23 | 24 | apply plugin: 'com.android.library' 25 | apply plugin: 'kotlin-android' 26 | 27 | android { 28 | // Conditional for compatibility with AGP <4.2. 29 | if (project.android.hasProperty("namespace")) { 30 | namespace 'es.antonborri.home_widget' 31 | } 32 | compileSdk 35 33 | 34 | sourceSets { 35 | main.java.srcDirs += 'src/main/kotlin' 36 | } 37 | defaultConfig { 38 | minSdkVersion 16 39 | } 40 | lintOptions { 41 | disable 'InvalidPackage' 42 | } 43 | compileOptions { 44 | targetCompatibility JavaVersion.VERSION_1_8 45 | sourceCompatibility JavaVersion.VERSION_1_8 46 | } 47 | kotlinOptions { 48 | jvmTarget = "1.8" 49 | } 50 | } 51 | 52 | dependencies { 53 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" 54 | implementation "androidx.glance:glance-appwidget:1.+" 55 | } 56 | -------------------------------------------------------------------------------- /packages/home_widget/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /packages/home_widget/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Oct 13 13:45:37 IST 2023 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /packages/home_widget/android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'home_widget' 2 | -------------------------------------------------------------------------------- /packages/home_widget/android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /packages/home_widget/android/src/main/kotlin/es/antonborri/home_widget/HomeWidgetBackgroundReceiver.kt: -------------------------------------------------------------------------------- 1 | package es.antonborri.home_widget 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | import io.flutter.FlutterInjector 7 | 8 | class HomeWidgetBackgroundReceiver : BroadcastReceiver() { 9 | override fun onReceive(context: Context, intent: Intent) { 10 | val flutterLoader = FlutterInjector.instance().flutterLoader() 11 | flutterLoader.startInitialization(context) 12 | flutterLoader.ensureInitializationComplete(context, null) 13 | HomeWidgetBackgroundService.enqueueWork(context, intent) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/home_widget/android/src/main/kotlin/es/antonborri/home_widget/HomeWidgetBackgroundService.kt: -------------------------------------------------------------------------------- 1 | package es.antonborri.home_widget 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.os.Handler 6 | import android.util.Log 7 | import androidx.core.app.JobIntentService 8 | import io.flutter.FlutterInjector 9 | import io.flutter.embedding.engine.FlutterEngine 10 | import io.flutter.embedding.engine.dart.DartExecutor 11 | import io.flutter.plugin.common.MethodCall 12 | import io.flutter.plugin.common.MethodChannel 13 | import io.flutter.view.FlutterCallbackInformation 14 | import java.util.* 15 | import java.util.concurrent.atomic.AtomicBoolean 16 | 17 | class HomeWidgetBackgroundService : MethodChannel.MethodCallHandler, JobIntentService() { 18 | 19 | private val queue = ArrayDeque>() 20 | private lateinit var channel: MethodChannel 21 | private lateinit var context: Context 22 | 23 | companion object { 24 | private const val TAG = "HomeWidgetService" 25 | private val JOB_ID = UUID.randomUUID().mostSignificantBits.toInt() 26 | private var engine: FlutterEngine? = null 27 | 28 | private val serviceStarted = AtomicBoolean(false) 29 | 30 | fun enqueueWork(context: Context, work: Intent) { 31 | enqueueWork(context, HomeWidgetBackgroundService::class.java, JOB_ID, work) 32 | } 33 | } 34 | 35 | override fun onCreate() { 36 | super.onCreate() 37 | synchronized(serviceStarted) { 38 | context = this 39 | if (engine == null) { 40 | val callbackHandle = HomeWidgetPlugin.getDispatcherHandle(context) 41 | 42 | if (callbackHandle == 0L) { 43 | Log.e(TAG, "No callbackHandle saved. Did you call HomeWidget.registerBackgroundCallback?") 44 | } 45 | 46 | engine = FlutterEngine(context) 47 | 48 | val callbackInfo = 49 | FlutterCallbackInformation.lookupCallbackInformation(callbackHandle) ?: return 50 | 51 | val callback = 52 | DartExecutor.DartCallback( 53 | context.assets, 54 | FlutterInjector.instance().flutterLoader().findAppBundlePath(), 55 | callbackInfo) 56 | engine?.dartExecutor?.executeDartCallback(callback) 57 | } 58 | } 59 | channel = 60 | MethodChannel(engine!!.getDartExecutor().getBinaryMessenger(), "home_widget/background") 61 | channel.setMethodCallHandler(this) 62 | } 63 | 64 | override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { 65 | if (call.method == "HomeWidget.backgroundInitialized") { 66 | synchronized(serviceStarted) { 67 | while (!queue.isEmpty()) { 68 | channel.invokeMethod("", queue.remove()) 69 | } 70 | serviceStarted.set(true) 71 | } 72 | } 73 | } 74 | 75 | override fun onHandleWork(intent: Intent) { 76 | val data = intent.data?.toString() ?: "" 77 | val args = listOf(HomeWidgetPlugin.getHandle(context), data) 78 | 79 | synchronized(serviceStarted) { 80 | if (!serviceStarted.get()) { 81 | queue.add(args) 82 | } else { 83 | Handler(context.mainLooper).post { channel.invokeMethod("", args) } 84 | } 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /packages/home_widget/android/src/main/kotlin/es/antonborri/home_widget/HomeWidgetGlanceState.kt: -------------------------------------------------------------------------------- 1 | import android.content.Context 2 | import android.content.SharedPreferences 3 | import android.os.Environment 4 | import androidx.datastore.core.DataStore 5 | import androidx.glance.state.GlanceStateDefinition 6 | import es.antonborri.home_widget.HomeWidgetPlugin 7 | import java.io.File 8 | import kotlinx.coroutines.flow.Flow 9 | import kotlinx.coroutines.flow.flow 10 | 11 | class HomeWidgetGlanceState(val preferences: SharedPreferences) 12 | 13 | class HomeWidgetGlanceStateDefinition : GlanceStateDefinition { 14 | override suspend fun getDataStore( 15 | context: Context, 16 | fileKey: String 17 | ): DataStore { 18 | val preferences = 19 | context.getSharedPreferences(HomeWidgetPlugin.PREFERENCES, Context.MODE_PRIVATE) 20 | return HomeWidgetGlanceDataStore(preferences) 21 | } 22 | 23 | override fun getLocation(context: Context, fileKey: String): File { 24 | return Environment.getDataDirectory() 25 | } 26 | } 27 | 28 | private class HomeWidgetGlanceDataStore(private val preferences: SharedPreferences) : 29 | DataStore { 30 | override val data: Flow 31 | get() = flow { emit(HomeWidgetGlanceState(preferences)) } 32 | 33 | override suspend fun updateData( 34 | transform: suspend (t: HomeWidgetGlanceState) -> HomeWidgetGlanceState 35 | ): HomeWidgetGlanceState { 36 | return transform(HomeWidgetGlanceState(preferences)) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/home_widget/android/src/main/kotlin/es/antonborri/home_widget/HomeWidgetGlanceWidgetReceiver.kt: -------------------------------------------------------------------------------- 1 | import android.appwidget.AppWidgetManager 2 | import android.content.Context 3 | import androidx.glance.appwidget.GlanceAppWidget 4 | import androidx.glance.appwidget.GlanceAppWidgetManager 5 | import androidx.glance.appwidget.GlanceAppWidgetReceiver 6 | import androidx.glance.appwidget.state.updateAppWidgetState 7 | import kotlinx.coroutines.runBlocking 8 | 9 | abstract class HomeWidgetGlanceWidgetReceiver : GlanceAppWidgetReceiver() { 10 | 11 | abstract override val glanceAppWidget: T 12 | 13 | override fun onUpdate( 14 | context: Context, 15 | appWidgetManager: AppWidgetManager, 16 | appWidgetIds: IntArray 17 | ) { 18 | super.onUpdate(context, appWidgetManager, appWidgetIds) 19 | runBlocking { 20 | appWidgetIds.forEach { 21 | val glanceId = GlanceAppWidgetManager(context).getGlanceIdBy(it) 22 | glanceAppWidget.apply { 23 | if (this.stateDefinition is HomeWidgetGlanceStateDefinition) { 24 | // Must Update State 25 | updateAppWidgetState( 26 | context = context, 27 | this.stateDefinition as HomeWidgetGlanceStateDefinition, 28 | glanceId) { currentState -> 29 | currentState 30 | } 31 | } 32 | // Update widget. 33 | update(context, glanceId) 34 | } 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/home_widget/android/src/main/kotlin/es/antonborri/home_widget/HomeWidgetIntent.kt: -------------------------------------------------------------------------------- 1 | package es.antonborri.home_widget 2 | 3 | import android.app.Activity 4 | import android.app.ActivityOptions 5 | import android.app.PendingIntent 6 | import android.content.Context 7 | import android.content.Intent 8 | import android.net.Uri 9 | import android.os.Build 10 | import androidx.glance.action.Action 11 | import androidx.glance.appwidget.action.actionStartActivity 12 | 13 | object HomeWidgetLaunchIntent { 14 | 15 | const val HOME_WIDGET_LAUNCH_ACTION = "es.antonborri.home_widget.action.LAUNCH" 16 | 17 | fun getActivity( 18 | context: Context, 19 | activityClass: Class, 20 | uri: Uri? = null 21 | ): PendingIntent where T : Activity { 22 | val intent = Intent(context, activityClass) 23 | intent.data = uri 24 | intent.action = HOME_WIDGET_LAUNCH_ACTION 25 | 26 | var flags = PendingIntent.FLAG_UPDATE_CURRENT 27 | if (Build.VERSION.SDK_INT >= 23) { 28 | flags = flags or PendingIntent.FLAG_IMMUTABLE 29 | } 30 | 31 | if (Build.VERSION.SDK_INT < 34) { 32 | return PendingIntent.getActivity(context, 0, intent, flags) 33 | } 34 | 35 | val options = ActivityOptions.makeBasic() 36 | if (Build.VERSION.SDK_INT >= 35) { 37 | options.setPendingIntentCreatorBackgroundActivityStartMode( 38 | ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED) 39 | } else if (Build.VERSION.SDK_INT >= 34) { 40 | options.pendingIntentBackgroundActivityStartMode = 41 | ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED 42 | } 43 | 44 | return PendingIntent.getActivity(context, 0, intent, flags, options.toBundle()) 45 | } 46 | } 47 | 48 | inline fun actionStartActivity(context: Context, uri: Uri? = null): Action { 49 | val intent = Intent(context, T::class.java) 50 | intent.data = uri 51 | intent.action = HomeWidgetLaunchIntent.HOME_WIDGET_LAUNCH_ACTION 52 | 53 | return actionStartActivity(intent) 54 | } 55 | 56 | object HomeWidgetBackgroundIntent { 57 | private const val HOME_WIDGET_BACKGROUND_ACTION = "es.antonborri.home_widget.action.BACKGROUND" 58 | 59 | fun getBroadcast(context: Context, uri: Uri? = null): PendingIntent { 60 | val intent = Intent(context, HomeWidgetBackgroundReceiver::class.java) 61 | intent.data = uri 62 | intent.action = HOME_WIDGET_BACKGROUND_ACTION 63 | 64 | var flags = PendingIntent.FLAG_UPDATE_CURRENT 65 | if (Build.VERSION.SDK_INT >= 23) { 66 | flags = flags or PendingIntent.FLAG_IMMUTABLE 67 | } 68 | 69 | return PendingIntent.getBroadcast(context, 0, intent, flags) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /packages/home_widget/android/src/main/kotlin/es/antonborri/home_widget/HomeWidgetProvider.kt: -------------------------------------------------------------------------------- 1 | package es.antonborri.home_widget 2 | 3 | import android.appwidget.AppWidgetManager 4 | import android.appwidget.AppWidgetProvider 5 | import android.content.Context 6 | import android.content.SharedPreferences 7 | 8 | abstract class HomeWidgetProvider : AppWidgetProvider() { 9 | 10 | override fun onUpdate( 11 | context: Context, 12 | appWidgetManager: AppWidgetManager, 13 | appWidgetIds: IntArray 14 | ) { 15 | super.onUpdate(context, appWidgetManager, appWidgetIds) 16 | onUpdate(context, appWidgetManager, appWidgetIds, HomeWidgetPlugin.getData(context)) 17 | } 18 | 19 | abstract fun onUpdate( 20 | context: Context, 21 | appWidgetManager: AppWidgetManager, 22 | appWidgetIds: IntArray, 23 | widgetData: SharedPreferences 24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /packages/home_widget/dart_test.yaml: -------------------------------------------------------------------------------- 1 | tags: 2 | golden: -------------------------------------------------------------------------------- /packages/home_widget/example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | # Exceptions to above rules. 44 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 45 | -------------------------------------------------------------------------------- /packages/home_widget/example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: "9e1c857886f07d342cf106f2cd588bcd5e031bb2" 8 | channel: "stable" 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: 9e1c857886f07d342cf106f2cd588bcd5e031bb2 17 | base_revision: 9e1c857886f07d342cf106f2cd588bcd5e031bb2 18 | - platform: android 19 | create_revision: 9e1c857886f07d342cf106f2cd588bcd5e031bb2 20 | base_revision: 9e1c857886f07d342cf106f2cd588bcd5e031bb2 21 | - platform: ios 22 | create_revision: 9e1c857886f07d342cf106f2cd588bcd5e031bb2 23 | base_revision: 9e1c857886f07d342cf106f2cd588bcd5e031bb2 24 | 25 | # User provided section 26 | 27 | # List of Local paths (relative to this file) that should be 28 | # ignored by the migrate tool. 29 | # 30 | # Files that are not part of the templates will be ignored by default. 31 | unmanaged_files: 32 | - 'lib/main.dart' 33 | - 'ios/Runner.xcodeproj/project.pbxproj' 34 | -------------------------------------------------------------------------------- /packages/home_widget/example/README.md: -------------------------------------------------------------------------------- 1 | # home_widget_example 2 | 3 | Demonstrates how to use the home_widget plugin. 4 | -------------------------------------------------------------------------------- /packages/home_widget/example/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | 3 | linter: 4 | rules: 5 | - require_trailing_commas -------------------------------------------------------------------------------- /packages/home_widget/example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | -------------------------------------------------------------------------------- /packages/home_widget/example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.android.application" 3 | id "kotlin-android" 4 | id "dev.flutter.flutter-gradle-plugin" 5 | } 6 | 7 | def localProperties = new Properties() 8 | def localPropertiesFile = rootProject.file('local.properties') 9 | if (localPropertiesFile.exists()) { 10 | localPropertiesFile.withReader('UTF-8') { reader -> 11 | localProperties.load(reader) 12 | } 13 | } 14 | 15 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 16 | if (flutterVersionCode == null) { 17 | flutterVersionCode = '1' 18 | } 19 | 20 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 21 | if (flutterVersionName == null) { 22 | flutterVersionName = '1.0' 23 | } 24 | 25 | android { 26 | compileSdkVersion 35 27 | ndkVersion = "26.3.11579264" 28 | 29 | sourceSets { 30 | main.java.srcDirs += 'src/main/kotlin' 31 | } 32 | 33 | compileOptions { 34 | sourceCompatibility JavaVersion.VERSION_21 35 | targetCompatibility JavaVersion.VERSION_21 36 | } 37 | 38 | kotlin { 39 | jvmToolchain(21) 40 | } 41 | 42 | lintOptions { 43 | disable 'InvalidPackage' 44 | } 45 | 46 | defaultConfig { 47 | applicationId "es.antonborri.home_widget_example" 48 | minSdkVersion 23 49 | targetSdkVersion 35 50 | multiDexEnabled true 51 | versionCode flutterVersionCode.toInteger() 52 | versionName flutterVersionName 53 | } 54 | 55 | buildFeatures { 56 | compose true 57 | } 58 | 59 | composeOptions { 60 | kotlinCompilerExtensionVersion = "1.5.4" 61 | } 62 | 63 | buildTypes { 64 | debug { 65 | minifyEnabled true 66 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 67 | } 68 | release { 69 | // TODO: Add your own signing config for the release build. 70 | // Signing with the debug keys for now, so `flutter run --release` works. 71 | signingConfig signingConfigs.debug 72 | } 73 | } 74 | namespace 'es.antonborri.home_widget_example' 75 | } 76 | 77 | flutter { 78 | source '../..' 79 | } 80 | 81 | dependencies { 82 | implementation 'androidx.glance:glance-appwidget:1.0.0' 83 | implementation "androidx.work:work-runtime-ktx:2.8.1" 84 | implementation "androidx.multidex:multidex:2.0.1" 85 | } 86 | -------------------------------------------------------------------------------- /packages/home_widget/example/android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | -dontwarn org.xmlpull.v1.** 2 | -dontwarn org.kxml2.io.** 3 | -dontwarn android.content.res.** 4 | -dontwarn org.slf4j.impl.StaticLoggerBinder 5 | 6 | -keep class org.xmlpull.** { *; } 7 | -keepclassmembers class org.xmlpull.** { *; } -------------------------------------------------------------------------------- /packages/home_widget/example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/home_widget/example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 11 | 19 | 23 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 39 | 40 | 41 | 42 | 44 | 45 | 46 | 49 | 50 | 51 | 52 | 55 | 56 | 57 | 58 | 60 | 61 | 62 | 63 | 64 | 66 | 67 | 69 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /packages/home_widget/example/android/app/src/main/kotlin/es/antonborri/home_widget_example/HomeWidgetExampleProvider.kt: -------------------------------------------------------------------------------- 1 | package es.antonborri.home_widget_example 2 | 3 | import android.appwidget.AppWidgetManager 4 | import android.content.Context 5 | import android.content.SharedPreferences 6 | import android.graphics.BitmapFactory 7 | import android.net.Uri 8 | import android.view.View 9 | import android.widget.RemoteViews 10 | import es.antonborri.home_widget.HomeWidgetBackgroundIntent 11 | import es.antonborri.home_widget.HomeWidgetLaunchIntent 12 | import es.antonborri.home_widget.HomeWidgetProvider 13 | 14 | class HomeWidgetExampleProvider : HomeWidgetProvider() { 15 | 16 | override fun onUpdate( 17 | context: Context, 18 | appWidgetManager: AppWidgetManager, 19 | appWidgetIds: IntArray, 20 | widgetData: SharedPreferences 21 | ) { 22 | appWidgetIds.forEach { widgetId -> 23 | val views = 24 | RemoteViews(context.packageName, R.layout.example_layout).apply { 25 | // Open App on Widget Click 26 | val pendingIntent = 27 | HomeWidgetLaunchIntent.getActivity(context, MainActivity::class.java) 28 | setOnClickPendingIntent(R.id.widget_container, pendingIntent) 29 | 30 | // Swap Title Text by calling Dart Code in the Background 31 | setTextViewText( 32 | R.id.widget_title, widgetData.getString("title", null) ?: "No Title Set") 33 | val backgroundIntent = 34 | HomeWidgetBackgroundIntent.getBroadcast( 35 | context, Uri.parse("homeWidgetExample://titleClicked")) 36 | setOnClickPendingIntent(R.id.widget_title, backgroundIntent) 37 | 38 | val message = widgetData.getString("message", null) 39 | setTextViewText(R.id.widget_message, message ?: "No Message Set") 40 | // Show Images saved with `renderFlutterWidget` 41 | val image = widgetData.getString("dashIcon", null) 42 | if (image != null) { 43 | setImageViewBitmap(R.id.widget_img, BitmapFactory.decodeFile(image)) 44 | setViewVisibility(R.id.widget_img, View.VISIBLE) 45 | } else { 46 | setViewVisibility(R.id.widget_img, View.GONE) 47 | } 48 | 49 | // Detect App opened via Click inside Flutter 50 | val pendingIntentWithData = 51 | HomeWidgetLaunchIntent.getActivity( 52 | context, 53 | MainActivity::class.java, 54 | Uri.parse("homeWidgetExample://message?message=$message")) 55 | setOnClickPendingIntent(R.id.widget_message, pendingIntentWithData) 56 | } 57 | 58 | appWidgetManager.updateAppWidget(widgetId, views) 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /packages/home_widget/example/android/app/src/main/kotlin/es/antonborri/home_widget_example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package es.antonborri.home_widget_example 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity : FlutterActivity() {} 6 | -------------------------------------------------------------------------------- /packages/home_widget/example/android/app/src/main/kotlin/es/antonborri/home_widget_example/glance/HomeWidgetGlanceAppWidget.kt: -------------------------------------------------------------------------------- 1 | package es.antonborri.home_widget_example.glance 2 | 3 | import HomeWidgetGlanceState 4 | import HomeWidgetGlanceStateDefinition 5 | import android.content.Context 6 | import android.graphics.BitmapFactory 7 | import android.net.Uri 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.ui.graphics.Color 10 | import androidx.compose.ui.unit.dp 11 | import androidx.compose.ui.unit.sp 12 | import androidx.glance.GlanceId 13 | import androidx.glance.GlanceModifier 14 | import androidx.glance.Image 15 | import androidx.glance.action.ActionParameters 16 | import androidx.glance.action.clickable 17 | import androidx.glance.appwidget.GlanceAppWidget 18 | import androidx.glance.appwidget.action.ActionCallback 19 | import androidx.glance.appwidget.action.actionRunCallback 20 | import androidx.glance.appwidget.provideContent 21 | import androidx.glance.background 22 | import androidx.glance.currentState 23 | import androidx.glance.layout.Alignment 24 | import androidx.glance.layout.Box 25 | import androidx.glance.layout.Column 26 | import androidx.glance.layout.fillMaxSize 27 | import androidx.glance.layout.padding 28 | import androidx.glance.text.FontWeight 29 | import androidx.glance.text.Text 30 | import androidx.glance.text.TextStyle 31 | import es.antonborri.home_widget.HomeWidgetBackgroundIntent 32 | import es.antonborri.home_widget.actionStartActivity 33 | import es.antonborri.home_widget_example.MainActivity 34 | 35 | class HomeWidgetGlanceAppWidget : GlanceAppWidget() { 36 | 37 | /** Needed for Updating */ 38 | override val stateDefinition = HomeWidgetGlanceStateDefinition() 39 | 40 | override suspend fun provideGlance(context: Context, id: GlanceId) { 41 | provideContent { GlanceContent(context, currentState()) } 42 | } 43 | 44 | @Composable 45 | private fun GlanceContent(context: Context, currentState: HomeWidgetGlanceState) { 46 | val data = currentState.preferences 47 | val imagePath = data.getString("dashIcon", null) 48 | 49 | val title = data.getString("title", "")!! 50 | val message = data.getString("message", "")!! 51 | 52 | Box( 53 | modifier = 54 | GlanceModifier.background(Color.White) 55 | .padding(16.dp) 56 | .clickable(onClick = actionStartActivity(context))) { 57 | Column( 58 | modifier = GlanceModifier.fillMaxSize(), 59 | verticalAlignment = Alignment.Vertical.Top, 60 | horizontalAlignment = Alignment.Horizontal.Start, 61 | ) { 62 | Text("Glance") 63 | Text( 64 | title, 65 | style = TextStyle(fontSize = 36.sp, fontWeight = FontWeight.Bold), 66 | modifier = 67 | GlanceModifier.clickable(onClick = actionRunCallback()), 68 | ) 69 | Text( 70 | message, 71 | style = TextStyle(fontSize = 18.sp), 72 | modifier = 73 | GlanceModifier.clickable( 74 | onClick = 75 | actionStartActivity( 76 | context, 77 | Uri.parse("homeWidgetExample://message?message=$message")))) 78 | imagePath?.let { 79 | val bitmap = BitmapFactory.decodeFile(it) 80 | Image(androidx.glance.ImageProvider(bitmap), null) 81 | } 82 | } 83 | } 84 | } 85 | } 86 | 87 | class InteractiveAction : ActionCallback { 88 | override suspend fun onAction( 89 | context: Context, 90 | glanceId: GlanceId, 91 | parameters: ActionParameters 92 | ) { 93 | val backgroundIntent = 94 | HomeWidgetBackgroundIntent.getBroadcast( 95 | context, Uri.parse("homeWidgetExample://titleClicked")) 96 | backgroundIntent.send() 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /packages/home_widget/example/android/app/src/main/kotlin/es/antonborri/home_widget_example/glance/HomeWidgetReceiver.kt: -------------------------------------------------------------------------------- 1 | package es.antonborri.home_widget_example.glance 2 | 3 | import HomeWidgetGlanceWidgetReceiver 4 | 5 | class HomeWidgetReceiver : HomeWidgetGlanceWidgetReceiver() { 6 | override val glanceAppWidget = HomeWidgetGlanceAppWidget() 7 | } 8 | -------------------------------------------------------------------------------- /packages/home_widget/example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/home_widget/example/android/app/src/main/res/drawable/widget_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/home_widget/example/android/app/src/main/res/layout/example_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 13 | 20 | 21 | 27 | 28 | 33 | -------------------------------------------------------------------------------- /packages/home_widget/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/packages/home_widget/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /packages/home_widget/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/packages/home_widget/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /packages/home_widget/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/packages/home_widget/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /packages/home_widget/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/packages/home_widget/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /packages/home_widget/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/packages/home_widget/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /packages/home_widget/example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /packages/home_widget/example/android/app/src/main/res/xml/home_widget_example.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | -------------------------------------------------------------------------------- /packages/home_widget/example/android/app/src/main/res/xml/home_widget_glance_example.xml: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /packages/home_widget/example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/home_widget/example/android/build.gradle: -------------------------------------------------------------------------------- 1 | allprojects { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | } 7 | 8 | rootProject.buildDir = '../build' 9 | subprojects { 10 | project.buildDir = "${rootProject.buildDir}/${project.name}" 11 | } 12 | subprojects { 13 | project.evaluationDependsOn(':app') 14 | } 15 | 16 | tasks.register("clean", Delete) { 17 | delete rootProject.buildDir 18 | } 19 | -------------------------------------------------------------------------------- /packages/home_widget/example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | android.defaults.buildfeatures.buildconfig=true 6 | android.nonTransitiveRClass=false 7 | android.nonFinalResIds=false 8 | -------------------------------------------------------------------------------- /packages/home_widget/example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /packages/home_widget/example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | def flutterSdkPath = { 3 | def properties = new Properties() 4 | file("local.properties").withInputStream { properties.load(it) } 5 | def flutterSdkPath = properties.getProperty("flutter.sdk") 6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 7 | return flutterSdkPath 8 | }() 9 | 10 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") 11 | 12 | repositories { 13 | google() 14 | mavenCentral() 15 | gradlePluginPortal() 16 | } 17 | } 18 | 19 | plugins { 20 | id "dev.flutter.flutter-plugin-loader" version "1.0.0" 21 | id "com.android.application" version '8.6.1' apply false 22 | id "org.jetbrains.kotlin.android" version "1.9.20" apply false 23 | } 24 | 25 | include ":app" -------------------------------------------------------------------------------- /packages/home_widget/example/integration_test/android_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:home_widget/home_widget.dart'; 3 | import 'package:integration_test/integration_test.dart'; 4 | 5 | void main() { 6 | IntegrationTestWidgetsFlutterBinding.ensureInitialized(); 7 | 8 | final testData = { 9 | 'stringKey': 'stringValue', 10 | 'intKey': 12, 11 | 'boolKey': true, 12 | 'floatingNumberKey': 12.1, 13 | 'largeDoubleKey': double.infinity, 14 | 'nullValueKey': null, 15 | 'longKey': DateTime(2024).millisecondsSinceEpoch, 16 | }; 17 | 18 | const defaultValue = MapEntry('defaultKey', 'defaultValue'); 19 | 20 | setUpAll(() { 21 | // Clear all Data 22 | for (final key in testData.keys) { 23 | HomeWidget.saveWidgetData(key, null); 24 | } 25 | }); 26 | 27 | group('Test Data operations', () { 28 | for (final testSet in testData.entries) { 29 | testWidgets('Test ${testSet.key}', (tester) async { 30 | // Save Data 31 | await HomeWidget.saveWidgetData(testSet.key, testSet.value); 32 | 33 | final retrievedData = await HomeWidget.getWidgetData(testSet.key); 34 | expect(retrievedData, testSet.value); 35 | }); 36 | } 37 | 38 | testWidgets('Delete Value successful', (tester) async { 39 | final initialData = await HomeWidget.getWidgetData(testData.keys.first); 40 | expect(initialData, testData.values.first); 41 | 42 | await HomeWidget.saveWidgetData(testData.values.first, null); 43 | 44 | final deletedData = await HomeWidget.getWidgetData(testData.keys.first); 45 | expect(deletedData, testData.values.first); 46 | }); 47 | 48 | testWidgets('Returns default Value', (tester) async { 49 | final returnValue = await HomeWidget.getWidgetData( 50 | defaultValue.key, 51 | defaultValue: defaultValue.value, 52 | ); 53 | 54 | expect(returnValue, defaultValue.value); 55 | }); 56 | }); 57 | 58 | testWidgets('Update Widget completes', (tester) async { 59 | final returnValue = await HomeWidget.updateWidget( 60 | name: 'HomeWidgetExampleProvider', 61 | ).timeout(const Duration(seconds: 5)); 62 | 63 | expect(returnValue, true); 64 | }); 65 | 66 | testWidgets('Register Background Callback', (tester) async { 67 | final returnValue = 68 | await HomeWidget.registerInteractivityCallback(backgroundCallback); 69 | expect(returnValue, true); 70 | }); 71 | 72 | testWidgets( 73 | 'Initially Launched completes and returns null if not launched from widget', 74 | (tester) async { 75 | final retrievedData = await HomeWidget.initiallyLaunchedFromHomeWidget(); 76 | expect(retrievedData, isNull); 77 | }); 78 | 79 | testWidgets('Get Installed Widgets returns empty list', (tester) async { 80 | final retrievedData = await HomeWidget.getInstalledWidgets(); 81 | expect(retrievedData, isEmpty); 82 | }); 83 | } 84 | 85 | Future backgroundCallback(Uri? uri) async {} 86 | -------------------------------------------------------------------------------- /packages/home_widget/example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /packages/home_widget/example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 12.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /packages/home_widget/example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /packages/home_widget/example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /packages/home_widget/example/ios/HomeWidgetExample/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/home_widget/example/ios/HomeWidgetExample/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | } 8 | ], 9 | "info" : { 10 | "author" : "xcode", 11 | "version" : 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/home_widget/example/ios/HomeWidgetExample/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/home_widget/example/ios/HomeWidgetExample/Assets.xcassets/WidgetBackground.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/home_widget/example/ios/HomeWidgetExample/HomeWidgetExample.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HomeWidgetExample.swift 3 | // HomeWidgetExample 4 | // 5 | // Created by Anton Borries on 04.10.20. 6 | // 7 | 8 | import SwiftUI 9 | import WidgetKit 10 | 11 | private let widgetGroupId = "group.es.antonborri.exampleHomeWidget" 12 | 13 | struct Provider: TimelineProvider { 14 | func placeholder(in context: Context) -> ExampleEntry { 15 | ExampleEntry(date: Date(), title: "Placeholder Title", message: "Placeholder Message") 16 | } 17 | 18 | func getSnapshot(in context: Context, completion: @escaping (ExampleEntry) -> Void) { 19 | let data = UserDefaults.init(suiteName: widgetGroupId) 20 | let entry = ExampleEntry( 21 | date: Date(), title: data?.string(forKey: "title") ?? "No Title Set", 22 | message: data?.string(forKey: "message") ?? "No Message Set") 23 | completion(entry) 24 | } 25 | 26 | func getTimeline(in context: Context, completion: @escaping (Timeline) -> Void) { 27 | getSnapshot(in: context) { (entry) in 28 | let timeline = Timeline(entries: [entry], policy: .atEnd) 29 | completion(timeline) 30 | } 31 | } 32 | } 33 | 34 | struct ExampleEntry: TimelineEntry { 35 | let date: Date 36 | let title: String 37 | let message: String 38 | } 39 | 40 | struct HomeWidgetExampleEntryView: View { 41 | var entry: Provider.Entry 42 | let data = UserDefaults.init(suiteName: widgetGroupId) 43 | let iconPath: String? 44 | 45 | init(entry: Provider.Entry) { 46 | self.entry = entry 47 | iconPath = data?.string(forKey: "dashIcon") 48 | 49 | } 50 | 51 | var body: some View { 52 | VStack.init( 53 | alignment: .center, 54 | spacing: nil, 55 | content: { 56 | if #available(iOSApplicationExtension 17, *) { 57 | Button( 58 | intent: BackgroundIntent( 59 | url: URL(string: "homeWidgetExample://titleClicked"), appGroup: widgetGroupId) 60 | ) { 61 | Text(entry.title).bold().font( 62 | .title) 63 | }.buttonStyle(.plain).frame(maxWidth: .infinity, alignment: .leading) 64 | } else { 65 | Text(entry.title).bold().font( 66 | .title 67 | ).frame( 68 | maxWidth: .infinity, alignment: .leading) 69 | } 70 | Text(entry.message) 71 | .font(.body) 72 | .widgetURL(URL(string: "homeWidgetExample://message?message=\(entry.message)&homeWidget")) 73 | .frame(maxWidth: .infinity, alignment: .leading) 74 | if iconPath != nil { 75 | Image(uiImage: UIImage(contentsOfFile: iconPath!)!).resizable() 76 | .scaledToFill() 77 | .frame(width: 64, height: 64) 78 | } 79 | } 80 | ) 81 | } 82 | } 83 | 84 | @main 85 | struct HomeWidgetExample: Widget { 86 | let kind: String = "HomeWidgetExample" 87 | 88 | var body: some WidgetConfiguration { 89 | StaticConfiguration(kind: kind, provider: Provider()) { entry in 90 | HomeWidgetExampleEntryView(entry: entry) 91 | } 92 | .configurationDisplayName("My Widget") 93 | .description("This is an example widget.") 94 | } 95 | } 96 | 97 | struct HomeWidgetExample_Previews: PreviewProvider { 98 | static var previews: some View { 99 | HomeWidgetExampleEntryView( 100 | entry: ExampleEntry(date: Date(), title: "Example Title", message: "Example Message") 101 | ) 102 | .previewContext(WidgetPreviewContext(family: .systemSmall)) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /packages/home_widget/example/ios/HomeWidgetExample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSExtension 6 | 7 | NSExtensionPointIdentifier 8 | com.apple.widgetkit-extension 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /packages/home_widget/example/ios/HomeWidgetExampleExtension.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.application-groups 6 | 7 | YOUR_GROUP_ID 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /packages/home_widget/example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '12.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | 36 | target 'RunnerTests' do 37 | use_frameworks! 38 | use_modular_headers! 39 | inherit! :search_paths 40 | end 41 | 42 | target 'HomeWidgetExampleExtension' do 43 | use_frameworks! 44 | use_modular_headers! 45 | inherit! :search_paths 46 | 47 | pod 'home_widget', :path => '.symlinks/plugins/home_widget/ios' 48 | end 49 | end 50 | 51 | post_install do |installer| 52 | installer.pods_project.targets.each do |target| 53 | flutter_additional_ios_build_settings(target) 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /packages/home_widget/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /packages/home_widget/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /packages/home_widget/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 43 | 49 | 50 | 51 | 52 | 53 | 63 | 65 | 71 | 72 | 73 | 74 | 80 | 82 | 88 | 89 | 90 | 91 | 93 | 94 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /packages/home_widget/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /packages/home_widget/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /packages/home_widget/example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | import home_widget 4 | import workmanager 5 | 6 | @UIApplicationMain 7 | @objc class AppDelegate: FlutterAppDelegate { 8 | override func application( 9 | _ application: UIApplication, 10 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 11 | ) -> Bool { 12 | GeneratedPluginRegistrant.register(with: self) 13 | 14 | UIApplication.shared.setMinimumBackgroundFetchInterval(TimeInterval(60 * 15)) 15 | 16 | WorkmanagerPlugin.setPluginRegistrantCallback { registry in 17 | GeneratedPluginRegistrant.register(with: registry) 18 | } 19 | 20 | if #available(iOS 17, *) { 21 | HomeWidgetBackgroundWorker.setPluginRegistrantCallback { registry in 22 | GeneratedPluginRegistrant.register(with: registry) 23 | } 24 | } 25 | 26 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/home_widget/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /packages/home_widget/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/packages/home_widget/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /packages/home_widget/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/packages/home_widget/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /packages/home_widget/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/packages/home_widget/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /packages/home_widget/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/packages/home_widget/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /packages/home_widget/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/packages/home_widget/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /packages/home_widget/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/packages/home_widget/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /packages/home_widget/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/packages/home_widget/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /packages/home_widget/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/packages/home_widget/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /packages/home_widget/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/packages/home_widget/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /packages/home_widget/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/packages/home_widget/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /packages/home_widget/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/packages/home_widget/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /packages/home_widget/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/packages/home_widget/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /packages/home_widget/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/packages/home_widget/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /packages/home_widget/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/packages/home_widget/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /packages/home_widget/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/packages/home_widget/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /packages/home_widget/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/home_widget/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/packages/home_widget/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /packages/home_widget/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/packages/home_widget/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /packages/home_widget/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/packages/home_widget/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /packages/home_widget/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | 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. -------------------------------------------------------------------------------- /packages/home_widget/example/ios/Runner/BackgroundIntent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BackgroundIntent.swift 3 | // Runner 4 | // 5 | // Created by Anton Borries on 26.08.23. 6 | // 7 | 8 | import AppIntents 9 | import Foundation 10 | import home_widget 11 | 12 | @available(iOS 17, *) 13 | public struct BackgroundIntent: AppIntent { 14 | static public var title: LocalizedStringResource = "HomeWidget Background Intent" 15 | 16 | @Parameter(title: "Widget URI") 17 | var url: URL? 18 | 19 | @Parameter(title: "AppGroup") 20 | var appGroup: String? 21 | 22 | public init() {} 23 | 24 | public init(url: URL?, appGroup: String?) { 25 | self.url = url 26 | self.appGroup = appGroup 27 | } 28 | 29 | public func perform() async throws -> some IntentResult { 30 | await HomeWidgetBackgroundWorker.run(url: url, appGroup: appGroup!) 31 | 32 | return .result() 33 | } 34 | } 35 | 36 | /// This is required if you want to have the widget be interactive even when the app is fully suspended. 37 | /// Note that this will launch your App so on the Flutter side you should check for the current Lifecycle State before doing heavy tasks 38 | @available(iOS 17, *) 39 | @available(iOSApplicationExtension, unavailable) 40 | extension BackgroundIntent: ForegroundContinuableIntent {} 41 | -------------------------------------------------------------------------------- /packages/home_widget/example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /packages/home_widget/example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /packages/home_widget/example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CADisableMinimumFrameDurationOnPhone 6 | 7 | CFBundleDevelopmentRegion 8 | $(DEVELOPMENT_LANGUAGE) 9 | CFBundleDisplayName 10 | Example 11 | CFBundleExecutable 12 | $(EXECUTABLE_NAME) 13 | CFBundleIdentifier 14 | $(PRODUCT_BUNDLE_IDENTIFIER) 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | home_widget_example 19 | CFBundlePackageType 20 | APPL 21 | CFBundleShortVersionString 22 | $(FLUTTER_BUILD_NAME) 23 | CFBundleSignature 24 | ???? 25 | CFBundleVersion 26 | $(FLUTTER_BUILD_NUMBER) 27 | LSRequiresIPhoneOS 28 | 29 | UIApplicationSupportsIndirectInputEvents 30 | 31 | UILaunchStoryboardName 32 | LaunchScreen 33 | UIMainStoryboardFile 34 | Main 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UISupportedInterfaceOrientations~ipad 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationPortraitUpsideDown 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /packages/home_widget/example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /packages/home_widget/example/ios/Runner/Runner.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.application-groups 6 | 7 | YOUR_GROUP_ID 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /packages/home_widget/example/ios/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /packages/home_widget/example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: home_widget_example 2 | description: Demonstrates how to use the home_widget plugin. 3 | 4 | publish_to: 'none' 5 | 6 | environment: 7 | sdk: ">=2.12.0 <4.0.0" 8 | 9 | dependency_overrides: 10 | workmanager: 11 | git: 12 | url: https://github.com/fluttercommunity/flutter_workmanager 13 | path: workmanager 14 | ref: d94eb34 15 | 16 | dependencies: 17 | flutter: 18 | sdk: flutter 19 | workmanager: ^0.5.2 20 | 21 | home_widget: 22 | path: ../ 23 | 24 | cupertino_icons: ^1.0.6 25 | 26 | dev_dependencies: 27 | flutter_lints: ^2.0.1 28 | device_info_plus: ^9.0.3 29 | integration_test: 30 | sdk: flutter 31 | flutter_test: 32 | sdk: flutter 33 | 34 | flutter: 35 | 36 | uses-material-design: true 37 | 38 | -------------------------------------------------------------------------------- /packages/home_widget/example/test_driver/integration_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:integration_test/integration_test_driver.dart'; 2 | 3 | Future main() => integrationDriver(); 4 | -------------------------------------------------------------------------------- /packages/home_widget/ios/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vagrant/ 3 | .sconsign.dblite 4 | .svn/ 5 | 6 | .DS_Store 7 | *.swp 8 | profile 9 | 10 | DerivedData/ 11 | build/ 12 | GeneratedPluginRegistrant.h 13 | GeneratedPluginRegistrant.m 14 | 15 | .generated/ 16 | 17 | *.pbxuser 18 | *.mode1v3 19 | *.mode2v3 20 | *.perspectivev3 21 | 22 | !default.pbxuser 23 | !default.mode1v3 24 | !default.mode2v3 25 | !default.perspectivev3 26 | 27 | xcuserdata 28 | 29 | *.moved-aside 30 | 31 | *.pyc 32 | *sync/ 33 | Icon? 34 | .tags* 35 | 36 | /Flutter/Generated.xcconfig 37 | /Flutter/flutter_export_environment.sh -------------------------------------------------------------------------------- /packages/home_widget/ios/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/packages/home_widget/ios/Assets/.gitkeep -------------------------------------------------------------------------------- /packages/home_widget/ios/Classes/HomeWidgetBackgroundWorker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HomeWidgetBackgroundIntent.swift 3 | // home_widget 4 | // 5 | // Created by Anton Borries on 25.08.23. 6 | // 7 | 8 | import Flutter 9 | import Foundation 10 | import Swift 11 | 12 | @available(iOS 17, *) 13 | public struct HomeWidgetBackgroundWorker { 14 | 15 | static let dispatcherKey: String = "home_widget.internal.background.dispatcher" 16 | static let callbackKey: String = "home_widget.internal.background.callback" 17 | 18 | static var engine: FlutterEngine? 19 | static var channel: FlutterMethodChannel? 20 | 21 | static var isSetupCompleted: Bool = false 22 | static var continuations: [CheckedContinuation] = [] 23 | 24 | private static var registerPlugins: FlutterPluginRegistrantCallback? 25 | 26 | public static func setPluginRegistrantCallback(registerPlugins: FlutterPluginRegistrantCallback) { 27 | self.registerPlugins = registerPlugins 28 | } 29 | 30 | /// Call this method to invoke the callback registered in your Flutter App. 31 | /// The url you provide will be used as arguments in the callback function in dart 32 | /// The AppGroup is necessary to retrieve the dart callbacks 33 | static public func run(url: URL?, appGroup: String) async { 34 | if isSetupCompleted == false { 35 | await withCheckedContinuation { continuation in 36 | continuations.append(continuation) 37 | } 38 | } 39 | 40 | let preferences = UserDefaults.init(suiteName: appGroup) 41 | let dispatcher = preferences?.object(forKey: dispatcherKey) as! Int64 42 | NSLog("Dispatcher: \(dispatcher)") 43 | 44 | await sendEvent(url: url, appGroup: appGroup) 45 | } 46 | 47 | static func setupEngine(dispatcher: Int64) { 48 | engine = FlutterEngine( 49 | name: "home_widget_background", project: nil, allowHeadlessExecution: true) 50 | 51 | channel = FlutterMethodChannel( 52 | name: "home_widget/background", binaryMessenger: engine!.binaryMessenger, 53 | codec: FlutterStandardMethodCodec.sharedInstance() 54 | ) 55 | let flutterCallbackInfo = FlutterCallbackCache.lookupCallbackInformation(dispatcher) 56 | let callbackName = flutterCallbackInfo?.callbackName 57 | let callbackLibrary = flutterCallbackInfo?.callbackLibraryPath 58 | 59 | let started = engine?.run( 60 | withEntrypoint: flutterCallbackInfo?.callbackName, 61 | libraryURI: flutterCallbackInfo?.callbackLibraryPath) 62 | if registerPlugins != nil { 63 | registerPlugins?(engine!) 64 | } else { 65 | HomeWidgetPlugin.register(with: engine!.registrar(forPlugin: "home_widget")!) 66 | } 67 | 68 | channel?.setMethodCallHandler(handle) 69 | } 70 | 71 | public static func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { 72 | switch call.method { 73 | case "HomeWidget.backgroundInitialized": 74 | isSetupCompleted = true 75 | while !continuations.isEmpty { 76 | let continuation = continuations.removeFirst() 77 | continuation.resume() 78 | } 79 | result(true) 80 | default: 81 | result(FlutterMethodNotImplemented) 82 | } 83 | } 84 | 85 | static func sendEvent(url: URL?, appGroup: String) async { 86 | guard let _channel = channel else { 87 | return 88 | } 89 | let preferences = UserDefaults.init(suiteName: appGroup) 90 | guard let _callback = preferences?.object(forKey: callbackKey) as? Int64 else { 91 | return 92 | } 93 | await withCheckedContinuation { continuation in 94 | DispatchQueue.main.async { 95 | _channel.invokeMethod("", arguments: [_callback, url?.absoluteString]) { _ in 96 | continuation.resume() 97 | } 98 | } 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /packages/home_widget/ios/home_widget.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. 3 | # Run `pod lib lint home_widget.podspec' to validate before publishing. 4 | # 5 | Pod::Spec.new do |s| 6 | s.name = 'home_widget' 7 | s.version = '0.0.1' 8 | s.summary = 'A new flutter plugin project.' 9 | s.description = <<-DESC 10 | A new flutter plugin project. 11 | DESC 12 | s.homepage = 'http://example.com' 13 | s.license = { :file => '../LICENSE' } 14 | s.author = { 'Your Company' => 'email@example.com' } 15 | s.source = { :path => '.' } 16 | s.source_files = 'Classes/**/*' 17 | s.dependency 'Flutter' 18 | s.platform = :ios, '11.0' 19 | 20 | # Flutter.framework does not contain a i386 slice. Only x86_64 simulators are supported. 21 | s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' } 22 | s.swift_version = '5.0' 23 | end 24 | -------------------------------------------------------------------------------- /packages/home_widget/lib/home_widget.dart: -------------------------------------------------------------------------------- 1 | library home_widget; 2 | 3 | export 'package:home_widget/src/home_widget.dart'; 4 | export 'package:home_widget/src/home_widget_callback_dispatcher.dart'; 5 | export 'package:home_widget/src/home_widget_info.dart'; 6 | -------------------------------------------------------------------------------- /packages/home_widget/lib/src/home_widget_callback_dispatcher.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:ui'; 3 | 4 | import 'package:flutter/cupertino.dart'; 5 | import 'package:flutter/services.dart'; 6 | 7 | /// Dispatcher used for calling dart code from Native Code while in the background 8 | @pragma("vm:entry-point") 9 | Future callbackDispatcher() async { 10 | const backgroundChannel = MethodChannel('home_widget/background'); 11 | WidgetsFlutterBinding.ensureInitialized(); 12 | 13 | backgroundChannel.setMethodCallHandler((call) async { 14 | final args = call.arguments; 15 | 16 | final callback = PluginUtilities.getCallbackFromHandle( 17 | CallbackHandle.fromRawHandle(args[0] as int), 18 | ) as FutureOr Function(Uri?); 19 | 20 | final rawUri = args[1] as String?; 21 | 22 | Uri? uri; 23 | if (rawUri != null) { 24 | uri = Uri.parse(rawUri); 25 | } 26 | 27 | await callback.call(uri); 28 | }); 29 | 30 | await backgroundChannel.invokeMethod('HomeWidget.backgroundInitialized'); 31 | } 32 | -------------------------------------------------------------------------------- /packages/home_widget/lib/src/home_widget_info.dart: -------------------------------------------------------------------------------- 1 | /// Represents information about the pinned home widget. 2 | class HomeWidgetInfo { 3 | /// Only iOS. The size of the widget: small, medium, or large. 4 | String? iOSFamily; 5 | 6 | /// Only iOS. The string specified during creation of the widget’s configuration. 7 | String? iOSKind; 8 | 9 | /// Only Android. Unique identifier for each instance of the widget, used for tracking individual widget usage. 10 | int? androidWidgetId; 11 | 12 | /// Only Android. The [androidClassName] parameter represents the class name of the widget. 13 | String? androidClassName; 14 | 15 | /// Only Android. Loads the localized label to display to the user in the AppWidget picker. 16 | String? androidLabel; 17 | 18 | /// Only iOS. The configuration of the widget if setup as a configurable widget. 19 | /// In case `WidgetConfigurationIntent` is used, the configuration needs to passed to the Widget 20 | Map? configuration; 21 | 22 | /// Constructs a [HomeWidgetInfo] object. 23 | HomeWidgetInfo({ 24 | this.iOSFamily, 25 | this.iOSKind, 26 | this.androidWidgetId, 27 | this.androidClassName, 28 | this.androidLabel, 29 | this.configuration, 30 | }); 31 | 32 | /// Constructs a [HomeWidgetInfo] object from a map. 33 | /// 34 | /// The [data] parameter is a map that contains the widget information. 35 | factory HomeWidgetInfo.fromMap(Map data) { 36 | return HomeWidgetInfo( 37 | iOSFamily: data['family'] as String?, 38 | iOSKind: data['kind'] as String?, 39 | androidWidgetId: data['widgetId'] as int?, 40 | androidClassName: data['androidClassName'] as String?, 41 | androidLabel: data['label'] as String?, 42 | configuration: ((data['configuration'] as Map?) 43 | ?..removeWhere((key, _) => key is! String)) 44 | ?.cast(), 45 | ); 46 | } 47 | 48 | @override 49 | String toString() { 50 | return 'HomeWidgetInfo(' 51 | 'iOSFamily: $iOSFamily, ' 52 | 'iOSKind: $iOSKind, ' 53 | 'androidWidgetId: $androidWidgetId, ' 54 | 'androidClassName: $androidClassName, ' 55 | 'androidLabel: $androidLabel, ' 56 | 'configuration: $configuration' 57 | ')'; 58 | } 59 | 60 | @override 61 | bool operator ==(Object other) { 62 | if (identical(this, other)) return true; 63 | 64 | return other is HomeWidgetInfo && 65 | other.iOSFamily == iOSFamily && 66 | other.iOSKind == iOSKind && 67 | other.androidWidgetId == androidWidgetId && 68 | other.androidClassName == androidClassName && 69 | other.androidLabel == androidLabel && 70 | other.configuration == configuration; 71 | } 72 | 73 | @override 74 | int get hashCode { 75 | return iOSFamily.hashCode ^ 76 | iOSKind.hashCode ^ 77 | androidWidgetId.hashCode ^ 78 | androidClassName.hashCode ^ 79 | androidLabel.hashCode ^ 80 | configuration.hashCode; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /packages/home_widget/pub/screenshots/android.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/packages/home_widget/pub/screenshots/android.png -------------------------------------------------------------------------------- /packages/home_widget/pub/screenshots/ios-counter.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/packages/home_widget/pub/screenshots/ios-counter.gif -------------------------------------------------------------------------------- /packages/home_widget/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: home_widget 2 | description: A plugin to provide a common interface for creating HomeScreen Widgets for Android and iOS. 3 | version: 0.8.0 4 | repository: https://github.com/ABausG/home_widget 5 | funding: 6 | - https://github.com/sponsors/ABausG/ 7 | topics: 8 | - home-screen-widget 9 | - native 10 | - android 11 | - ios 12 | screenshots: 13 | - description: 'iOS Counter HomeScreen Widget' 14 | path: pub/screenshots/ios-counter.gif 15 | - description: 'Android HomeScreen Widget' 16 | path: pub/screenshots/android.png 17 | documentation: https://docs.page/ABausG/home_widget 18 | 19 | environment: 20 | sdk: '>=3.4.0 <4.0.0' 21 | flutter: '>=3.20.0-1.2.pre' 22 | 23 | dependencies: 24 | flutter: 25 | sdk: flutter 26 | path_provider: ^2.1.3 27 | path_provider_foundation: ^2.4.0 28 | 29 | dev_dependencies: 30 | flutter_test: 31 | sdk: flutter 32 | flutter_lints: ^3.0.1 33 | golden_toolkit: ^0.15.0 34 | mocktail: ^1.0.3 35 | path_provider_platform_interface: 36 | plugin_platform_interface: 37 | 38 | flutter: 39 | plugin: 40 | platforms: 41 | android: 42 | package: es.antonborri.home_widget 43 | pluginClass: HomeWidgetPlugin 44 | ios: 45 | pluginClass: HomeWidgetPlugin 46 | -------------------------------------------------------------------------------- /packages/home_widget/test/background_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:ui'; 3 | 4 | import 'package:flutter/services.dart'; 5 | import 'package:flutter_test/flutter_test.dart'; 6 | import 'package:home_widget/home_widget.dart'; 7 | 8 | Completer completer = Completer(); 9 | const backgroundChannel = MethodChannel('home_widget/background'); 10 | 11 | void main() { 12 | testWidgets('Callback Dispatcher calls callbacks', (tester) async { 13 | final callbackHandle = 14 | PluginUtilities.getCallbackHandle(testCallback)?.toRawHandle(); 15 | const testUri = 'homeWidget://homeWidgetTest'; 16 | 17 | tester.binding.defaultBinaryMessenger 18 | .setMockMethodCallHandler(backgroundChannel, (call) async { 19 | if (call.method == 'HomeWidget.backgroundInitialized') { 20 | emitEvent( 21 | tester, 22 | backgroundChannel.codec 23 | .encodeMethodCall(MethodCall('', [callbackHandle, testUri])), 24 | ); 25 | return true; 26 | } else { 27 | return null; 28 | } 29 | }); 30 | 31 | await callbackDispatcher(); 32 | 33 | final receivedUri = await completer.future; 34 | 35 | expect(receivedUri, Uri.parse(testUri)); 36 | }); 37 | } 38 | 39 | void emitEvent(WidgetTester tester, ByteData? event) { 40 | tester.binding.defaultBinaryMessenger.handlePlatformMessage( 41 | backgroundChannel.name, 42 | event, 43 | (ByteData? reply) {}, 44 | ); 45 | } 46 | 47 | @pragma('vm:entry-point') 48 | Future testCallback(Uri? uri) async { 49 | completer.complete(uri); 50 | } 51 | -------------------------------------------------------------------------------- /packages/home_widget/test/goldens/render-flutter-widget.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABausG/home_widget/404bac018012b5466fc939fa3cc6cee87d1c06d6/packages/home_widget/test/goldens/render-flutter-widget.png -------------------------------------------------------------------------------- /packages/home_widget/test/home_widget_info_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:home_widget/home_widget.dart'; 3 | 4 | void main() { 5 | group('HomeWidgetInfo', () { 6 | test('fromMap constructs HomeWidgetInfo object from map', () { 7 | final data = { 8 | 'family': 'medium', 9 | 'kind': 'anotherKind', 10 | 'widgetId': 1, 11 | 'androidClassName': 'com.example.AnotherWidget', 12 | 'label': 'Another Widget', 13 | 'configuration': {'config': 'value'}, 14 | }; 15 | 16 | final info = HomeWidgetInfo.fromMap(data); 17 | 18 | expect(info.iOSFamily, 'medium'); 19 | expect(info.iOSKind, 'anotherKind'); 20 | expect(info.androidClassName, 'com.example.AnotherWidget'); 21 | expect(info.androidLabel, 'Another Widget'); 22 | expect(info.androidWidgetId, 1); 23 | expect(info.configuration, {'config': 'value'}); 24 | }); 25 | 26 | test('handle configuration', () { 27 | final data = { 28 | 'family': 'medium', 29 | 'kind': 'anotherKind', 30 | 'widgetId': 1, 31 | 'androidClassName': 'com.example.AnotherWidget', 32 | 'label': 'Another Widget', 33 | 'configuration': {'config': 'value'}, 34 | }; 35 | 36 | final info = HomeWidgetInfo.fromMap(data); 37 | 38 | expect(info.configuration, {'config': 'value'}); 39 | }); 40 | 41 | test('HomeWidgetInfo toString', () { 42 | final homeWidgetInfo = HomeWidgetInfo( 43 | iOSFamily: 'systemSmall', 44 | iOSKind: 'ParkingWidget', 45 | androidWidgetId: 1, 46 | androidClassName: 'com.example.MyWidget', 47 | androidLabel: 'My Widget', 48 | configuration: {'config': 'example'}, 49 | ); 50 | 51 | expect( 52 | homeWidgetInfo.toString(), 53 | 'HomeWidgetInfo(iOSFamily: systemSmall, iOSKind: ParkingWidget, androidWidgetId: 1, androidClassName: com.example.MyWidget, androidLabel: My Widget, configuration: {config: example})', 54 | ); 55 | }); 56 | 57 | test('HomeWidgetInfo equality', () { 58 | final info1 = HomeWidgetInfo( 59 | iOSFamily: 'medium', 60 | iOSKind: 'anotherKind', 61 | androidWidgetId: 1, 62 | androidClassName: 'com.example.AnotherWidget', 63 | androidLabel: 'Another Widget', 64 | ); 65 | 66 | final info2 = HomeWidgetInfo( 67 | iOSFamily: 'medium', 68 | iOSKind: 'anotherKind', 69 | androidWidgetId: 1, 70 | androidClassName: 'com.example.AnotherWidget', 71 | androidLabel: 'Another Widget', 72 | ); 73 | 74 | final info3 = HomeWidgetInfo( 75 | iOSFamily: 'systemSmall', 76 | iOSKind: 'ParkingWidget', 77 | androidWidgetId: 1, 78 | androidClassName: 'com.example.MyWidget', 79 | androidLabel: 'My Widget', 80 | ); 81 | 82 | expect(info1 == info2, true); 83 | expect(info1.hashCode, equals(info2.hashCode)); 84 | expect(info1 == info3, false); 85 | expect(info1.hashCode, isNot(equals(info3.hashCode))); 86 | }); 87 | }); 88 | } 89 | -------------------------------------------------------------------------------- /packages/home_widget/test/mocks.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: depend_on_referenced_packages 2 | 3 | import 'dart:io'; 4 | 5 | import 'package:mocktail/mocktail.dart'; 6 | import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; 7 | import 'package:plugin_platform_interface/plugin_platform_interface.dart'; 8 | 9 | class MockFile extends Mock implements File {} 10 | 11 | class MockPathProvider extends Mock 12 | with MockPlatformInterfaceMixin 13 | implements PathProviderPlatform {} 14 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: home_widget_workspace 2 | 3 | environment: 4 | sdk: '>=3.0.0 <4.0.0' 5 | dependencies: 6 | melos: ^6.1.0 7 | --------------------------------------------------------------------------------