From 9c93192c25edeeb33c8637949232341e903abcbe Mon Sep 17 00:00:00 2001 From: Biplab Dutta Date: Sun, 3 Sep 2023 02:50:58 +0545 Subject: [PATCH 01/11] doc: update readme with a new project for fpdart learning --- packages/fpdart/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/fpdart/README.md b/packages/fpdart/README.md index 199cc17..0fbf2c5 100644 --- a/packages/fpdart/README.md +++ b/packages/fpdart/README.md @@ -51,6 +51,7 @@ fpdart is inspired by [fp-ts](https://gcanti.github.io/fp-ts/), [cats](https://t - [🧑‍đŸĢ Getting started with functional programming](#-getting-started-with-functional-programming) - [đŸ’ģ Installation](#-installation) - [✨ Examples](#-examples) + - [`now_in_dart_flutter`](#now_in_dart_flutter) - [`fpdart` + `riverpod`](#fpdart--riverpod) - [Pokeapi](#pokeapi) - [Open Meteo API](#open-meteo-api) @@ -128,6 +129,9 @@ dependencies: ## ✨ Examples +### [`now_in_dart_flutter`](https://github.com/Biplab-Dutta/now-in-dart-flutter/tree/master/lib) +A simple app that uses WebView under the hood to display all the news and updates regarding Dart and Flutter. The information is retrieved using GitHub's API and the github repository used as source is that of Flutter and Dart's. The project heavily relies on `fpdart` and its latest APIs (Either, Do, Task, IO, flatmap, etc). It uses `flutter_bloc` for state management. + ### [`fpdart` + `riverpod`](https://www.sandromaglione.com/course/fpdart-riverpod-develop-flutter-app) Step by step course on how to build a safe, maintainable, and testable Flutter app using `fpdart` and `riverpod`. From ba7580fd59d2979d54eb50eceeaaa171666c34b0 Mon Sep 17 00:00:00 2001 From: Biplab Dutta Date: Sun, 3 Sep 2023 02:58:49 +0545 Subject: [PATCH 02/11] doc: add a new example project --- examples/now_in_dart_flutter/.gitignore | 47 ++ examples/now_in_dart_flutter/LICENSE | 21 + examples/now_in_dart_flutter/README.md | 11 + .../now_in_dart_flutter/analysis_options.yaml | 14 + .../now_in_dart_flutter/android/.gitignore | 13 + .../android/app/build.gradle | 71 +++ .../android/app/src/debug/AndroidManifest.xml | 8 + .../android/app/src/main/AndroidManifest.xml | 19 + .../now_in_dart_flutter/MainActivity.kt | 6 + .../res/drawable-v21/launch_background.xml | 12 + .../main/res/drawable/launch_background.xml | 12 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 544 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 442 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 721 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 1031 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 1443 bytes .../app/src/main/res/values-night/styles.xml | 18 + .../app/src/main/res/values/styles.xml | 18 + .../app/src/profile/AndroidManifest.xml | 8 + .../now_in_dart_flutter/android/build.gradle | 31 + .../android/gradle.properties | 3 + .../gradle/wrapper/gradle-wrapper.properties | 5 + .../android/settings.gradle | 11 + .../assets/icon_dart.svg.vec | Bin 0 -> 767 bytes .../assets/icon_flutter.svg.vec | Bin 0 -> 1819 bytes examples/now_in_dart_flutter/ios/.gitignore | 34 ++ .../ios/Flutter/AppFrameworkInfo.plist | 26 + .../ios/Flutter/Debug.xcconfig | 2 + .../ios/Flutter/Release.xcconfig | 2 + examples/now_in_dart_flutter/ios/Podfile | 41 ++ examples/now_in_dart_flutter/ios/Podfile.lock | 34 ++ .../ios/Runner.xcodeproj/project.pbxproj | 549 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../xcshareddata/xcschemes/Runner.xcscheme | 87 +++ .../contents.xcworkspacedata | 10 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../ios/Runner/AppDelegate.swift | 13 + .../AppIcon.appiconset/Contents.json | 122 ++++ .../Icon-App-1024x1024@1x.png | Bin 0 -> 10932 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 0 -> 564 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 0 -> 1283 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 0 -> 1588 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 0 -> 1025 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 0 -> 1716 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 0 -> 1920 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 0 -> 1283 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 0 -> 1895 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 0 -> 2665 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 0 -> 2665 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 0 -> 3831 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 0 -> 1888 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 0 -> 3294 bytes .../Icon-App-83.5x83.5@2x.png | Bin 0 -> 3612 bytes .../LaunchImage.imageset/Contents.json | 23 + .../LaunchImage.imageset/LaunchImage.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@2x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@3x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/README.md | 5 + .../Runner/Base.lproj/LaunchScreen.storyboard | 37 ++ .../ios/Runner/Base.lproj/Main.storyboard | 26 + .../now_in_dart_flutter/ios/Runner/Info.plist | 51 ++ .../ios/Runner/Runner-Bridging-Header.h | 1 + examples/now_in_dart_flutter/lib/app/app.dart | 31 + .../lib/app/app_bloc_observer.dart | 17 + .../now_in_dart_flutter/lib/bootstrap.dart | 43 ++ .../lib/core/data/data.dart | 5 + .../lib/core/data/dio_extension.dart | 9 + .../lib/core/data/github_header.dart | 37 ++ .../lib/core/data/github_header_cache.dart | 38 ++ .../now_in_dart_flutter/lib/core/data/id.dart | 5 + .../lib/core/data/isar_database.dart | 27 + .../lib/core/data/remote_response.dart | 31 + .../lib/core/domain/failure.dart | 29 + .../lib/core/domain/fresh.dart | 48 ++ .../lib/core/presentation/assets_path.dart | 7 + .../core/presentation/lazy_indexed_stack.dart | 63 ++ .../presentation/no_connection_toast.dart | 30 + .../core/presentation/no_results_display.dart | 32 + .../lib/core/presentation/responsive.dart | 24 + .../features/detail/core/data/detail_dto.dart | 54 ++ .../core/data/detail_local_service.dart | 33 ++ .../core/data/detail_remote_service.dart | 96 +++ .../features/detail/core/domain/detail.dart | 19 + .../presentation/widget/detail_webview.dart | 61 ++ .../application/dart_detail_bloc.dart | 63 ++ .../application/dart_detail_event.dart | 19 + .../application/dart_detail_state.dart | 31 + .../data/dart_detail_local_service.dart | 13 + .../data/dart_detail_remote_service.dart | 18 + .../data/dart_detail_repository.dart | 49 ++ .../view/dart_changelog_page.dart | 76 +++ .../application/flutter_detail_bloc.dart | 91 +++ .../application/flutter_detail_event.dart | 32 + .../application/flutter_detail_state.dart | 31 + .../data/flutter_detail_local_service.dart | 13 + .../data/flutter_detail_remote_service.dart | 25 + .../data/flutter_detail_repository.dart | 60 ++ .../view/flutter_detail_common.dart | 75 +++ .../view/flutter_detail_page.dart | 32 + .../view/flutter_release_notes_page.dart | 28 + .../view/flutter_whats_new_page.dart | 27 + .../lib/features/home/cubit/home_cubit.dart | 10 + .../lib/features/home/cubit/home_state.dart | 12 + .../lib/features/home/home.dart | 2 + .../lib/features/home/view/home_page.dart | 137 +++++ .../lib/features/home/view/view.dart | 1 + examples/now_in_dart_flutter/lib/main.dart | 25 + examples/now_in_dart_flutter/pubspec.yaml | 39 ++ 111 files changed, 3078 insertions(+) create mode 100644 examples/now_in_dart_flutter/.gitignore create mode 100644 examples/now_in_dart_flutter/LICENSE create mode 100644 examples/now_in_dart_flutter/README.md create mode 100644 examples/now_in_dart_flutter/analysis_options.yaml create mode 100644 examples/now_in_dart_flutter/android/.gitignore create mode 100644 examples/now_in_dart_flutter/android/app/build.gradle create mode 100644 examples/now_in_dart_flutter/android/app/src/debug/AndroidManifest.xml create mode 100644 examples/now_in_dart_flutter/android/app/src/main/AndroidManifest.xml create mode 100644 examples/now_in_dart_flutter/android/app/src/main/kotlin/com/example/now_in_dart_flutter/MainActivity.kt create mode 100644 examples/now_in_dart_flutter/android/app/src/main/res/drawable-v21/launch_background.xml create mode 100644 examples/now_in_dart_flutter/android/app/src/main/res/drawable/launch_background.xml create mode 100644 examples/now_in_dart_flutter/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 examples/now_in_dart_flutter/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 examples/now_in_dart_flutter/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 examples/now_in_dart_flutter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 examples/now_in_dart_flutter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 examples/now_in_dart_flutter/android/app/src/main/res/values-night/styles.xml create mode 100644 examples/now_in_dart_flutter/android/app/src/main/res/values/styles.xml create mode 100644 examples/now_in_dart_flutter/android/app/src/profile/AndroidManifest.xml create mode 100644 examples/now_in_dart_flutter/android/build.gradle create mode 100644 examples/now_in_dart_flutter/android/gradle.properties create mode 100644 examples/now_in_dart_flutter/android/gradle/wrapper/gradle-wrapper.properties create mode 100644 examples/now_in_dart_flutter/android/settings.gradle create mode 100644 examples/now_in_dart_flutter/assets/icon_dart.svg.vec create mode 100644 examples/now_in_dart_flutter/assets/icon_flutter.svg.vec create mode 100644 examples/now_in_dart_flutter/ios/.gitignore create mode 100644 examples/now_in_dart_flutter/ios/Flutter/AppFrameworkInfo.plist create mode 100644 examples/now_in_dart_flutter/ios/Flutter/Debug.xcconfig create mode 100644 examples/now_in_dart_flutter/ios/Flutter/Release.xcconfig create mode 100644 examples/now_in_dart_flutter/ios/Podfile create mode 100644 examples/now_in_dart_flutter/ios/Podfile.lock create mode 100644 examples/now_in_dart_flutter/ios/Runner.xcodeproj/project.pbxproj create mode 100644 examples/now_in_dart_flutter/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 examples/now_in_dart_flutter/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 examples/now_in_dart_flutter/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 examples/now_in_dart_flutter/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme create mode 100644 examples/now_in_dart_flutter/ios/Runner.xcworkspace/contents.xcworkspacedata create mode 100644 examples/now_in_dart_flutter/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 examples/now_in_dart_flutter/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 examples/now_in_dart_flutter/ios/Runner/AppDelegate.swift create mode 100644 examples/now_in_dart_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 examples/now_in_dart_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png create mode 100644 examples/now_in_dart_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png create mode 100644 examples/now_in_dart_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png create mode 100644 examples/now_in_dart_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png create mode 100644 examples/now_in_dart_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png create mode 100644 examples/now_in_dart_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png create mode 100644 examples/now_in_dart_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png create mode 100644 examples/now_in_dart_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png create mode 100644 examples/now_in_dart_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png create mode 100644 examples/now_in_dart_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png create mode 100644 examples/now_in_dart_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png create mode 100644 examples/now_in_dart_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png create mode 100644 examples/now_in_dart_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png create mode 100644 examples/now_in_dart_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png create mode 100644 examples/now_in_dart_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png create mode 100644 examples/now_in_dart_flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json create mode 100644 examples/now_in_dart_flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png create mode 100644 examples/now_in_dart_flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png create mode 100644 examples/now_in_dart_flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png create mode 100644 examples/now_in_dart_flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md create mode 100644 examples/now_in_dart_flutter/ios/Runner/Base.lproj/LaunchScreen.storyboard create mode 100644 examples/now_in_dart_flutter/ios/Runner/Base.lproj/Main.storyboard create mode 100644 examples/now_in_dart_flutter/ios/Runner/Info.plist create mode 100644 examples/now_in_dart_flutter/ios/Runner/Runner-Bridging-Header.h create mode 100644 examples/now_in_dart_flutter/lib/app/app.dart create mode 100644 examples/now_in_dart_flutter/lib/app/app_bloc_observer.dart create mode 100644 examples/now_in_dart_flutter/lib/bootstrap.dart create mode 100644 examples/now_in_dart_flutter/lib/core/data/data.dart create mode 100644 examples/now_in_dart_flutter/lib/core/data/dio_extension.dart create mode 100644 examples/now_in_dart_flutter/lib/core/data/github_header.dart create mode 100644 examples/now_in_dart_flutter/lib/core/data/github_header_cache.dart create mode 100644 examples/now_in_dart_flutter/lib/core/data/id.dart create mode 100644 examples/now_in_dart_flutter/lib/core/data/isar_database.dart create mode 100644 examples/now_in_dart_flutter/lib/core/data/remote_response.dart create mode 100644 examples/now_in_dart_flutter/lib/core/domain/failure.dart create mode 100644 examples/now_in_dart_flutter/lib/core/domain/fresh.dart create mode 100644 examples/now_in_dart_flutter/lib/core/presentation/assets_path.dart create mode 100644 examples/now_in_dart_flutter/lib/core/presentation/lazy_indexed_stack.dart create mode 100644 examples/now_in_dart_flutter/lib/core/presentation/no_connection_toast.dart create mode 100644 examples/now_in_dart_flutter/lib/core/presentation/no_results_display.dart create mode 100644 examples/now_in_dart_flutter/lib/core/presentation/responsive.dart create mode 100644 examples/now_in_dart_flutter/lib/features/detail/core/data/detail_dto.dart create mode 100644 examples/now_in_dart_flutter/lib/features/detail/core/data/detail_local_service.dart create mode 100644 examples/now_in_dart_flutter/lib/features/detail/core/data/detail_remote_service.dart create mode 100644 examples/now_in_dart_flutter/lib/features/detail/core/domain/detail.dart create mode 100644 examples/now_in_dart_flutter/lib/features/detail/core/presentation/widget/detail_webview.dart create mode 100644 examples/now_in_dart_flutter/lib/features/detail/dart_detail/application/dart_detail_bloc.dart create mode 100644 examples/now_in_dart_flutter/lib/features/detail/dart_detail/application/dart_detail_event.dart create mode 100644 examples/now_in_dart_flutter/lib/features/detail/dart_detail/application/dart_detail_state.dart create mode 100644 examples/now_in_dart_flutter/lib/features/detail/dart_detail/data/dart_detail_local_service.dart create mode 100644 examples/now_in_dart_flutter/lib/features/detail/dart_detail/data/dart_detail_remote_service.dart create mode 100644 examples/now_in_dart_flutter/lib/features/detail/dart_detail/data/dart_detail_repository.dart create mode 100644 examples/now_in_dart_flutter/lib/features/detail/dart_detail/presentation/view/dart_changelog_page.dart create mode 100644 examples/now_in_dart_flutter/lib/features/detail/flutter_detail/application/flutter_detail_bloc.dart create mode 100644 examples/now_in_dart_flutter/lib/features/detail/flutter_detail/application/flutter_detail_event.dart create mode 100644 examples/now_in_dart_flutter/lib/features/detail/flutter_detail/application/flutter_detail_state.dart create mode 100644 examples/now_in_dart_flutter/lib/features/detail/flutter_detail/data/flutter_detail_local_service.dart create mode 100644 examples/now_in_dart_flutter/lib/features/detail/flutter_detail/data/flutter_detail_remote_service.dart create mode 100644 examples/now_in_dart_flutter/lib/features/detail/flutter_detail/data/flutter_detail_repository.dart create mode 100644 examples/now_in_dart_flutter/lib/features/detail/flutter_detail/presentation/view/flutter_detail_common.dart create mode 100644 examples/now_in_dart_flutter/lib/features/detail/flutter_detail/presentation/view/flutter_detail_page.dart create mode 100644 examples/now_in_dart_flutter/lib/features/detail/flutter_detail/presentation/view/flutter_release_notes_page.dart create mode 100644 examples/now_in_dart_flutter/lib/features/detail/flutter_detail/presentation/view/flutter_whats_new_page.dart create mode 100644 examples/now_in_dart_flutter/lib/features/home/cubit/home_cubit.dart create mode 100644 examples/now_in_dart_flutter/lib/features/home/cubit/home_state.dart create mode 100644 examples/now_in_dart_flutter/lib/features/home/home.dart create mode 100644 examples/now_in_dart_flutter/lib/features/home/view/home_page.dart create mode 100644 examples/now_in_dart_flutter/lib/features/home/view/view.dart create mode 100644 examples/now_in_dart_flutter/lib/main.dart create mode 100644 examples/now_in_dart_flutter/pubspec.yaml diff --git a/examples/now_in_dart_flutter/.gitignore b/examples/now_in_dart_flutter/.gitignore new file mode 100644 index 0000000..4be7d82 --- /dev/null +++ b/examples/now_in_dart_flutter/.gitignore @@ -0,0 +1,47 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release + +# Isar specific +libisar.so diff --git a/examples/now_in_dart_flutter/LICENSE b/examples/now_in_dart_flutter/LICENSE new file mode 100644 index 0000000..64a93f2 --- /dev/null +++ b/examples/now_in_dart_flutter/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Biplab Dutta + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/examples/now_in_dart_flutter/README.md b/examples/now_in_dart_flutter/README.md new file mode 100644 index 0000000..5acbeae --- /dev/null +++ b/examples/now_in_dart_flutter/README.md @@ -0,0 +1,11 @@ +# now_in_dart_flutter + +### Online App Demo + +https://user-images.githubusercontent.com/63902683/192081298-78bb3e31-10ed-4150-b726-e9181b240346.mp4 + +### Offline App Demo + +https://user-images.githubusercontent.com/63902683/192081334-09f4be61-e836-4661-9aa9-cacce7c50739.mp4 + +New Readme is being added soon... diff --git a/examples/now_in_dart_flutter/analysis_options.yaml b/examples/now_in_dart_flutter/analysis_options.yaml new file mode 100644 index 0000000..1aa2aeb --- /dev/null +++ b/examples/now_in_dart_flutter/analysis_options.yaml @@ -0,0 +1,14 @@ +include: package:very_good_analysis/analysis_options.yaml + +linter: + rules: + public_member_api_docs: false + avoid_private_typedef_functions: false + library_private_types_in_public_api: false + sort_pub_dependencies: false + avoid_multiple_declarations_per_line: false + +analyzer: + exclude: + - '**/*.g.dart' + - '**/*.freezed.dart' diff --git a/examples/now_in_dart_flutter/android/.gitignore b/examples/now_in_dart_flutter/android/.gitignore new file mode 100644 index 0000000..6f56801 --- /dev/null +++ b/examples/now_in_dart_flutter/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/examples/now_in_dart_flutter/android/app/build.gradle b/examples/now_in_dart_flutter/android/app/build.gradle new file mode 100644 index 0000000..dda07d6 --- /dev/null +++ b/examples/now_in_dart_flutter/android/app/build.gradle @@ -0,0 +1,71 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion 33 + ndkVersion flutter.ndkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.example.now_in_dart_flutter" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. + minSdkVersion 19 + targetSdkVersion flutter.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} diff --git a/examples/now_in_dart_flutter/android/app/src/debug/AndroidManifest.xml b/examples/now_in_dart_flutter/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..13fc696 --- /dev/null +++ b/examples/now_in_dart_flutter/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + diff --git a/examples/now_in_dart_flutter/android/app/src/main/AndroidManifest.xml b/examples/now_in_dart_flutter/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..99a0572 --- /dev/null +++ b/examples/now_in_dart_flutter/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/now_in_dart_flutter/android/app/src/main/kotlin/com/example/now_in_dart_flutter/MainActivity.kt b/examples/now_in_dart_flutter/android/app/src/main/kotlin/com/example/now_in_dart_flutter/MainActivity.kt new file mode 100644 index 0000000..e27d236 --- /dev/null +++ b/examples/now_in_dart_flutter/android/app/src/main/kotlin/com/example/now_in_dart_flutter/MainActivity.kt @@ -0,0 +1,6 @@ +package com.example.now_in_dart_flutter + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/examples/now_in_dart_flutter/android/app/src/main/res/drawable-v21/launch_background.xml b/examples/now_in_dart_flutter/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/examples/now_in_dart_flutter/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/examples/now_in_dart_flutter/android/app/src/main/res/drawable/launch_background.xml b/examples/now_in_dart_flutter/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/examples/now_in_dart_flutter/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/examples/now_in_dart_flutter/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/examples/now_in_dart_flutter/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..db77bb4b7b0906d62b1847e87f15cdcacf6a4f29 GIT binary patch literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ literal 0 HcmV?d00001 diff --git a/examples/now_in_dart_flutter/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/examples/now_in_dart_flutter/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..17987b79bb8a35cc66c3c1fd44f5a5526c1b78be GIT binary patch literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ literal 0 HcmV?d00001 diff --git a/examples/now_in_dart_flutter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/examples/now_in_dart_flutter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..d5f1c8d34e7a88e3f88bea192c3a370d44689c3c GIT binary patch literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof literal 0 HcmV?d00001 diff --git a/examples/now_in_dart_flutter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/examples/now_in_dart_flutter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..4d6372eebdb28e45604e46eeda8dd24651419bc0 GIT binary patch literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` literal 0 HcmV?d00001 diff --git a/examples/now_in_dart_flutter/android/app/src/main/res/values-night/styles.xml b/examples/now_in_dart_flutter/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/examples/now_in_dart_flutter/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/examples/now_in_dart_flutter/android/app/src/main/res/values/styles.xml b/examples/now_in_dart_flutter/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/examples/now_in_dart_flutter/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/examples/now_in_dart_flutter/android/app/src/profile/AndroidManifest.xml b/examples/now_in_dart_flutter/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..13fc696 --- /dev/null +++ b/examples/now_in_dart_flutter/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + diff --git a/examples/now_in_dart_flutter/android/build.gradle b/examples/now_in_dart_flutter/android/build.gradle new file mode 100644 index 0000000..e394c2e --- /dev/null +++ b/examples/now_in_dart_flutter/android/build.gradle @@ -0,0 +1,31 @@ +buildscript { + ext.kotlin_version = '1.8.22' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:7.1.2' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/examples/now_in_dart_flutter/android/gradle.properties b/examples/now_in_dart_flutter/android/gradle.properties new file mode 100644 index 0000000..94adc3a --- /dev/null +++ b/examples/now_in_dart_flutter/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/examples/now_in_dart_flutter/android/gradle/wrapper/gradle-wrapper.properties b/examples/now_in_dart_flutter/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..cb24abd --- /dev/null +++ b/examples/now_in_dart_flutter/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip diff --git a/examples/now_in_dart_flutter/android/settings.gradle b/examples/now_in_dart_flutter/android/settings.gradle new file mode 100644 index 0000000..44e62bc --- /dev/null +++ b/examples/now_in_dart_flutter/android/settings.gradle @@ -0,0 +1,11 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/examples/now_in_dart_flutter/assets/icon_dart.svg.vec b/examples/now_in_dart_flutter/assets/icon_dart.svg.vec new file mode 100644 index 0000000000000000000000000000000000000000..3f06a445716ec2ddaa2c46a2cb7a8411cf433232 GIT binary patch literal 767 zcmaJ*za3Jsz_-xLGlQ7D&$AWaM3FHY z=w}uD@n4>}euBIKU0vYpxJXooX2#v_$1eKyX@nU;jp2`DnP*MZv#$;C9K*NRoj0cD z&Ka}S_pgJ0?5E{=aF@{-zL%yB#L$1RTd4T~4)*bi?-=<5;x%yIVecBV{SW3qTR_7l zHhU&KcS};neIKeg7ez!X&L<-FYoSN<8T-{)qVi2ix#jwivIg!hxXV6?27NbCf2O8F aHmE2{UvN0=N}d3Yngi_s literal 0 HcmV?d00001 diff --git a/examples/now_in_dart_flutter/assets/icon_flutter.svg.vec b/examples/now_in_dart_flutter/assets/icon_flutter.svg.vec new file mode 100644 index 0000000000000000000000000000000000000000..c0ebafea81b04a421eeb4f14a46601c70a5dfae2 GIT binary patch literal 1819 zcmbVNZBSHI7(Tlf2!(+K(ZB_QvP@YbMih4ucD>I=EFwdzv%$I$r4pbx_)%eq3nBv2 zAj0m7-=GU3AR0r!Zhl1MIEwzrWYo%xG1Ml@K^bQ;Vmo))tJTlW%$?^w_s+fV`##V6 z-19+r1rf|4!~{aD2((?yv@?X*?SZxnE9{Jbiw>&$VbLLBy$EnwsD1!DyJRf3#fO!6kF(DI2 z_l1TUjHXKwu)P*;CG(NB!+_|Ek>L31YYlhc@;ed-wyu}J1`~?xVXy_%!9elc$S&w9 zPF(s7Ybah;6vBgIe?&UkCc@AgEXS}&f>q*Jg!@V`oG(MPN`gZXQkp9qkJMq9H!lX7 zjvx&BtI+V&oA#iA)ZmAbFILd^0#IL_3FSZ#dgE5ZI75Q$%8mGaoeX7niXpw0fD3+u$zIfbUl<5R&N*KIr5)7LRn0)^ZOB z89k%IJ&)(#P66a9YnkC4LXyD(d8b37PlklO!qup0^JG?k_s(27p+EVkp(T0DcHN! zf|{^obm%QO@@f(^bRSxih!pyK=xG8Xvn}Yl{}yJyW5J`&x1mn$g;!nz0>0^i%f~9{ zb9=CISc~-)pTnBI79J2MKgDTf14jSKK|*9b)V4HS38eGu)JZI0h&Lyk@VS;$%_Qy?-y`2M&w;35 zl9R->Sq^R!x+D+S_rs#gU3RM)l86)l$S}|jm5#LihuQDRJ z#EP7JBYxRo#ZeCv(hgdY+-<^?ZY$z7CdfasAZ6(;^zG}UGgE<~OKnv166B7XpY_gh z5lgv#-8l2wX zy~6vJ_c8Bl-simU`TxL`c5ie~dl&jv8=IJvi*nOdwluQ{FO{8Otz+d_e)K!GuXQ(m zZ60GMmhT5&rCxjX~C|vK)d~K4TrWI literal 0 HcmV?d00001 diff --git a/examples/now_in_dart_flutter/ios/.gitignore b/examples/now_in_dart_flutter/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/examples/now_in_dart_flutter/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/examples/now_in_dart_flutter/ios/Flutter/AppFrameworkInfo.plist b/examples/now_in_dart_flutter/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..9625e10 --- /dev/null +++ b/examples/now_in_dart_flutter/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 11.0 + + diff --git a/examples/now_in_dart_flutter/ios/Flutter/Debug.xcconfig b/examples/now_in_dart_flutter/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..ec97fc6 --- /dev/null +++ b/examples/now_in_dart_flutter/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/examples/now_in_dart_flutter/ios/Flutter/Release.xcconfig b/examples/now_in_dart_flutter/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..c4855bf --- /dev/null +++ b/examples/now_in_dart_flutter/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/examples/now_in_dart_flutter/ios/Podfile b/examples/now_in_dart_flutter/ios/Podfile new file mode 100644 index 0000000..88359b2 --- /dev/null +++ b/examples/now_in_dart_flutter/ios/Podfile @@ -0,0 +1,41 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '11.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/examples/now_in_dart_flutter/ios/Podfile.lock b/examples/now_in_dart_flutter/ios/Podfile.lock new file mode 100644 index 0000000..3d0a47d --- /dev/null +++ b/examples/now_in_dart_flutter/ios/Podfile.lock @@ -0,0 +1,34 @@ +PODS: + - Flutter (1.0.0) + - isar_flutter_libs (1.0.0): + - Flutter + - url_launcher_ios (0.0.1): + - Flutter + - webview_flutter_wkwebview (0.0.1): + - Flutter + +DEPENDENCIES: + - Flutter (from `Flutter`) + - isar_flutter_libs (from `.symlinks/plugins/isar_flutter_libs/ios`) + - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) + - webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/ios`) + +EXTERNAL SOURCES: + Flutter: + :path: Flutter + isar_flutter_libs: + :path: ".symlinks/plugins/isar_flutter_libs/ios" + url_launcher_ios: + :path: ".symlinks/plugins/url_launcher_ios/ios" + webview_flutter_wkwebview: + :path: ".symlinks/plugins/webview_flutter_wkwebview/ios" + +SPEC CHECKSUMS: + Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 + isar_flutter_libs: bfb66f35a1fa9db9ec96b93539a03329ce147738 + url_launcher_ios: 839c58cdb4279282219f5e248c3321761ff3c4de + webview_flutter_wkwebview: b7e70ef1ddded7e69c796c7390ee74180182971f + +PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3 + +COCOAPODS: 1.11.3 diff --git a/examples/now_in_dart_flutter/ios/Runner.xcodeproj/project.pbxproj b/examples/now_in_dart_flutter/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..a28ac90 --- /dev/null +++ b/examples/now_in_dart_flutter/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,549 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 8CF41CD242281DA4B22F6178 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1538B71A50E98E1AAA59DA4E /* Pods_Runner.framework */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 061D9865A4F0F2A2FEAE1D5A /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 1538B71A50E98E1AAA59DA4E /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 93E0263D59E1A3A8EBE471F6 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + BA5EF43BCF85EA570255BAD4 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 8CF41CD242281DA4B22F6178 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 4C9024B4174EB08FFC557A57 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 1538B71A50E98E1AAA59DA4E /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 784D6B34FE9CF29AD488A34D /* Pods */ = { + isa = PBXGroup; + children = ( + 061D9865A4F0F2A2FEAE1D5A /* Pods-Runner.debug.xcconfig */, + 93E0263D59E1A3A8EBE471F6 /* Pods-Runner.release.xcconfig */, + BA5EF43BCF85EA570255BAD4 /* Pods-Runner.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 784D6B34FE9CF29AD488A34D /* Pods */, + 4C9024B4174EB08FFC557A57 /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 6972F1D59FBB2DB0C8839758 /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 28C32D7B14E8C18C921C5CC3 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1300; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 28C32D7B14E8C18C921C5CC3 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 6972F1D59FBB2DB0C8839758 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.nowInDartFlutter; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.nowInDartFlutter; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.nowInDartFlutter; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/examples/now_in_dart_flutter/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/examples/now_in_dart_flutter/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/examples/now_in_dart_flutter/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/examples/now_in_dart_flutter/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/examples/now_in_dart_flutter/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/examples/now_in_dart_flutter/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/examples/now_in_dart_flutter/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/examples/now_in_dart_flutter/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/examples/now_in_dart_flutter/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/examples/now_in_dart_flutter/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/examples/now_in_dart_flutter/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..c87d15a --- /dev/null +++ b/examples/now_in_dart_flutter/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/now_in_dart_flutter/ios/Runner.xcworkspace/contents.xcworkspacedata b/examples/now_in_dart_flutter/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/examples/now_in_dart_flutter/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/examples/now_in_dart_flutter/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/examples/now_in_dart_flutter/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/examples/now_in_dart_flutter/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/examples/now_in_dart_flutter/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/examples/now_in_dart_flutter/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/examples/now_in_dart_flutter/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/examples/now_in_dart_flutter/ios/Runner/AppDelegate.swift b/examples/now_in_dart_flutter/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..70693e4 --- /dev/null +++ b/examples/now_in_dart_flutter/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/examples/now_in_dart_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/examples/now_in_dart_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/examples/now_in_dart_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/examples/now_in_dart_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/examples/now_in_dart_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..dc9ada4725e9b0ddb1deab583e5b5102493aa332 GIT binary patch literal 10932 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 literal 0 HcmV?d00001 diff --git a/examples/now_in_dart_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/examples/now_in_dart_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..f091b6b0bca859a3f474b03065bef75ba58a9e4c GIT binary patch literal 1588 zcmV-42Fv-0P)C1SqPt}wig>|5Crh^=oyX$BK<}M8eLU3e2hGT;=G|!_SP)7zNI6fqUMB=)y zRAZ>eDe#*r`yDAVgB_R*LB*MAc)8(b{g{9McCXW!lq7r(btRoB9!8B-#AI6JMb~YFBEvdsV)`mEQO^&#eRKx@b&x- z5lZm*!WfD8oCLzfHGz#u7sT0^VLMI1MqGxF^v+`4YYnVYgk*=kU?HsSz{v({E3lb9 z>+xILjBN)t6`=g~IBOelGQ(O990@BfXf(DRI5I$qN$0Gkz-FSc$3a+2fX$AedL4u{ z4V+5Ong(9LiGcIKW?_352sR;LtDPmPJXI{YtT=O8=76o9;*n%_m|xo!i>7$IrZ-{l z-x3`7M}qzHsPV@$v#>H-TpjDh2UE$9g6sysUREDy_R(a)>=eHw-WAyfIN z*qb!_hW>G)Tu8nSw9yn#3wFMiLcfc4pY0ek1}8(NqkBR@t4{~oC>ryc-h_ByH(Cg5 z>ao-}771+xE3um9lWAY1FeQFxowa1(!J(;Jg*wrg!=6FdRX+t_<%z&d&?|Bn){>zm zZQj(aA_HeBY&OC^jj*)N`8fa^ePOU72VpInJoI1?`ty#lvlNzs(&MZX+R%2xS~5Kh zX*|AU4QE#~SgPzOXe9>tRj>hjU@c1k5Y_mW*Jp3fI;)1&g3j|zDgC+}2Q_v%YfDax z!?umcN^n}KYQ|a$Lr+51Nf9dkkYFSjZZjkma$0KOj+;aQ&721~t7QUKx61J3(P4P1 zstI~7-wOACnWP4=8oGOwz%vNDqD8w&Q`qcNGGrbbf&0s9L0De{4{mRS?o0MU+nR_! zrvshUau0G^DeMhM_v{5BuLjb#Hh@r23lDAk8oF(C+P0rsBpv85EP>4CVMx#04MOfG z;P%vktHcXwTj~+IE(~px)3*MY77e}p#|c>TD?sMatC0Tu4iKKJ0(X8jxQY*gYtxsC z(zYC$g|@+I+kY;dg_dE>scBf&bP1Nc@Hz<3R)V`=AGkc;8CXqdi=B4l2k|g;2%#m& z*jfX^%b!A8#bI!j9-0Fi0bOXl(-c^AB9|nQaE`*)Hw+o&jS9@7&Gov#HbD~#d{twV zXd^Tr^mWLfFh$@Dr$e;PBEz4(-2q1FF0}c;~B5sA}+Q>TOoP+t>wf)V9Iy=5ruQa;z)y zI9C9*oUga6=hxw6QasLPnee@3^Rr*M{CdaL5=R41nLs(AHk_=Y+A9$2&H(B7!_pURs&8aNw7?`&Z&xY_Ye z)~D5Bog^td-^QbUtkTirdyK^mTHAOuptDflut!#^lnKqU md>ggs(5nOWAqO?umG&QVYK#ibz}*4>0000U6E9hRK9^#O7(mu>ETqrXGsduA8$)?`v2seloOCza43C{NQ$$gAOH**MCn0Q?+L7dl7qnbRdqZ8LSVp1ItDxhxD?t@5_yHg6A8yI zC*%Wgg22K|8E#!~cTNYR~@Y9KepMPrrB8cABapAFa=`H+UGhkXUZV1GnwR1*lPyZ;*K(i~2gp|@bzp8}og7e*#% zEnr|^CWdVV!-4*Y_7rFvlww2Ze+>j*!Z!pQ?2l->4q#nqRu9`ELo6RMS5=br47g_X zRw}P9a7RRYQ%2Vsd0Me{_(EggTnuN6j=-?uFS6j^u69elMypu?t>op*wBx<=Wx8?( ztpe^(fwM6jJX7M-l*k3kEpWOl_Vk3@(_w4oc}4YF4|Rt=2V^XU?#Yz`8(e?aZ@#li0n*=g^qOcVpd-Wbok=@b#Yw zqn8u9a)z>l(1kEaPYZ6hwubN6i<8QHgsu0oE) ziJ(p;Wxm>sf!K+cw>R-(^Y2_bahB+&KI9y^);#0qt}t-$C|Bo71lHi{_+lg#f%RFy z0um=e3$K3i6K{U_4K!EX?F&rExl^W|G8Z8;`5z-k}OGNZ0#WVb$WCpQu-_YsiqKP?BB# vzVHS-CTUF4Ozn5G+mq_~Qqto~ahA+K`|lyv3(-e}00000NkvXXu0mjfd`9t{ literal 0 HcmV?d00001 diff --git a/examples/now_in_dart_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/examples/now_in_dart_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..d0ef06e7edb86cdfe0d15b4b0d98334a86163658 GIT binary patch literal 1716 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 literal 0 HcmV?d00001 diff --git a/examples/now_in_dart_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/examples/now_in_dart_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..c8f9ed8f5cee1c98386d13b17e89f719e83555b2 GIT binary patch literal 1895 zcmV-t2blPYP)FQtfgmafE#=YDCq`qUBt#QpG%*H6QHY765~R=q zZ6iudfM}q!Pz#~9JgOi8QJ|DSu?1-*(kSi1K4#~5?#|rh?sS)(-JQqX*}ciXJ56_H zdw=^s_srbAdqxlvGyrgGet#6T7_|j;95sL%MtM;q86vOxKM$f#puR)Bjv9Zvz9-di zXOTSsZkM83)E9PYBXC<$6(|>lNLVBb&&6y{NByFCp%6+^ALR@NCTse_wqvNmSWI-m z!$%KlHFH2omF!>#%1l3LTZg(s7eof$7*xB)ZQ0h?ejh?Ta9fDv59+u#MokW+1t8Zb zgHv%K(u9G^Lv`lh#f3<6!JVTL3(dCpxHbnbA;kKqQyd1~^Xe0VIaYBSWm6nsr;dFj z4;G-RyL?cYgsN1{L4ZFFNa;8)Rv0fM0C(~Tkit94 zz#~A)59?QjD&pAPSEQ)p8gP|DS{ng)j=2ux)_EzzJ773GmQ_Cic%3JJhC0t2cx>|v zJcVusIB!%F90{+}8hG3QU4KNeKmK%T>mN57NnCZ^56=0?&3@!j>a>B43pi{!u z7JyDj7`6d)qVp^R=%j>UIY6f+3`+qzIc!Y_=+uN^3BYV|o+$vGo-j-Wm<10%A=(Yk^beI{t%ld@yhKjq0iNjqN4XMGgQtbKubPM$JWBz}YA65k%dm*awtC^+f;a-x4+ddbH^7iDWGg&N0n#MW{kA|=8iMUiFYvMoDY@sPC#t$55gn6ykUTPAr`a@!(;np824>2xJthS z*ZdmT`g5-`BuJs`0LVhz+D9NNa3<=6m;cQLaF?tCv8)zcRSh66*Z|vXhG@$I%U~2l z?`Q zykI#*+rQ=z6Jm=Bui-SfpDYLA=|vzGE(dYm=OC8XM&MDo7ux4UF1~0J1+i%aCUpRe zt3L_uNyQ*cE(38Uy03H%I*)*Bh=Lb^Xj3?I^Hnbeq72(EOK^Y93CNp*uAA{5Lc=ky zx=~RKa4{iTm{_>_vSCm?$Ej=i6@=m%@VvAITnigVg{&@!7CDgs908761meDK5azA} z4?=NOH|PdvabgJ&fW2{Mo$Q0CcD8Qc84%{JPYt5EiG{MdLIAeX%T=D7NIP4%Hw}p9 zg)==!2Lbp#j{u_}hMiao9=!VSyx0gHbeCS`;q&vzeq|fs`y&^X-lso(Ls@-706qmA z7u*T5PMo_w3{se1t2`zWeO^hOvTsohG_;>J0wVqVe+n)AbQCx)yh9;w+J6?NF5Lmo zecS@ieAKL8%bVd@+-KT{yI|S}O>pYckUFs;ry9Ow$CD@ztz5K-*D$^{i(_1llhSh^ zEkL$}tsQt5>QA^;QgjgIfBDmcOgi5YDyu?t6vSnbp=1+@6D& z5MJ}B8q;bRlVoxasyhcUF1+)o`&3r0colr}QJ3hcSdLu;9;td>kf@Tcn<@9sIx&=m z;AD;SCh95=&p;$r{Xz3iWCO^MX83AGJ(yH&eTXgv|0=34#-&WAmw{)U7OU9!Wz^!7 zZ%jZFi@JR;>Mhi7S>V7wQ176|FdW2m?&`qa(ScO^CFPR80HucLHOTy%5s*HR0^8)i h0WYBP*#0Ks^FNSabJA*5${_#%002ovPDHLkV1oKhTl@e3 literal 0 HcmV?d00001 diff --git a/examples/now_in_dart_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/examples/now_in_dart_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..a6d6b8609df07bf62e5100a53a01510388bd2b22 GIT binary patch literal 2665 zcmV-v3YPVWP)oFh3q0MFesq&64WThn3$;G69TfjsAv=f2G9}p zgSx99+!YV6qME!>9MD13x)k(+XE7W?_O4LoLb5ND8 zaV{9+P@>42xDfRiYBMSgD$0!vssptcb;&?u9u(LLBKmkZ>RMD=kvD3h`sk6!QYtBa ztlZI#nu$8lJ^q2Z79UTgZe>BU73(Aospiq+?SdMt8lDZ;*?@tyWVZVS_Q7S&*tJaiRlJ z+aSMOmbg3@h5}v;A*c8SbqM3icg-`Cnwl;7Ts%A1RkNIp+Txl-Ckkvg4oxrqGA5ewEgYqwtECD<_3Egu)xGllKt&J8g&+=ac@Jq4-?w6M3b*>w5 z69N3O%=I^6&UL5gZ!}trC7bUj*12xLdkNs~Bz4QdJJ*UDZox2UGR}SNg@lmOvhCc~ z*f_UeXv(=#I#*7>VZx2ObEN~UoGUTl=-@)E;YtCRZ>SVp$p9yG5hEFZ!`wI!spd)n zSk+vK0Vin7FL{7f&6OB%f;SH22dtbcF<|9fi2Fp%q4kxL!b1#l^)8dUwJ zwEf{(wJj@8iYDVnKB`eSU+;ml-t2`@%_)0jDM`+a46xhDbBj2+&Ih>1A>6aky#(-SYyE{R3f#y57wfLs z6w1p~$bp;6!9DX$M+J~S@D6vJAaElETnsX4h9a5tvPhC3L@qB~bOzkL@^z0k_hS{T4PF*TDrgdXp+dzsE? z>V|VR035Pl9n5&-RePFdS{7KAr2vPOqR9=M$vXA1Yy5>w;EsF`;OK{2pkn-kpp9Pw z)r;5JfJKKaT$4qCb{TaXHjb$QA{y0EYy*+b1XI;6Ah- zw13P)xT`>~eFoJC!>{2XL(a_#upp3gaR1#5+L(Jmzp4TBnx{~WHedpJ1ch8JFk~Sw z>F+gN+i+VD?gMXwcIhn8rz`>e>J^TI3E-MW>f}6R-pL}>WMOa0k#jN+`RyUVUC;#D zg|~oS^$6%wpF{^Qr+}X>0PKcr3Fc&>Z>uv@C);pwDs@2bZWhYP!rvGx?_|q{d`t<*XEb#=aOb=N+L@CVBGqImZf&+a zCQEa3$~@#kC);pasdG=f6tuIi0PO-y&tvX%>Mv=oY3U$nD zJ#gMegnQ46pq+3r=;zmgcG+zRc9D~c>z+jo9&D+`E6$LmyFqlmCYw;-Zooma{sR@~ z)_^|YL1&&@|GXo*pivH7k!msl+$Sew3%XJnxajt0K%3M6Bd&YFNy9}tWG^aovK2eX z1aL1%7;KRDrA@eG-Wr6w+;*H_VD~qLiVI`{_;>o)k`{8xa3EJT1O_>#iy_?va0eR? zDV=N%;Zjb%Z2s$@O>w@iqt!I}tLjGk!=p`D23I}N4Be@$(|iSA zf3Ih7b<{zqpDB4WF_5X1(peKe+rASze%u8eKLn#KKXt;UZ+Adf$_TO+vTqshLLJ5c z52HucO=lrNVae5XWOLm!V@n-ObU11!b+DN<$RuU+YsrBq*lYT;?AwJpmNKniF0Q1< zJCo>Q$=v$@&y=sj6{r!Y&y&`0$-I}S!H_~pI&2H8Z1C|BX4VgZ^-! zje3-;x0PBD!M`v*J_)rL^+$<1VJhH*2Fi~aA7s&@_rUHYJ9zD=M%4AFQ`}k8OC$9s XsPq=LnkwKG00000NkvXXu0mjfhAk5^ literal 0 HcmV?d00001 diff --git a/examples/now_in_dart_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/examples/now_in_dart_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..a6d6b8609df07bf62e5100a53a01510388bd2b22 GIT binary patch literal 2665 zcmV-v3YPVWP)oFh3q0MFesq&64WThn3$;G69TfjsAv=f2G9}p zgSx99+!YV6qME!>9MD13x)k(+XE7W?_O4LoLb5ND8 zaV{9+P@>42xDfRiYBMSgD$0!vssptcb;&?u9u(LLBKmkZ>RMD=kvD3h`sk6!QYtBa ztlZI#nu$8lJ^q2Z79UTgZe>BU73(Aospiq+?SdMt8lDZ;*?@tyWVZVS_Q7S&*tJaiRlJ z+aSMOmbg3@h5}v;A*c8SbqM3icg-`Cnwl;7Ts%A1RkNIp+Txl-Ckkvg4oxrqGA5ewEgYqwtECD<_3Egu)xGllKt&J8g&+=ac@Jq4-?w6M3b*>w5 z69N3O%=I^6&UL5gZ!}trC7bUj*12xLdkNs~Bz4QdJJ*UDZox2UGR}SNg@lmOvhCc~ z*f_UeXv(=#I#*7>VZx2ObEN~UoGUTl=-@)E;YtCRZ>SVp$p9yG5hEFZ!`wI!spd)n zSk+vK0Vin7FL{7f&6OB%f;SH22dtbcF<|9fi2Fp%q4kxL!b1#l^)8dUwJ zwEf{(wJj@8iYDVnKB`eSU+;ml-t2`@%_)0jDM`+a46xhDbBj2+&Ih>1A>6aky#(-SYyE{R3f#y57wfLs z6w1p~$bp;6!9DX$M+J~S@D6vJAaElETnsX4h9a5tvPhC3L@qB~bOzkL@^z0k_hS{T4PF*TDrgdXp+dzsE? z>V|VR035Pl9n5&-RePFdS{7KAr2vPOqR9=M$vXA1Yy5>w;EsF`;OK{2pkn-kpp9Pw z)r;5JfJKKaT$4qCb{TaXHjb$QA{y0EYy*+b1XI;6Ah- zw13P)xT`>~eFoJC!>{2XL(a_#upp3gaR1#5+L(Jmzp4TBnx{~WHedpJ1ch8JFk~Sw z>F+gN+i+VD?gMXwcIhn8rz`>e>J^TI3E-MW>f}6R-pL}>WMOa0k#jN+`RyUVUC;#D zg|~oS^$6%wpF{^Qr+}X>0PKcr3Fc&>Z>uv@C);pwDs@2bZWhYP!rvGx?_|q{d`t<*XEb#=aOb=N+L@CVBGqImZf&+a zCQEa3$~@#kC);pasdG=f6tuIi0PO-y&tvX%>Mv=oY3U$nD zJ#gMegnQ46pq+3r=;zmgcG+zRc9D~c>z+jo9&D+`E6$LmyFqlmCYw;-Zooma{sR@~ z)_^|YL1&&@|GXo*pivH7k!msl+$Sew3%XJnxajt0K%3M6Bd&YFNy9}tWG^aovK2eX z1aL1%7;KRDrA@eG-Wr6w+;*H_VD~qLiVI`{_;>o)k`{8xa3EJT1O_>#iy_?va0eR? zDV=N%;Zjb%Z2s$@O>w@iqt!I}tLjGk!=p`D23I}N4Be@$(|iSA zf3Ih7b<{zqpDB4WF_5X1(peKe+rASze%u8eKLn#KKXt;UZ+Adf$_TO+vTqshLLJ5c z52HucO=lrNVae5XWOLm!V@n-ObU11!b+DN<$RuU+YsrBq*lYT;?AwJpmNKniF0Q1< zJCo>Q$=v$@&y=sj6{r!Y&y&`0$-I}S!H_~pI&2H8Z1C|BX4VgZ^-! zje3-;x0PBD!M`v*J_)rL^+$<1VJhH*2Fi~aA7s&@_rUHYJ9zD=M%4AFQ`}k8OC$9s XsPq=LnkwKG00000NkvXXu0mjfhAk5^ literal 0 HcmV?d00001 diff --git a/examples/now_in_dart_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/examples/now_in_dart_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..75b2d164a5a98e212cca15ea7bf2ab5de5108680 GIT binary patch literal 3831 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 literal 0 HcmV?d00001 diff --git a/examples/now_in_dart_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/examples/now_in_dart_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..c4df70d39da7941ef3f6dcb7f06a192d8dcb308d GIT binary patch literal 1888 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 iW8Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/examples/now_in_dart_flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/examples/now_in_dart_flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/examples/now_in_dart_flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/examples/now_in_dart_flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/examples/now_in_dart_flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/examples/now_in_dart_flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/examples/now_in_dart_flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/examples/now_in_dart_flutter/ios/Runner/Base.lproj/LaunchScreen.storyboard b/examples/now_in_dart_flutter/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/examples/now_in_dart_flutter/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/now_in_dart_flutter/ios/Runner/Base.lproj/Main.storyboard b/examples/now_in_dart_flutter/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/examples/now_in_dart_flutter/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/now_in_dart_flutter/ios/Runner/Info.plist b/examples/now_in_dart_flutter/ios/Runner/Info.plist new file mode 100644 index 0000000..a239fa0 --- /dev/null +++ b/examples/now_in_dart_flutter/ios/Runner/Info.plist @@ -0,0 +1,51 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Now In Dart Flutter + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + now_in_dart_flutter + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/examples/now_in_dart_flutter/ios/Runner/Runner-Bridging-Header.h b/examples/now_in_dart_flutter/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/examples/now_in_dart_flutter/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/examples/now_in_dart_flutter/lib/app/app.dart b/examples/now_in_dart_flutter/lib/app/app.dart new file mode 100644 index 0000000..d50de29 --- /dev/null +++ b/examples/now_in_dart_flutter/lib/app/app.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:now_in_dart_flutter/features/detail/dart_detail/data/dart_detail_repository.dart'; +import 'package:now_in_dart_flutter/features/detail/flutter_detail/data/flutter_detail_repository.dart'; +import 'package:now_in_dart_flutter/features/home/home.dart'; + +class App extends StatelessWidget { + const App({ + required DartDetailRepository dartDetailRepository, + required FlutterDetailRepository flutterDetailRepository, + super.key, + }) : _dartDetailRepository = dartDetailRepository, + _flutterDetailRepository = flutterDetailRepository; + + final DartDetailRepository _dartDetailRepository; + final FlutterDetailRepository _flutterDetailRepository; + + @override + Widget build(BuildContext context) { + return MultiRepositoryProvider( + providers: [ + RepositoryProvider.value(value: _dartDetailRepository), + RepositoryProvider.value(value: _flutterDetailRepository), + ], + child: MaterialApp( + darkTheme: ThemeData.dark(), + home: const HomePage(), + ), + ); + } +} diff --git a/examples/now_in_dart_flutter/lib/app/app_bloc_observer.dart b/examples/now_in_dart_flutter/lib/app/app_bloc_observer.dart new file mode 100644 index 0000000..15b4508 --- /dev/null +++ b/examples/now_in_dart_flutter/lib/app/app_bloc_observer.dart @@ -0,0 +1,17 @@ +import 'dart:developer'; + +import 'package:flutter_bloc/flutter_bloc.dart'; + +class AppBlocObserver extends BlocObserver { + @override + void onChange(BlocBase bloc, Change change) { + super.onChange(bloc, change); + log('onChange(${bloc.runtimeType}, change)'); + } + + @override + void onError(BlocBase bloc, Object error, StackTrace stackTrace) { + log('onError(${bloc.runtimeType}, error, stackTrace)'); + super.onError(bloc, error, stackTrace); + } +} diff --git a/examples/now_in_dart_flutter/lib/bootstrap.dart b/examples/now_in_dart_flutter/lib/bootstrap.dart new file mode 100644 index 0000000..c6bcbf4 --- /dev/null +++ b/examples/now_in_dart_flutter/lib/bootstrap.dart @@ -0,0 +1,43 @@ +import 'dart:async'; +import 'dart:developer' as dev show log; + +import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:now_in_dart_flutter/app/app_bloc_observer.dart'; +import 'package:now_in_dart_flutter/core/data/data.dart'; + +typedef _BootstrapBuilder = Widget Function(Dio dio); + +void bootstrap(_BootstrapBuilder builder) { + Bloc.observer = AppBlocObserver(); + + FlutterError.onError = (details) { + dev.log( + details.exceptionAsString(), + stackTrace: details.stack, + ); + }; + + runZonedGuarded( + () async { + WidgetsFlutterBinding.ensureInitialized(); + await IsarDatabase().init(); + final dio = Dio() + ..options = BaseOptions( + baseUrl: 'https://api.github.com/', + headers: {'Accept': 'application/vnd.github.html+json'}, + responseType: ResponseType.plain, + validateStatus: (status) { + return status != null && status >= 200 && status < 400; + }, + ); + + runApp(builder(dio)); + }, + (error, stackTrace) => dev.log( + error.toString(), + stackTrace: stackTrace, + ), + ); +} diff --git a/examples/now_in_dart_flutter/lib/core/data/data.dart b/examples/now_in_dart_flutter/lib/core/data/data.dart new file mode 100644 index 0000000..cf87fdd --- /dev/null +++ b/examples/now_in_dart_flutter/lib/core/data/data.dart @@ -0,0 +1,5 @@ +export 'dio_extension.dart'; +export 'github_header.dart'; +export 'github_header_cache.dart'; +export 'isar_database.dart'; +export 'remote_response.dart'; diff --git a/examples/now_in_dart_flutter/lib/core/data/dio_extension.dart b/examples/now_in_dart_flutter/lib/core/data/dio_extension.dart new file mode 100644 index 0000000..b666bc6 --- /dev/null +++ b/examples/now_in_dart_flutter/lib/core/data/dio_extension.dart @@ -0,0 +1,9 @@ +import 'dart:io'; + +import 'package:dio/dio.dart'; + +extension DioErrorX on DioException { + bool get isNoConnectionError { + return error is SocketException; + } +} diff --git a/examples/now_in_dart_flutter/lib/core/data/github_header.dart b/examples/now_in_dart_flutter/lib/core/data/github_header.dart new file mode 100644 index 0000000..66143dd --- /dev/null +++ b/examples/now_in_dart_flutter/lib/core/data/github_header.dart @@ -0,0 +1,37 @@ +import 'package:dio/dio.dart'; +import 'package:equatable/equatable.dart'; +import 'package:isar/isar.dart'; + +part 'github_header.g.dart'; + +@Collection(inheritance: false) +class GithubHeader extends Equatable { + const GithubHeader({ + required this.id, + required this.eTag, + required this.path, + }); + + factory GithubHeader.parse(int id, Response response, String path) { + return GithubHeader( + id: id, + eTag: response.headers.map['ETag']![0], + path: path, + ); + } + + final String eTag; + + // We are only making `path` a property of this class because we want to make + // a query using path value. If Isar supports key-value storage mechanism too + // in the future, then the `path` property can be removed from this file. + + @Index(unique: true) + final String path; + + final Id id; + + @ignore + @override + List get props => [id, eTag, path]; +} diff --git a/examples/now_in_dart_flutter/lib/core/data/github_header_cache.dart b/examples/now_in_dart_flutter/lib/core/data/github_header_cache.dart new file mode 100644 index 0000000..ef85cf2 --- /dev/null +++ b/examples/now_in_dart_flutter/lib/core/data/github_header_cache.dart @@ -0,0 +1,38 @@ +import 'package:fpdart/fpdart.dart'; +import 'package:isar/isar.dart'; +import 'package:now_in_dart_flutter/core/data/github_header.dart'; +import 'package:now_in_dart_flutter/core/data/isar_database.dart'; + +abstract class HeaderCache { + Task saveHeader(GithubHeader header); + Task getHeader(String path); +} + +class GithubHeaderCache implements HeaderCache { + GithubHeaderCache({ + IsarDatabase? isarDb, + }) : _isarDb = isarDb ?? IsarDatabase(); + + final IsarDatabase _isarDb; + + Isar get _isar => _isarDb.instance; + + IsarCollection get _githubHeaders => _isar.githubHeaders; + + @override + Task saveHeader(GithubHeader header) { + final txn = _isar.writeTxn( + () async { + await _githubHeaders.put(header); + return unit; + }, + silent: true, + ); + return Task(() => txn); + } + + @override + Task getHeader(String path) { + return Task(() => _githubHeaders.getByPath(path)); + } +} diff --git a/examples/now_in_dart_flutter/lib/core/data/id.dart b/examples/now_in_dart_flutter/lib/core/data/id.dart new file mode 100644 index 0000000..2eea5d4 --- /dev/null +++ b/examples/now_in_dart_flutter/lib/core/data/id.dart @@ -0,0 +1,5 @@ +abstract class EntityId { + static const dartChangelogDetail = 1; + static const flutterWhatsNewDetail = 2; + static const flutterReleaseNotesDetail = 3; +} diff --git a/examples/now_in_dart_flutter/lib/core/data/isar_database.dart b/examples/now_in_dart_flutter/lib/core/data/isar_database.dart new file mode 100644 index 0000000..4ba5ae5 --- /dev/null +++ b/examples/now_in_dart_flutter/lib/core/data/isar_database.dart @@ -0,0 +1,27 @@ +import 'package:isar/isar.dart'; +import 'package:now_in_dart_flutter/core/data/github_header.dart'; +import 'package:now_in_dart_flutter/features/detail/core/data/detail_dto.dart'; +import 'package:path_provider/path_provider.dart'; + +class IsarDatabase { + factory IsarDatabase() => _isarDatabase; + IsarDatabase._internal(); + + static final _isarDatabase = IsarDatabase._internal(); + + late Isar _instance; + + Isar get instance => _instance; + + /// Initializes the isar database. + /// + /// This method needs to be called before accessing any isar-specific APIs. + Future init() async { + final dir = await getApplicationDocumentsDirectory(); + if (Isar.instanceNames.isNotEmpty) return; + _instance = await Isar.open( + [GithubHeaderSchema, DetailDTOSchema], + directory: dir.path, + ); + } +} diff --git a/examples/now_in_dart_flutter/lib/core/data/remote_response.dart b/examples/now_in_dart_flutter/lib/core/data/remote_response.dart new file mode 100644 index 0000000..1033c97 --- /dev/null +++ b/examples/now_in_dart_flutter/lib/core/data/remote_response.dart @@ -0,0 +1,31 @@ +sealed class RemoteResponse { + const RemoteResponse(); +} + +class NoConnectionRemoteResponse extends RemoteResponse { + const NoConnectionRemoteResponse(); +} + +class UnModifiedRemoteResponse extends RemoteResponse { + const UnModifiedRemoteResponse(); +} + +class ModifiedRemoteResponse extends RemoteResponse { + const ModifiedRemoteResponse(this.data); + + final T data; +} + +extension RemoteResponseExt on RemoteResponse { + A when({ + required A Function() noConnection, + required A Function() unmodifed, + required A Function(T data) modified, + }) { + return switch (this) { + NoConnectionRemoteResponse() => noConnection(), + UnModifiedRemoteResponse() => unmodifed(), + ModifiedRemoteResponse(:final data) => modified(data), + }; + } +} diff --git a/examples/now_in_dart_flutter/lib/core/domain/failure.dart b/examples/now_in_dart_flutter/lib/core/domain/failure.dart new file mode 100644 index 0000000..ba47e75 --- /dev/null +++ b/examples/now_in_dart_flutter/lib/core/domain/failure.dart @@ -0,0 +1,29 @@ +import 'package:equatable/equatable.dart'; + +sealed class Failure extends Equatable { + const Failure(this.message, {this.code, this.stackTrace, this.errorObject}); + + final String message; + final int? code; + final Object? errorObject; + final StackTrace? stackTrace; +} + +class ApiFailure extends Failure { + const ApiFailure( + super.message, { + super.code, + super.stackTrace, + super.errorObject, + }); + + @override + List get props => [message, code, stackTrace, errorObject]; +} + +class UriParserFailure extends Failure { + const UriParserFailure(super.message, {super.errorObject, super.stackTrace}); + + @override + List get props => [message, errorObject, stackTrace]; +} diff --git a/examples/now_in_dart_flutter/lib/core/domain/fresh.dart b/examples/now_in_dart_flutter/lib/core/domain/fresh.dart new file mode 100644 index 0000000..5053890 --- /dev/null +++ b/examples/now_in_dart_flutter/lib/core/domain/fresh.dart @@ -0,0 +1,48 @@ +import 'package:equatable/equatable.dart'; + +class Fresh extends Equatable { + const Fresh._({this.entity, this.isFresh}); + + /// Factory for [WhenFresh] + const factory Fresh.yes({ + required T entity, + }) = WhenFresh._; + + /// Factory for [WhenNotFresh] + const factory Fresh.no({ + required T entity, + }) = WhenNotFresh._; + + /// Entity whose freshness is to be checked. + final T? entity; + + /// Determines if the entity is fresh or not. + final bool? isFresh; + + @override + List get props => [entity, isFresh]; +} + +/// Represents that the entity is fresh. +class WhenFresh extends Fresh { + const WhenFresh._({ + required T super.entity, + }) : super._(isFresh: true); + + @override + String toString() { + return 'WhenFresh(entity: $entity, isFresh: true)'; + } +} + +/// Represents that the entity is not fresh. +class WhenNotFresh extends Fresh { + const WhenNotFresh._({ + required T super.entity, + }) : super._(isFresh: false); + + @override + String toString() { + return 'WhenNotFresh(entity: $entity, isFresh: false)'; + } +} diff --git a/examples/now_in_dart_flutter/lib/core/presentation/assets_path.dart b/examples/now_in_dart_flutter/lib/core/presentation/assets_path.dart new file mode 100644 index 0000000..40465e6 --- /dev/null +++ b/examples/now_in_dart_flutter/lib/core/presentation/assets_path.dart @@ -0,0 +1,7 @@ +abstract class AssetsPath { + /// assets/icon_dart.svg.vec + static const dartIcon = 'assets/icon_dart.svg.vec'; + + /// assets/icon_flutter.svg.vec + static const flutterIcon = 'assets/icon_flutter.svg.vec'; +} diff --git a/examples/now_in_dart_flutter/lib/core/presentation/lazy_indexed_stack.dart b/examples/now_in_dart_flutter/lib/core/presentation/lazy_indexed_stack.dart new file mode 100644 index 0000000..315c867 --- /dev/null +++ b/examples/now_in_dart_flutter/lib/core/presentation/lazy_indexed_stack.dart @@ -0,0 +1,63 @@ +import 'package:flutter/material.dart'; + +/// IndexedStack but lazy. +/// +/// Source code credit: [marcossevilla](https://github.com/marcossevilla/lazy_indexed_stack/blob/main/lib/src/flutter_lazy_indexed_stack.dart) +class LazyIndexedStack extends StatefulWidget { + const LazyIndexedStack({ + super.key, + this.index = 0, + this.children = const [], + }); + + final int index; + + final List children; + + @override + State createState() => _LazyIndexedStackState(); +} + +class _LazyIndexedStackState extends State { + late final List _activatedChildren; + + @override + void initState() { + super.initState(); + _activatedChildren = List.generate( + widget.children.length, + (i) => i == widget.index, + ); + } + + @override + void didUpdateWidget(LazyIndexedStack oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.index != widget.index) _activateChild(widget.index); + } + + void _activateChild(int? index) { + if (index == null) return; + + if (!_activatedChildren[index]) _activatedChildren[index] = true; + } + + List get children { + return List.generate( + widget.children.length, + (i) { + return _activatedChildren[i] + ? widget.children[i] + : const SizedBox.shrink(); + }, + ); + } + + @override + Widget build(BuildContext context) { + return IndexedStack( + index: widget.index, + children: children, + ); + } +} diff --git a/examples/now_in_dart_flutter/lib/core/presentation/no_connection_toast.dart b/examples/now_in_dart_flutter/lib/core/presentation/no_connection_toast.dart new file mode 100644 index 0000000..56cba06 --- /dev/null +++ b/examples/now_in_dart_flutter/lib/core/presentation/no_connection_toast.dart @@ -0,0 +1,30 @@ +import 'package:flash/flash.dart'; +import 'package:flutter/material.dart'; + +Future showNoConnectionToast( + String message, + BuildContext context, +) async { + await showFlash( + context: context, + duration: const Duration(seconds: 2), + builder: (context, controller) { + return FlashBar( + controller: controller, + backgroundColor: Colors.black.withOpacity(0.7), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), + margin: const EdgeInsets.all(8), + content: Padding( + padding: const EdgeInsets.all(8), + child: Text( + message, + style: const TextStyle( + color: Colors.white, + fontSize: 16, + ), + ), + ), + ); + }, + ); +} diff --git a/examples/now_in_dart_flutter/lib/core/presentation/no_results_display.dart b/examples/now_in_dart_flutter/lib/core/presentation/no_results_display.dart new file mode 100644 index 0000000..342ee30 --- /dev/null +++ b/examples/now_in_dart_flutter/lib/core/presentation/no_results_display.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; + +class NoResultsDisplay extends StatelessWidget { + const NoResultsDisplay({ + required this.message, + super.key, + }); + + final String message; + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(8), + alignment: Alignment.center, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon( + Icons.hourglass_empty, + size: 96, + ), + Text( + message, + style: Theme.of(context).textTheme.headlineSmall, + textAlign: TextAlign.center, + ), + ], + ), + ); + } +} diff --git a/examples/now_in_dart_flutter/lib/core/presentation/responsive.dart b/examples/now_in_dart_flutter/lib/core/presentation/responsive.dart new file mode 100644 index 0000000..31ea69c --- /dev/null +++ b/examples/now_in_dart_flutter/lib/core/presentation/responsive.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; + +class Responsive extends StatelessWidget { + const Responsive({ + required this.mobile, + required this.tabletOrDesktop, + super.key, + }); + + final Widget mobile, tabletOrDesktop; + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (context, constraints) { + if (constraints.maxWidth < 650) { + return mobile; + } else { + return tabletOrDesktop; + } + }, + ); + } +} diff --git a/examples/now_in_dart_flutter/lib/features/detail/core/data/detail_dto.dart b/examples/now_in_dart_flutter/lib/features/detail/core/data/detail_dto.dart new file mode 100644 index 0000000..9bde4eb --- /dev/null +++ b/examples/now_in_dart_flutter/lib/features/detail/core/data/detail_dto.dart @@ -0,0 +1,54 @@ +import 'package:equatable/equatable.dart'; +import 'package:isar/isar.dart'; +import 'package:now_in_dart_flutter/features/detail/core/domain/detail.dart'; + +part 'detail_dto.g.dart'; + +@Collection(inheritance: false) +class DetailDTO extends Equatable { + const DetailDTO({required this.id, required this.html}); + + /// The parser that parses the received html data. + /// + /// The markdowns in the flutter's github repo has some data in the format + /// `%7B%7Bsite.url%7D%7D` which actually is `{{site.url}}`. But WebView will + /// not be able to take us to relevant web page if `%7B%7Bsite.url%7D%7D` + /// isn't parsed. So, we need to convert `%7B%7Bsite.url%7D%7D` to + /// `https://docs.flutter.dev`. + /// + /// The mappings will have to be done in accordance to [_mappings]. + factory DetailDTO.parseHtml(int id, String html) { + final parsedHtml = _mappings.entries.fold( + html, + (str, map) => str.replaceAll(map.key, map.value), + ); + return DetailDTO(id: id, html: parsedHtml); + } + + final Id id; + final String html; + + Detail toDomain() => Detail(html: html); + + static const _mappings = { + '%7B%7Bsite.url%7D%7D': 'https://docs.flutter.dev', + '%7B%7Bsite.medium%7D%7D': 'https://medium.com', + '%7B%7Bsite.github%7D%7D': 'https://github.com', + '%7B%7Bsite.groups%7D%7D': 'https://groups.google.com', + '%7B%7Bsite.dart-site%7D%7D': 'https://dart.dev', + '%7B%7Bsite.main-url%7D%7D': 'https://flutter.dev', + '%7B%7Bsite.codelabs%7D%7D': 'https://codelabs.developers.google.com', + '%7B%7Bsite.youtube-site%7D%7D': 'https://youtube.com', + '%7B%7Bsite.flutter-medium%7D%7D': 'https://medium.com/flutter', + '%7B%7Bsite.repo.this%7D%7D': 'https://github.com/flutter/website', + '%7B%7Bsite.firebase%7D%7D': 'https://firebase.google.com', + '%7B%7Bsite.google-blog%7D%7D': 'https://developers.googleblog.com', + '%7B%7Bsite.pub%7D%7D': 'https://pub.dev', + '%7B%7Bsite.api%7D%7D': 'https://api.flutter.dev', + '%7B%7Bsite.repo.flutter%7D%7D': 'https://github.com/flutter/flutter', + }; + + @ignore + @override + List get props => [id, html]; +} diff --git a/examples/now_in_dart_flutter/lib/features/detail/core/data/detail_local_service.dart b/examples/now_in_dart_flutter/lib/features/detail/core/data/detail_local_service.dart new file mode 100644 index 0000000..5806505 --- /dev/null +++ b/examples/now_in_dart_flutter/lib/features/detail/core/data/detail_local_service.dart @@ -0,0 +1,33 @@ +import 'package:fpdart/fpdart.dart'; +import 'package:isar/isar.dart'; +import 'package:meta/meta.dart'; +import 'package:now_in_dart_flutter/core/data/data.dart'; +import 'package:now_in_dart_flutter/features/detail/core/data/detail_dto.dart'; + +abstract class DetailLocalService { + DetailLocalService({ + IsarDatabase? isarDb, + }) : _isarDb = isarDb ?? IsarDatabase(); + + final IsarDatabase _isarDb; + + Isar get _isar => _isarDb.instance; + + @protected + Task upsertDetail(DetailDTO detailDTO) { + final txn = _isar.writeTxn( + () async { + await _isar.detailDTOs.put(detailDTO); + return unit; + }, + silent: true, + ); + + return Task(() => txn); + } + + @protected + Task getDetail(int id) { + return Task(() => _isar.detailDTOs.get(id)); + } +} diff --git a/examples/now_in_dart_flutter/lib/features/detail/core/data/detail_remote_service.dart b/examples/now_in_dart_flutter/lib/features/detail/core/data/detail_remote_service.dart new file mode 100644 index 0000000..3feb2ff --- /dev/null +++ b/examples/now_in_dart_flutter/lib/features/detail/core/data/detail_remote_service.dart @@ -0,0 +1,96 @@ +import 'package:dio/dio.dart'; +import 'package:fpdart/fpdart.dart'; +import 'package:meta/meta.dart'; +import 'package:now_in_dart_flutter/core/data/data.dart'; +import 'package:now_in_dart_flutter/core/domain/failure.dart'; + +typedef _FailureOrRemoteResponse = TaskEither>; + +abstract class DetailRemoteService { + DetailRemoteService({ + Dio? dio, + HeaderCache? headerCache, + }) : _dio = dio ?? Dio(), + _headerCache = headerCache ?? GithubHeaderCache(); + + final Dio _dio; + final HeaderCache _headerCache; + + @protected + _FailureOrRemoteResponse getDetail( + int id, + String fullPathToMarkdownFile, + ) { + return TaskEither.Do( + (_) async { + final requestUri = await _( + _uriParser(fullPathToMarkdownFile).toTaskEither(), + ); + + final cachedHeader = await _( + _headerCache.getHeader(fullPathToMarkdownFile).toTaskEither(), + ); + + return _( + TaskEither>.tryCatch( + () => _dio.getUri( + requestUri, + options: Options( + headers: { + 'If-None-Match': cachedHeader?.eTag ?? '', + }, + ), + ), + (e, stackTrace) { + return ApiFailure( + 'Error on network request', + errorObject: e, + stackTrace: stackTrace, + ); + }, + ).flatMap( + (response) { + return TaskEither>( + () async { + if (response.statusCode == 200) { + final header = GithubHeader.parse( + id, + response, + fullPathToMarkdownFile, + ); + + await _(_headerCache.saveHeader(header).toTaskEither()); + + final html = response.data!; + return right(ModifiedRemoteResponse(html)); + } + + return right(const UnModifiedRemoteResponse()); + }, + ); + }, + ).orElse( + (failure) { + final error = failure.errorObject; + if (error is DioException && error.isNoConnectionError) { + return TaskEither.right(const NoConnectionRemoteResponse()); + } + return TaskEither.left(failure); + }, + ), + ); + }, + ); + } +} + +IOEither _uriParser(String uri) { + return IOEither.tryCatch( + () => Uri.parse(uri), + (e, stackTrace) => UriParserFailure( + 'Invalid Uri string', + errorObject: e, + stackTrace: stackTrace, + ), + ); +} diff --git a/examples/now_in_dart_flutter/lib/features/detail/core/domain/detail.dart b/examples/now_in_dart_flutter/lib/features/detail/core/domain/detail.dart new file mode 100644 index 0000000..8c42391 --- /dev/null +++ b/examples/now_in_dart_flutter/lib/features/detail/core/domain/detail.dart @@ -0,0 +1,19 @@ +import 'package:equatable/equatable.dart'; + +class Detail extends Equatable { + const Detail({required this.html}); + + final String html; + + /// An empty detail used to represent null detail. + + // This pattern helps us to work with concrete domain level entities and + // avoid nulls. + static const empty = Detail(html: ''); + + /// Convenience getter to determine whether the current detail is empty. + bool get isEmpty => this == Detail.empty; + + @override + List get props => [html]; +} diff --git a/examples/now_in_dart_flutter/lib/features/detail/core/presentation/widget/detail_webview.dart b/examples/now_in_dart_flutter/lib/features/detail/core/presentation/widget/detail_webview.dart new file mode 100644 index 0000000..e255054 --- /dev/null +++ b/examples/now_in_dart_flutter/lib/features/detail/core/presentation/widget/detail_webview.dart @@ -0,0 +1,61 @@ +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:url_launcher/url_launcher.dart'; +import 'package:webview_flutter/webview_flutter.dart'; + +class DetailWebView extends StatelessWidget { + const DetailWebView({ + required String html, + super.key, + }) : _html = html; + + final String _html; + + @override + Widget build(BuildContext context) { + final url = Uri.dataFromString( + ''' + + + + + $_html + + $_css + ''', + mimeType: 'text/html', + encoding: utf8, + ).toString(); + return WebViewWidget( + controller: WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate( + NavigationDelegate( + onNavigationRequest: (navReq) { + if (navReq.url == url) { + return NavigationDecision.navigate; + } + launchUrl(Uri.parse(navReq.url)); + return NavigationDecision.prevent; + }, + ), + ) + ..loadRequest(Uri.parse(url)) + ..enableZoom(false), + gestureRecognizers: const { + Factory( + VerticalDragGestureRecognizer.new, + ), + }, + ); + } + + static const _css = """ + + """; +} diff --git a/examples/now_in_dart_flutter/lib/features/detail/dart_detail/application/dart_detail_bloc.dart b/examples/now_in_dart_flutter/lib/features/detail/dart_detail/application/dart_detail_bloc.dart new file mode 100644 index 0000000..813ba3b --- /dev/null +++ b/examples/now_in_dart_flutter/lib/features/detail/dart_detail/application/dart_detail_bloc.dart @@ -0,0 +1,63 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:fpdart/fpdart.dart'; +import 'package:now_in_dart_flutter/core/domain/failure.dart'; +import 'package:now_in_dart_flutter/core/domain/fresh.dart'; +import 'package:now_in_dart_flutter/features/detail/core/domain/detail.dart'; +import 'package:now_in_dart_flutter/features/detail/dart_detail/data/dart_detail_repository.dart'; + +part 'dart_detail_event.dart'; +part 'dart_detail_state.dart'; + +class DartDetailBloc extends Bloc { + DartDetailBloc({ + required DartDetailRepository repository, + }) : _repository = repository, + super(const DartDetailState()) { + on( + (event, emit) async { + await event.when( + changelogDetailRequested: (id) { + return _onDartChangelogDetailRequested(emit, id); + }, + ); + }, + ); + } + + final DartDetailRepository _repository; + + Future _onDartChangelogDetailRequested( + Emitter emit, + int id, + ) async { + emit(state.copyWith(status: () => DartDetailStatus.loading)); + final failureOrSuccessDetail = await _repository.getDartDetail(id).run(); + return failureOrSuccessDetail.match( + (failure) { + emit( + state.copyWith( + status: () => DartDetailStatus.failure, + failureMessage: () { + return switch (failure) { + ApiFailure() => failure.message, + UriParserFailure() => failure.message, + }; + }, + ), + ); + return unit; + }, + (detail) { + emit( + state.copyWith( + status: () => DartDetailStatus.success, + detail: () => detail, + failureMessage: () => null, + ), + ); + return unit; + }, + ); + } +} diff --git a/examples/now_in_dart_flutter/lib/features/detail/dart_detail/application/dart_detail_event.dart b/examples/now_in_dart_flutter/lib/features/detail/dart_detail/application/dart_detail_event.dart new file mode 100644 index 0000000..1f490f3 --- /dev/null +++ b/examples/now_in_dart_flutter/lib/features/detail/dart_detail/application/dart_detail_event.dart @@ -0,0 +1,19 @@ +part of 'dart_detail_bloc.dart'; + +sealed class DartDetailEvent { + const DartDetailEvent(); +} + +class DartChangelogDetailRequested extends DartDetailEvent { + const DartChangelogDetailRequested(this.id); + + final int id; +} + +extension DartDetailEventExt on DartDetailEvent { + A when({required A Function(int) changelogDetailRequested}) { + return switch (this) { + DartChangelogDetailRequested(:final id) => changelogDetailRequested(id), + }; + } +} diff --git a/examples/now_in_dart_flutter/lib/features/detail/dart_detail/application/dart_detail_state.dart b/examples/now_in_dart_flutter/lib/features/detail/dart_detail/application/dart_detail_state.dart new file mode 100644 index 0000000..49b4084 --- /dev/null +++ b/examples/now_in_dart_flutter/lib/features/detail/dart_detail/application/dart_detail_state.dart @@ -0,0 +1,31 @@ +part of 'dart_detail_bloc.dart'; + +enum DartDetailStatus { initial, loading, success, failure } + +class DartDetailState extends Equatable { + const DartDetailState({ + this.status = DartDetailStatus.initial, + this.detail = const Fresh.yes(entity: Detail.empty), + this.failureMessage, + }); + + final DartDetailStatus status; + final Fresh detail; + final String? failureMessage; + + DartDetailState copyWith({ + DartDetailStatus Function()? status, + Fresh Function()? detail, + String? Function()? failureMessage, + }) { + return DartDetailState( + status: status != null ? status() : this.status, + detail: detail != null ? detail() : this.detail, + failureMessage: + failureMessage != null ? failureMessage() : this.failureMessage, + ); + } + + @override + List get props => [status, detail, failureMessage]; +} diff --git a/examples/now_in_dart_flutter/lib/features/detail/dart_detail/data/dart_detail_local_service.dart b/examples/now_in_dart_flutter/lib/features/detail/dart_detail/data/dart_detail_local_service.dart new file mode 100644 index 0000000..d5f5b7a --- /dev/null +++ b/examples/now_in_dart_flutter/lib/features/detail/dart_detail/data/dart_detail_local_service.dart @@ -0,0 +1,13 @@ +import 'package:fpdart/fpdart.dart'; +import 'package:now_in_dart_flutter/features/detail/core/data/detail_dto.dart'; +import 'package:now_in_dart_flutter/features/detail/core/data/detail_local_service.dart'; + +class DartDetailLocalService extends DetailLocalService { + DartDetailLocalService({super.isarDb}); + + Task upsertDartDetail(DetailDTO detailDTO) { + return super.upsertDetail(detailDTO); + } + + Task getDartDetail(int id) => super.getDetail(id); +} diff --git a/examples/now_in_dart_flutter/lib/features/detail/dart_detail/data/dart_detail_remote_service.dart b/examples/now_in_dart_flutter/lib/features/detail/dart_detail/data/dart_detail_remote_service.dart new file mode 100644 index 0000000..0a10cc1 --- /dev/null +++ b/examples/now_in_dart_flutter/lib/features/detail/dart_detail/data/dart_detail_remote_service.dart @@ -0,0 +1,18 @@ +import 'package:fpdart/fpdart.dart'; +import 'package:now_in_dart_flutter/core/data/remote_response.dart'; +import 'package:now_in_dart_flutter/core/domain/failure.dart'; +import 'package:now_in_dart_flutter/features/detail/core/data/detail_remote_service.dart'; + +typedef _DartDetail = TaskEither>; + +class DartDetailRemoteService extends DetailRemoteService { + DartDetailRemoteService({ + super.dio, + super.headerCache, + }); + + _DartDetail getDartChangelogDetail(int id) { + const fullPathToMarkdownFile = 'repos/dart-lang/sdk/contents/CHANGELOG.md'; + return super.getDetail(id, fullPathToMarkdownFile); + } +} diff --git a/examples/now_in_dart_flutter/lib/features/detail/dart_detail/data/dart_detail_repository.dart b/examples/now_in_dart_flutter/lib/features/detail/dart_detail/data/dart_detail_repository.dart new file mode 100644 index 0000000..3632646 --- /dev/null +++ b/examples/now_in_dart_flutter/lib/features/detail/dart_detail/data/dart_detail_repository.dart @@ -0,0 +1,49 @@ +import 'package:fpdart/fpdart.dart'; +import 'package:now_in_dart_flutter/core/data/data.dart'; +import 'package:now_in_dart_flutter/core/domain/failure.dart'; +import 'package:now_in_dart_flutter/core/domain/fresh.dart'; +import 'package:now_in_dart_flutter/features/detail/core/data/detail_dto.dart'; +import 'package:now_in_dart_flutter/features/detail/core/domain/detail.dart'; +import 'package:now_in_dart_flutter/features/detail/dart_detail/data/dart_detail_local_service.dart'; +import 'package:now_in_dart_flutter/features/detail/dart_detail/data/dart_detail_remote_service.dart'; + +typedef _DartDetailOrFailure = TaskEither>; + +class DartDetailRepository { + DartDetailRepository({ + DartDetailLocalService? localService, + DartDetailRemoteService? remoteService, + }) : _localService = localService ?? DartDetailLocalService(), + _remoteService = remoteService ?? DartDetailRemoteService(); + + final DartDetailLocalService _localService; + final DartDetailRemoteService _remoteService; + + _DartDetailOrFailure getDartDetail(int id) { + return TaskEither.Do( + (_) async { + final remoteResponse = await _( + _remoteService.getDartChangelogDetail(id), + ); + + return remoteResponse.when( + noConnection: () async { + final dto = await _(_localService.getDartDetail(id).toTaskEither()); + return Fresh.no(entity: dto?.toDomain() ?? Detail.empty); + }, + unmodifed: () async { + final cachedData = await _( + _localService.getDartDetail(id).toTaskEither(), + ); + return Fresh.yes(entity: cachedData?.toDomain() ?? Detail.empty); + }, + modified: (data) async { + final dto = DetailDTO.parseHtml(id, data); + await _(_localService.upsertDartDetail(dto).toTaskEither()); + return Fresh.yes(entity: dto.toDomain()); + }, + ); + }, + ); + } +} diff --git a/examples/now_in_dart_flutter/lib/features/detail/dart_detail/presentation/view/dart_changelog_page.dart b/examples/now_in_dart_flutter/lib/features/detail/dart_detail/presentation/view/dart_changelog_page.dart new file mode 100644 index 0000000..2aa2cfa --- /dev/null +++ b/examples/now_in_dart_flutter/lib/features/detail/dart_detail/presentation/view/dart_changelog_page.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:now_in_dart_flutter/core/data/id.dart'; +import 'package:now_in_dart_flutter/core/presentation/no_connection_toast.dart'; +import 'package:now_in_dart_flutter/core/presentation/no_results_display.dart'; +import 'package:now_in_dart_flutter/features/detail/core/presentation/widget/detail_webview.dart'; +import 'package:now_in_dart_flutter/features/detail/dart_detail/application/dart_detail_bloc.dart'; +import 'package:now_in_dart_flutter/features/detail/dart_detail/data/dart_detail_repository.dart'; + +class DartChangelogPage extends StatelessWidget { + const DartChangelogPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Dart')), + body: BlocProvider( + create: (context) { + const id = EntityId.dartChangelogDetail; + return DartDetailBloc( + repository: context.read(), + )..add(const DartChangelogDetailRequested(id)); + }, + child: const DartChangelogView(), + ), + ); + } +} + +class DartChangelogView extends StatefulWidget { + const DartChangelogView({super.key}); + + @override + State createState() => _DartChangelogViewState(); +} + +class _DartChangelogViewState extends State { + bool _hasAlreadyShownNoConnectionToast = false; + + @override + Widget build(BuildContext context) { + return BlocConsumer( + listener: (context, state) { + if (!state.detail.isFresh! && !_hasAlreadyShownNoConnectionToast) { + _hasAlreadyShownNoConnectionToast = true; + showNoConnectionToast('No Internet Connection!!!', context); + } + }, + builder: (context, state) { + switch (state.status) { + case DartDetailStatus.initial: + return const SizedBox.shrink(); + + case DartDetailStatus.loading: + return const Center( + child: CircularProgressIndicator.adaptive(), + ); + + case DartDetailStatus.success: + final receivedDetail = state.detail; + if (receivedDetail.entity!.isEmpty) { + return const NoResultsDisplay( + message: "Sorry. There's nothing to display ☚ī¸", + ); + } + return DetailWebView( + html: receivedDetail.entity!.html, + ); + + case DartDetailStatus.failure: + return NoResultsDisplay(message: state.failureMessage!); + } + }, + ); + } +} diff --git a/examples/now_in_dart_flutter/lib/features/detail/flutter_detail/application/flutter_detail_bloc.dart b/examples/now_in_dart_flutter/lib/features/detail/flutter_detail/application/flutter_detail_bloc.dart new file mode 100644 index 0000000..a033aac --- /dev/null +++ b/examples/now_in_dart_flutter/lib/features/detail/flutter_detail/application/flutter_detail_bloc.dart @@ -0,0 +1,91 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:fpdart/fpdart.dart'; +import 'package:now_in_dart_flutter/core/domain/failure.dart'; +import 'package:now_in_dart_flutter/core/domain/fresh.dart'; +import 'package:now_in_dart_flutter/features/detail/core/domain/detail.dart'; +import 'package:now_in_dart_flutter/features/detail/flutter_detail/data/flutter_detail_repository.dart'; + +part 'flutter_detail_event.dart'; +part 'flutter_detail_state.dart'; + +typedef _DetailFailureOrSuccess = TaskEither>; + +class FlutterDetailBloc extends Bloc { + FlutterDetailBloc({ + required FlutterDetailRepository repository, + }) : _repository = repository, + super(const FlutterDetailState()) { + on( + (event, emit) async { + await event.when( + flutterWhatsNewDetailRequested: (id) { + return _onFlutterWhatsNewDetailRequested(emit, id); + }, + flutterReleaseNotesDetailRequested: (id) { + return _onFlutterReleaseNotesDetailRequested(emit, id); + }, + ); + }, + ); + } + + final FlutterDetailRepository _repository; + + Future _onFlutterWhatsNewDetailRequested( + Emitter emit, + int id, + ) { + return _onFlutterDetailRequested( + _repository.getWhatsNewFlutterDetail, + emit, + id, + ); + } + + Future _onFlutterReleaseNotesDetailRequested( + Emitter emit, + int id, + ) { + return _onFlutterDetailRequested( + _repository.getFlutterReleaseNotesDetail, + emit, + id, + ); + } + + Future _onFlutterDetailRequested( + _DetailFailureOrSuccess Function(int) caller, + Emitter emit, + int id, + ) async { + emit(state.copyWith(status: () => FlutterDetailStatus.loading)); + final failureOrSuccessDetail = await caller(id).run(); + return failureOrSuccessDetail.match( + (failure) { + emit( + state.copyWith( + status: () => FlutterDetailStatus.failure, + failureMessage: () { + return switch (failure) { + ApiFailure() => failure.message, + UriParserFailure() => failure.message, + }; + }, + ), + ); + return unit; + }, + (detail) { + emit( + state.copyWith( + status: () => FlutterDetailStatus.success, + detail: () => detail, + failureMessage: () => null, + ), + ); + return unit; + }, + ); + } +} diff --git a/examples/now_in_dart_flutter/lib/features/detail/flutter_detail/application/flutter_detail_event.dart b/examples/now_in_dart_flutter/lib/features/detail/flutter_detail/application/flutter_detail_event.dart new file mode 100644 index 0000000..6b0d54e --- /dev/null +++ b/examples/now_in_dart_flutter/lib/features/detail/flutter_detail/application/flutter_detail_event.dart @@ -0,0 +1,32 @@ +// ignore_for_file: public_member_api_docs, sort_constructors_first +part of 'flutter_detail_bloc.dart'; + +sealed class FlutterDetailEvent { + const FlutterDetailEvent(); +} + +class FlutterWhatsNewDetailRequested extends FlutterDetailEvent { + const FlutterWhatsNewDetailRequested(this.id); + + final int id; +} + +class FlutterReleaseNotesDetailRequested extends FlutterDetailEvent { + const FlutterReleaseNotesDetailRequested(this.id); + + final int id; +} + +extension FlutterDetailEventExt on FlutterDetailEvent { + A when({ + required A Function(int) flutterWhatsNewDetailRequested, + required A Function(int) flutterReleaseNotesDetailRequested, + }) { + return switch (this) { + FlutterWhatsNewDetailRequested(:final id) => + flutterWhatsNewDetailRequested(id), + FlutterReleaseNotesDetailRequested(:final id) => + flutterReleaseNotesDetailRequested(id), + }; + } +} diff --git a/examples/now_in_dart_flutter/lib/features/detail/flutter_detail/application/flutter_detail_state.dart b/examples/now_in_dart_flutter/lib/features/detail/flutter_detail/application/flutter_detail_state.dart new file mode 100644 index 0000000..cce07e2 --- /dev/null +++ b/examples/now_in_dart_flutter/lib/features/detail/flutter_detail/application/flutter_detail_state.dart @@ -0,0 +1,31 @@ +part of 'flutter_detail_bloc.dart'; + +enum FlutterDetailStatus { initial, loading, success, failure } + +class FlutterDetailState extends Equatable { + const FlutterDetailState({ + this.status = FlutterDetailStatus.initial, + this.detail = const Fresh.yes(entity: Detail.empty), + this.failureMessage, + }); + + final FlutterDetailStatus status; + final Fresh detail; + final String? failureMessage; + + FlutterDetailState copyWith({ + FlutterDetailStatus Function()? status, + Fresh Function()? detail, + String? Function()? failureMessage, + }) { + return FlutterDetailState( + status: status != null ? status() : this.status, + detail: detail != null ? detail() : this.detail, + failureMessage: + failureMessage != null ? failureMessage() : this.failureMessage, + ); + } + + @override + List get props => [status, detail, failureMessage]; +} diff --git a/examples/now_in_dart_flutter/lib/features/detail/flutter_detail/data/flutter_detail_local_service.dart b/examples/now_in_dart_flutter/lib/features/detail/flutter_detail/data/flutter_detail_local_service.dart new file mode 100644 index 0000000..c869eb9 --- /dev/null +++ b/examples/now_in_dart_flutter/lib/features/detail/flutter_detail/data/flutter_detail_local_service.dart @@ -0,0 +1,13 @@ +import 'package:fpdart/fpdart.dart'; +import 'package:now_in_dart_flutter/features/detail/core/data/detail_dto.dart'; +import 'package:now_in_dart_flutter/features/detail/core/data/detail_local_service.dart'; + +class FlutterDetailLocalService extends DetailLocalService { + FlutterDetailLocalService({super.isarDb}); + + Task upsertFlutterDetail(DetailDTO detailDTO) { + return super.upsertDetail(detailDTO); + } + + Task getFlutterDetail(int id) => super.getDetail(id); +} diff --git a/examples/now_in_dart_flutter/lib/features/detail/flutter_detail/data/flutter_detail_remote_service.dart b/examples/now_in_dart_flutter/lib/features/detail/flutter_detail/data/flutter_detail_remote_service.dart new file mode 100644 index 0000000..9d1a8b2 --- /dev/null +++ b/examples/now_in_dart_flutter/lib/features/detail/flutter_detail/data/flutter_detail_remote_service.dart @@ -0,0 +1,25 @@ +import 'package:fpdart/fpdart.dart'; +import 'package:now_in_dart_flutter/core/data/remote_response.dart'; +import 'package:now_in_dart_flutter/core/domain/failure.dart'; +import 'package:now_in_dart_flutter/features/detail/core/data/detail_remote_service.dart'; + +typedef _FlutterDetail = TaskEither>; + +class FlutterDetailRemoteService extends DetailRemoteService { + FlutterDetailRemoteService({ + super.dio, + super.headerCache, + }); + + _FlutterDetail getWhatsNewFlutterDetail(int id) { + const fullPathToMarkdownFile = + 'repos/flutter/website/contents/src/release/whats-new.md'; + return super.getDetail(id, fullPathToMarkdownFile); + } + + _FlutterDetail getFlutterReleaseNotesDetail(int id) { + const fullPathToMarkdownFile = + 'repos/flutter/website/contents/src/release/release-notes/index.md'; + return super.getDetail(id, fullPathToMarkdownFile); + } +} diff --git a/examples/now_in_dart_flutter/lib/features/detail/flutter_detail/data/flutter_detail_repository.dart b/examples/now_in_dart_flutter/lib/features/detail/flutter_detail/data/flutter_detail_repository.dart new file mode 100644 index 0000000..5c4cf32 --- /dev/null +++ b/examples/now_in_dart_flutter/lib/features/detail/flutter_detail/data/flutter_detail_repository.dart @@ -0,0 +1,60 @@ +import 'package:fpdart/fpdart.dart'; +import 'package:now_in_dart_flutter/core/data/remote_response.dart'; +import 'package:now_in_dart_flutter/core/domain/failure.dart'; +import 'package:now_in_dart_flutter/core/domain/fresh.dart'; +import 'package:now_in_dart_flutter/features/detail/core/data/detail_dto.dart'; +import 'package:now_in_dart_flutter/features/detail/core/domain/detail.dart'; +import 'package:now_in_dart_flutter/features/detail/flutter_detail/data/flutter_detail_local_service.dart'; +import 'package:now_in_dart_flutter/features/detail/flutter_detail/data/flutter_detail_remote_service.dart'; + +typedef _FlutterDetailOrFailure = TaskEither>; + +class FlutterDetailRepository { + FlutterDetailRepository({ + FlutterDetailLocalService? localService, + FlutterDetailRemoteService? remoteService, + }) : _localService = localService ?? FlutterDetailLocalService(), + _remoteService = remoteService ?? FlutterDetailRemoteService(); + + final FlutterDetailLocalService _localService; + final FlutterDetailRemoteService _remoteService; + + _FlutterDetailOrFailure getWhatsNewFlutterDetail(int id) { + return _getFlutterDetail(id, _remoteService.getWhatsNewFlutterDetail); + } + + _FlutterDetailOrFailure getFlutterReleaseNotesDetail(int id) { + return _getFlutterDetail(id, _remoteService.getFlutterReleaseNotesDetail); + } + + _FlutterDetailOrFailure _getFlutterDetail( + int id, + TaskEither> Function(int) caller, + ) { + return TaskEither.Do( + (_) async { + final remoteResponse = await _(caller(id)); + + return remoteResponse.when( + noConnection: () async { + final dto = await _( + _localService.getFlutterDetail(id).toTaskEither(), + ); + return Fresh.no(entity: dto?.toDomain() ?? Detail.empty); + }, + unmodifed: () async { + final cachedData = await _( + _localService.getFlutterDetail(id).toTaskEither(), + ); + return Fresh.yes(entity: cachedData?.toDomain() ?? Detail.empty); + }, + modified: (data) async { + final dto = DetailDTO.parseHtml(id, data); + await _(_localService.upsertFlutterDetail(dto).toTaskEither()); + return Fresh.yes(entity: dto.toDomain()); + }, + ); + }, + ); + } +} diff --git a/examples/now_in_dart_flutter/lib/features/detail/flutter_detail/presentation/view/flutter_detail_common.dart b/examples/now_in_dart_flutter/lib/features/detail/flutter_detail/presentation/view/flutter_detail_common.dart new file mode 100644 index 0000000..1cefc06 --- /dev/null +++ b/examples/now_in_dart_flutter/lib/features/detail/flutter_detail/presentation/view/flutter_detail_common.dart @@ -0,0 +1,75 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:now_in_dart_flutter/core/presentation/no_connection_toast.dart'; +import 'package:now_in_dart_flutter/core/presentation/no_results_display.dart'; +import 'package:now_in_dart_flutter/features/detail/core/presentation/widget/detail_webview.dart'; +import 'package:now_in_dart_flutter/features/detail/flutter_detail/application/flutter_detail_bloc.dart'; +import 'package:now_in_dart_flutter/features/detail/flutter_detail/data/flutter_detail_repository.dart'; + +class FlutterDetailCommonPage extends StatelessWidget { + const FlutterDetailCommonPage({ + required FlutterDetailEvent event, + super.key, + }) : _event = event; + + final FlutterDetailEvent _event; + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) { + return FlutterDetailBloc( + repository: context.read(), + )..add(_event); + }, + child: const FlutterDetailCommonView(), + ); + } +} + +class FlutterDetailCommonView extends StatefulWidget { + const FlutterDetailCommonView({super.key}); + + @override + State createState() => + _FlutterDetailCommonViewState(); +} + +class _FlutterDetailCommonViewState extends State { + bool _hasAlreadyShownNoConnectionToast = false; + + @override + Widget build(BuildContext context) { + return BlocConsumer( + listener: (context, state) { + if (!state.detail.isFresh! && !_hasAlreadyShownNoConnectionToast) { + _hasAlreadyShownNoConnectionToast = true; + showNoConnectionToast('No Internet Connection!!!', context); + } + }, + builder: (context, state) { + switch (state.status) { + case FlutterDetailStatus.initial: + return const SizedBox.shrink(); + + case FlutterDetailStatus.loading: + return const Center( + child: CircularProgressIndicator.adaptive(), + ); + + case FlutterDetailStatus.success: + final receivedDetail = state.detail; + if (receivedDetail.entity!.isEmpty) { + return const NoResultsDisplay( + message: "Sorry. There's nothing to display ☚ī¸", + ); + } + return DetailWebView(html: receivedDetail.entity!.html); + + case FlutterDetailStatus.failure: + return NoResultsDisplay(message: state.failureMessage!); + } + }, + ); + } +} diff --git a/examples/now_in_dart_flutter/lib/features/detail/flutter_detail/presentation/view/flutter_detail_page.dart b/examples/now_in_dart_flutter/lib/features/detail/flutter_detail/presentation/view/flutter_detail_page.dart new file mode 100644 index 0000000..71f2d81 --- /dev/null +++ b/examples/now_in_dart_flutter/lib/features/detail/flutter_detail/presentation/view/flutter_detail_page.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; +import 'package:now_in_dart_flutter/features/detail/flutter_detail/presentation/view/flutter_release_notes_page.dart'; +import 'package:now_in_dart_flutter/features/detail/flutter_detail/presentation/view/flutter_whats_new_page.dart'; + +class FlutterDetailPage extends StatelessWidget { + const FlutterDetailPage({super.key}); + + @override + Widget build(BuildContext context) { + const tabs = [ + Tab(text: "What's new 🆕"), + Tab(text: 'Release Notes 🗒ī¸'), + ]; + return DefaultTabController( + length: tabs.length, + child: Scaffold( + appBar: AppBar( + title: const Text('Flutter'), + bottom: const TabBar( + tabs: tabs, + ), + ), + body: const TabBarView( + children: [ + FlutterWhatsNewPage(), + FlutterReleaseNotesPage(), + ], + ), + ), + ); + } +} diff --git a/examples/now_in_dart_flutter/lib/features/detail/flutter_detail/presentation/view/flutter_release_notes_page.dart b/examples/now_in_dart_flutter/lib/features/detail/flutter_detail/presentation/view/flutter_release_notes_page.dart new file mode 100644 index 0000000..33454a6 --- /dev/null +++ b/examples/now_in_dart_flutter/lib/features/detail/flutter_detail/presentation/view/flutter_release_notes_page.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; +import 'package:now_in_dart_flutter/core/data/id.dart'; +import 'package:now_in_dart_flutter/features/detail/flutter_detail/application/flutter_detail_bloc.dart'; +import 'package:now_in_dart_flutter/features/detail/flutter_detail/presentation/view/flutter_detail_common.dart'; + +class FlutterReleaseNotesPage extends StatefulWidget { + const FlutterReleaseNotesPage({super.key}); + + @override + State createState() => + _FlutterReleaseNotesPageState(); +} + +class _FlutterReleaseNotesPageState extends State + with AutomaticKeepAliveClientMixin { + @override + Widget build(BuildContext context) { + super.build(context); + return const FlutterDetailCommonPage( + event: FlutterReleaseNotesDetailRequested( + EntityId.flutterReleaseNotesDetail, + ), + ); + } + + @override + bool get wantKeepAlive => true; +} diff --git a/examples/now_in_dart_flutter/lib/features/detail/flutter_detail/presentation/view/flutter_whats_new_page.dart b/examples/now_in_dart_flutter/lib/features/detail/flutter_detail/presentation/view/flutter_whats_new_page.dart new file mode 100644 index 0000000..fa44879 --- /dev/null +++ b/examples/now_in_dart_flutter/lib/features/detail/flutter_detail/presentation/view/flutter_whats_new_page.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; +import 'package:now_in_dart_flutter/core/data/id.dart'; +import 'package:now_in_dart_flutter/features/detail/flutter_detail/application/flutter_detail_bloc.dart'; +import 'package:now_in_dart_flutter/features/detail/flutter_detail/presentation/view/flutter_detail_common.dart'; + +class FlutterWhatsNewPage extends StatefulWidget { + const FlutterWhatsNewPage({super.key}); + + @override + State createState() => _FlutterWhatsNewPageState(); +} + +class _FlutterWhatsNewPageState extends State + with AutomaticKeepAliveClientMixin { + @override + Widget build(BuildContext context) { + super.build(context); + return const FlutterDetailCommonPage( + event: FlutterWhatsNewDetailRequested( + EntityId.flutterWhatsNewDetail, + ), + ); + } + + @override + bool get wantKeepAlive => true; +} diff --git a/examples/now_in_dart_flutter/lib/features/home/cubit/home_cubit.dart b/examples/now_in_dart_flutter/lib/features/home/cubit/home_cubit.dart new file mode 100644 index 0000000..30ec9de --- /dev/null +++ b/examples/now_in_dart_flutter/lib/features/home/cubit/home_cubit.dart @@ -0,0 +1,10 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +part 'home_state.dart'; + +class HomeCubit extends Cubit { + HomeCubit() : super(const HomeState()); + + void setTab(int index) => emit(HomeState(index: index)); +} diff --git a/examples/now_in_dart_flutter/lib/features/home/cubit/home_state.dart b/examples/now_in_dart_flutter/lib/features/home/cubit/home_state.dart new file mode 100644 index 0000000..873e598 --- /dev/null +++ b/examples/now_in_dart_flutter/lib/features/home/cubit/home_state.dart @@ -0,0 +1,12 @@ +part of 'home_cubit.dart'; + +class HomeState extends Equatable { + const HomeState({ + this.index = 0, + }); + + final int index; + + @override + List get props => [index]; +} diff --git a/examples/now_in_dart_flutter/lib/features/home/home.dart b/examples/now_in_dart_flutter/lib/features/home/home.dart new file mode 100644 index 0000000..0e9281a --- /dev/null +++ b/examples/now_in_dart_flutter/lib/features/home/home.dart @@ -0,0 +1,2 @@ +export 'cubit/home_cubit.dart'; +export 'view/view.dart'; diff --git a/examples/now_in_dart_flutter/lib/features/home/view/home_page.dart b/examples/now_in_dart_flutter/lib/features/home/view/home_page.dart new file mode 100644 index 0000000..d44b1d3 --- /dev/null +++ b/examples/now_in_dart_flutter/lib/features/home/view/home_page.dart @@ -0,0 +1,137 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:now_in_dart_flutter/core/presentation/assets_path.dart'; +import 'package:now_in_dart_flutter/core/presentation/lazy_indexed_stack.dart'; +import 'package:now_in_dart_flutter/core/presentation/responsive.dart'; +import 'package:now_in_dart_flutter/features/detail/dart_detail/presentation/view/dart_changelog_page.dart'; +import 'package:now_in_dart_flutter/features/detail/flutter_detail/presentation/view/flutter_detail_page.dart'; +import 'package:now_in_dart_flutter/features/home/cubit/home_cubit.dart'; +import 'package:vector_graphics/vector_graphics.dart'; + +class HomePage extends StatelessWidget { + const HomePage({super.key}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => HomeCubit(), + child: const HomeView(), + ); + } +} + +class HomeView extends StatelessWidget { + const HomeView({super.key}); + + @override + Widget build(BuildContext context) { + return const Responsive( + mobile: MobileView(), + tabletOrDesktop: TabletOrDesktopView(), + ); + } +} + +class MobileView extends StatelessWidget { + const MobileView({super.key}); + + @override + Widget build(BuildContext context) { + SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); + const pages = [DartChangelogPage(), FlutterDetailPage()]; + + final selectedTabIndex = context.select( + (HomeCubit cubit) => cubit.state.index, + ); + return Scaffold( + body: LazyIndexedStack( + index: selectedTabIndex, + children: pages, + ), + bottomNavigationBar: NavigationBar( + destinations: _destinations, + selectedIndex: selectedTabIndex, + onDestinationSelected: context.read().setTab, + ), + ); + } +} + +class TabletOrDesktopView extends StatelessWidget { + const TabletOrDesktopView({super.key}); + + @override + Widget build(BuildContext context) { + SystemChrome.setPreferredOrientations([ + DeviceOrientation.landscapeLeft, + DeviceOrientation.landscapeRight, + ]); + + const pages = [DartChangelogPage(), FlutterDetailPage()]; + + final selectedTabIndex = context.select( + (HomeCubit cubit) => cubit.state.index, + ); + return Scaffold( + body: Row( + children: [ + NavigationRail( + destinations: _railDestinations, + selectedIndex: selectedTabIndex, + useIndicator: true, + labelType: NavigationRailLabelType.selected, + groupAlignment: 0, + onDestinationSelected: context.read().setTab, + ), + const VerticalDivider(thickness: 1, width: 1), + Expanded( + child: LazyIndexedStack( + index: selectedTabIndex, + children: pages, + ), + ), + ], + ), + ); + } +} + +final _destinations = [ + const NavigationDestination( + icon: SvgPicture( + AssetBytesLoader(AssetsPath.dartIcon), + width: 24, + height: 24, + ), + label: 'Dart', + ), + const NavigationDestination( + icon: SvgPicture( + AssetBytesLoader(AssetsPath.flutterIcon), + width: 24, + height: 24, + ), + label: 'Flutter', + ), +]; + +final _railDestinations = [ + NavigationRailDestination( + icon: SvgPicture.asset( + AssetsPath.dartIcon, + width: 24, + height: 24, + ), + label: const Text('Dart'), + ), + NavigationRailDestination( + icon: SvgPicture.asset( + AssetsPath.flutterIcon, + width: 24, + height: 24, + ), + label: const Text('Flutter'), + ), +]; diff --git a/examples/now_in_dart_flutter/lib/features/home/view/view.dart b/examples/now_in_dart_flutter/lib/features/home/view/view.dart new file mode 100644 index 0000000..e4ff269 --- /dev/null +++ b/examples/now_in_dart_flutter/lib/features/home/view/view.dart @@ -0,0 +1 @@ +export 'home_page.dart'; diff --git a/examples/now_in_dart_flutter/lib/main.dart b/examples/now_in_dart_flutter/lib/main.dart new file mode 100644 index 0000000..c15f6c0 --- /dev/null +++ b/examples/now_in_dart_flutter/lib/main.dart @@ -0,0 +1,25 @@ +import 'package:now_in_dart_flutter/app/app.dart'; +import 'package:now_in_dart_flutter/bootstrap.dart'; +import 'package:now_in_dart_flutter/features/detail/dart_detail/data/dart_detail_remote_service.dart'; +import 'package:now_in_dart_flutter/features/detail/dart_detail/data/dart_detail_repository.dart'; +import 'package:now_in_dart_flutter/features/detail/flutter_detail/data/flutter_detail_remote_service.dart'; +import 'package:now_in_dart_flutter/features/detail/flutter_detail/data/flutter_detail_repository.dart'; + +void main() { + bootstrap( + (dio) { + final dartDetailRepository = DartDetailRepository( + remoteService: DartDetailRemoteService(dio: dio), + ); + + final flutterDetailRepository = FlutterDetailRepository( + remoteService: FlutterDetailRemoteService(dio: dio), + ); + + return App( + dartDetailRepository: dartDetailRepository, + flutterDetailRepository: flutterDetailRepository, + ); + }, + ); +} diff --git a/examples/now_in_dart_flutter/pubspec.yaml b/examples/now_in_dart_flutter/pubspec.yaml new file mode 100644 index 0000000..bfae3d0 --- /dev/null +++ b/examples/now_in_dart_flutter/pubspec.yaml @@ -0,0 +1,39 @@ +name: now_in_dart_flutter +description: A simple app that uses WebView under the hood to display all the news and updates regarding Dart and Flutter. The information is retrieved using GitHub's API and the github repository used as source is that of Flutter and Dart's. + +publish_to: 'none' + +version: 2.0.0 + +environment: + sdk: '>=3.1.0 <4.0.0' + +dependencies: + flutter: + sdk: flutter + + dio: ^5.3.2 + equatable: ^2.0.5 + flash: ^3.0.5+1 + flutter_bloc: ^8.1.3 + flutter_svg: ^2.0.7 + fpdart: ^1.1.0 + isar: ^3.1.0+1 + isar_flutter_libs: ^3.1.0+1 + meta: ^1.9.1 + url_launcher: ^6.1.12 + webview_flutter: ^4.2.3 + path_provider: ^2.1.0 + vector_graphics: ^1.1.7 + +dev_dependencies: + build_runner: ^2.4.6 + flutter_lints: ^2.0.2 + mocktail: ^1.0.0 + test: ^1.24.6 + very_good_analysis: ^5.0.0+1 + +flutter: + uses-material-design: true + assets: + - assets/ From dc6a22363023fb9e733b39194e2b67d5597e8fa2 Mon Sep 17 00:00:00 2001 From: Biplab Dutta Date: Sun, 3 Sep 2023 03:05:09 +0545 Subject: [PATCH 03/11] remove unnecessary comment --- .../detail/flutter_detail/application/flutter_detail_event.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/now_in_dart_flutter/lib/features/detail/flutter_detail/application/flutter_detail_event.dart b/examples/now_in_dart_flutter/lib/features/detail/flutter_detail/application/flutter_detail_event.dart index 6b0d54e..8fcff24 100644 --- a/examples/now_in_dart_flutter/lib/features/detail/flutter_detail/application/flutter_detail_event.dart +++ b/examples/now_in_dart_flutter/lib/features/detail/flutter_detail/application/flutter_detail_event.dart @@ -1,4 +1,3 @@ -// ignore_for_file: public_member_api_docs, sort_constructors_first part of 'flutter_detail_bloc.dart'; sealed class FlutterDetailEvent { From ddeceddf39170130b13a330b9e4f9906281284d9 Mon Sep 17 00:00:00 2001 From: Biplab Dutta Date: Sun, 3 Sep 2023 03:10:20 +0545 Subject: [PATCH 04/11] fix typo in example code --- .../now_in_dart_flutter/lib/core/data/remote_response.dart | 4 ++-- .../detail/dart_detail/data/dart_detail_repository.dart | 2 +- .../detail/flutter_detail/data/flutter_detail_repository.dart | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/now_in_dart_flutter/lib/core/data/remote_response.dart b/examples/now_in_dart_flutter/lib/core/data/remote_response.dart index 1033c97..b8dfa6c 100644 --- a/examples/now_in_dart_flutter/lib/core/data/remote_response.dart +++ b/examples/now_in_dart_flutter/lib/core/data/remote_response.dart @@ -19,12 +19,12 @@ class ModifiedRemoteResponse extends RemoteResponse { extension RemoteResponseExt on RemoteResponse { A when({ required A Function() noConnection, - required A Function() unmodifed, + required A Function() unmodified, required A Function(T data) modified, }) { return switch (this) { NoConnectionRemoteResponse() => noConnection(), - UnModifiedRemoteResponse() => unmodifed(), + UnModifiedRemoteResponse() => unmodified(), ModifiedRemoteResponse(:final data) => modified(data), }; } diff --git a/examples/now_in_dart_flutter/lib/features/detail/dart_detail/data/dart_detail_repository.dart b/examples/now_in_dart_flutter/lib/features/detail/dart_detail/data/dart_detail_repository.dart index 3632646..0ef296e 100644 --- a/examples/now_in_dart_flutter/lib/features/detail/dart_detail/data/dart_detail_repository.dart +++ b/examples/now_in_dart_flutter/lib/features/detail/dart_detail/data/dart_detail_repository.dart @@ -31,7 +31,7 @@ class DartDetailRepository { final dto = await _(_localService.getDartDetail(id).toTaskEither()); return Fresh.no(entity: dto?.toDomain() ?? Detail.empty); }, - unmodifed: () async { + unmodified: () async { final cachedData = await _( _localService.getDartDetail(id).toTaskEither(), ); diff --git a/examples/now_in_dart_flutter/lib/features/detail/flutter_detail/data/flutter_detail_repository.dart b/examples/now_in_dart_flutter/lib/features/detail/flutter_detail/data/flutter_detail_repository.dart index 5c4cf32..968a28e 100644 --- a/examples/now_in_dart_flutter/lib/features/detail/flutter_detail/data/flutter_detail_repository.dart +++ b/examples/now_in_dart_flutter/lib/features/detail/flutter_detail/data/flutter_detail_repository.dart @@ -42,7 +42,7 @@ class FlutterDetailRepository { ); return Fresh.no(entity: dto?.toDomain() ?? Detail.empty); }, - unmodifed: () async { + unmodified: () async { final cachedData = await _( _localService.getFlutterDetail(id).toTaskEither(), ); From 4609daaf71110fcaa0518fa98a2c86967bb541d2 Mon Sep 17 00:00:00 2001 From: Biplab Dutta Date: Fri, 8 Dec 2023 04:14:10 +0545 Subject: [PATCH 05/11] refactor: make minor refactorings across different files --- .../now_in_dart_flutter/lib/core/data/data.dart | 1 + .../lib/core/data/dio_extension.dart | 6 ++---- .../lib/core/data/remote_response.dart | 10 +++++++++- .../now_in_dart_flutter/lib/core/data/utils.dart | 13 +++++++++++++ .../lib/features/detail/core/data/constants.dart | 10 ++++++++++ .../detail/core/data/detail_local_service.dart | 2 ++ .../detail/core/data/detail_remote_service.dart | 14 ++------------ .../data/dart_detail_remote_service.dart | 4 ++-- .../data/flutter_detail_remote_service.dart | 9 +++------ 9 files changed, 44 insertions(+), 25 deletions(-) create mode 100644 examples/now_in_dart_flutter/lib/core/data/utils.dart create mode 100644 examples/now_in_dart_flutter/lib/features/detail/core/data/constants.dart diff --git a/examples/now_in_dart_flutter/lib/core/data/data.dart b/examples/now_in_dart_flutter/lib/core/data/data.dart index cf87fdd..b9d4d1a 100644 --- a/examples/now_in_dart_flutter/lib/core/data/data.dart +++ b/examples/now_in_dart_flutter/lib/core/data/data.dart @@ -3,3 +3,4 @@ export 'github_header.dart'; export 'github_header_cache.dart'; export 'isar_database.dart'; export 'remote_response.dart'; +export 'utils.dart'; diff --git a/examples/now_in_dart_flutter/lib/core/data/dio_extension.dart b/examples/now_in_dart_flutter/lib/core/data/dio_extension.dart index b666bc6..0aa8779 100644 --- a/examples/now_in_dart_flutter/lib/core/data/dio_extension.dart +++ b/examples/now_in_dart_flutter/lib/core/data/dio_extension.dart @@ -1,9 +1,7 @@ -import 'dart:io'; - import 'package:dio/dio.dart'; -extension DioErrorX on DioException { +extension DioExceptionExtension on DioException { bool get isNoConnectionError { - return error is SocketException; + return type == DioExceptionType.connectionError; } } diff --git a/examples/now_in_dart_flutter/lib/core/data/remote_response.dart b/examples/now_in_dart_flutter/lib/core/data/remote_response.dart index b8dfa6c..745fad6 100644 --- a/examples/now_in_dart_flutter/lib/core/data/remote_response.dart +++ b/examples/now_in_dart_flutter/lib/core/data/remote_response.dart @@ -1,5 +1,10 @@ -sealed class RemoteResponse { +import 'package:equatable/equatable.dart'; + +sealed class RemoteResponse extends Equatable { const RemoteResponse(); + + @override + List get props => []; } class NoConnectionRemoteResponse extends RemoteResponse { @@ -14,6 +19,9 @@ class ModifiedRemoteResponse extends RemoteResponse { const ModifiedRemoteResponse(this.data); final T data; + + @override + List get props => [data]; } extension RemoteResponseExt on RemoteResponse { diff --git a/examples/now_in_dart_flutter/lib/core/data/utils.dart b/examples/now_in_dart_flutter/lib/core/data/utils.dart new file mode 100644 index 0000000..57627e2 --- /dev/null +++ b/examples/now_in_dart_flutter/lib/core/data/utils.dart @@ -0,0 +1,13 @@ +import 'package:fpdart/fpdart.dart'; +import 'package:now_in_dart_flutter/core/domain/failure.dart'; + +IOEither uriParser(String uri) { + return IOEither.tryCatch( + () => Uri.parse(uri), + (e, stackTrace) => UriParserFailure( + 'Invalid Uri string', + errorObject: e, + stackTrace: stackTrace, + ), + ); +} diff --git a/examples/now_in_dart_flutter/lib/features/detail/core/data/constants.dart b/examples/now_in_dart_flutter/lib/features/detail/core/data/constants.dart new file mode 100644 index 0000000..fde2fc7 --- /dev/null +++ b/examples/now_in_dart_flutter/lib/features/detail/core/data/constants.dart @@ -0,0 +1,10 @@ +/// repos/flutter/website/contents/src/release/whats-new.md +const flutterWhatsNewPath = + 'repos/flutter/website/contents/src/release/whats-new.md'; + +/// repos/flutter/website/contents/src/release/release-notes/index.md +const flutterReleaseNotesPath = + 'repos/flutter/website/contents/src/release/release-notes/index.md'; + +/// repos/dart-lang/sdk/contents/CHANGELOG.md +const dartChangelogPath = 'repos/dart-lang/sdk/contents/CHANGELOG.md'; diff --git a/examples/now_in_dart_flutter/lib/features/detail/core/data/detail_local_service.dart b/examples/now_in_dart_flutter/lib/features/detail/core/data/detail_local_service.dart index 5806505..fc51ac4 100644 --- a/examples/now_in_dart_flutter/lib/features/detail/core/data/detail_local_service.dart +++ b/examples/now_in_dart_flutter/lib/features/detail/core/data/detail_local_service.dart @@ -14,6 +14,7 @@ abstract class DetailLocalService { Isar get _isar => _isarDb.instance; @protected + @visibleForTesting Task upsertDetail(DetailDTO detailDTO) { final txn = _isar.writeTxn( () async { @@ -27,6 +28,7 @@ abstract class DetailLocalService { } @protected + @visibleForTesting Task getDetail(int id) { return Task(() => _isar.detailDTOs.get(id)); } diff --git a/examples/now_in_dart_flutter/lib/features/detail/core/data/detail_remote_service.dart b/examples/now_in_dart_flutter/lib/features/detail/core/data/detail_remote_service.dart index 3feb2ff..99d3284 100644 --- a/examples/now_in_dart_flutter/lib/features/detail/core/data/detail_remote_service.dart +++ b/examples/now_in_dart_flutter/lib/features/detail/core/data/detail_remote_service.dart @@ -17,6 +17,7 @@ abstract class DetailRemoteService { final HeaderCache _headerCache; @protected + @visibleForTesting _FailureOrRemoteResponse getDetail( int id, String fullPathToMarkdownFile, @@ -24,7 +25,7 @@ abstract class DetailRemoteService { return TaskEither.Do( (_) async { final requestUri = await _( - _uriParser(fullPathToMarkdownFile).toTaskEither(), + uriParser(fullPathToMarkdownFile).toTaskEither(), ); final cachedHeader = await _( @@ -83,14 +84,3 @@ abstract class DetailRemoteService { ); } } - -IOEither _uriParser(String uri) { - return IOEither.tryCatch( - () => Uri.parse(uri), - (e, stackTrace) => UriParserFailure( - 'Invalid Uri string', - errorObject: e, - stackTrace: stackTrace, - ), - ); -} diff --git a/examples/now_in_dart_flutter/lib/features/detail/dart_detail/data/dart_detail_remote_service.dart b/examples/now_in_dart_flutter/lib/features/detail/dart_detail/data/dart_detail_remote_service.dart index 0a10cc1..243e142 100644 --- a/examples/now_in_dart_flutter/lib/features/detail/dart_detail/data/dart_detail_remote_service.dart +++ b/examples/now_in_dart_flutter/lib/features/detail/dart_detail/data/dart_detail_remote_service.dart @@ -1,6 +1,7 @@ import 'package:fpdart/fpdart.dart'; import 'package:now_in_dart_flutter/core/data/remote_response.dart'; import 'package:now_in_dart_flutter/core/domain/failure.dart'; +import 'package:now_in_dart_flutter/features/detail/core/data/constants.dart'; import 'package:now_in_dart_flutter/features/detail/core/data/detail_remote_service.dart'; typedef _DartDetail = TaskEither>; @@ -12,7 +13,6 @@ class DartDetailRemoteService extends DetailRemoteService { }); _DartDetail getDartChangelogDetail(int id) { - const fullPathToMarkdownFile = 'repos/dart-lang/sdk/contents/CHANGELOG.md'; - return super.getDetail(id, fullPathToMarkdownFile); + return super.getDetail(id, dartChangelogPath); } } diff --git a/examples/now_in_dart_flutter/lib/features/detail/flutter_detail/data/flutter_detail_remote_service.dart b/examples/now_in_dart_flutter/lib/features/detail/flutter_detail/data/flutter_detail_remote_service.dart index 9d1a8b2..28d420e 100644 --- a/examples/now_in_dart_flutter/lib/features/detail/flutter_detail/data/flutter_detail_remote_service.dart +++ b/examples/now_in_dart_flutter/lib/features/detail/flutter_detail/data/flutter_detail_remote_service.dart @@ -1,6 +1,7 @@ import 'package:fpdart/fpdart.dart'; import 'package:now_in_dart_flutter/core/data/remote_response.dart'; import 'package:now_in_dart_flutter/core/domain/failure.dart'; +import 'package:now_in_dart_flutter/features/detail/core/data/constants.dart'; import 'package:now_in_dart_flutter/features/detail/core/data/detail_remote_service.dart'; typedef _FlutterDetail = TaskEither>; @@ -12,14 +13,10 @@ class FlutterDetailRemoteService extends DetailRemoteService { }); _FlutterDetail getWhatsNewFlutterDetail(int id) { - const fullPathToMarkdownFile = - 'repos/flutter/website/contents/src/release/whats-new.md'; - return super.getDetail(id, fullPathToMarkdownFile); + return super.getDetail(id, flutterWhatsNewPath); } _FlutterDetail getFlutterReleaseNotesDetail(int id) { - const fullPathToMarkdownFile = - 'repos/flutter/website/contents/src/release/release-notes/index.md'; - return super.getDetail(id, fullPathToMarkdownFile); + return super.getDetail(id, flutterReleaseNotesPath); } } From 1e7fa2145e60802de7982f5ea4b01cca9240ae4e Mon Sep 17 00:00:00 2001 From: Biplab Dutta Date: Fri, 8 Dec 2023 04:15:02 +0545 Subject: [PATCH 06/11] test: add a unit test for `detail_local_service.dart` --- .../core/data/detail_local_service_test.dart | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 examples/now_in_dart_flutter/test/features/detail/core/data/detail_local_service_test.dart diff --git a/examples/now_in_dart_flutter/test/features/detail/core/data/detail_local_service_test.dart b/examples/now_in_dart_flutter/test/features/detail/core/data/detail_local_service_test.dart new file mode 100644 index 0000000..de6bfc0 --- /dev/null +++ b/examples/now_in_dart_flutter/test/features/detail/core/data/detail_local_service_test.dart @@ -0,0 +1,77 @@ +import 'package:fpdart/fpdart.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:now_in_dart_flutter/features/detail/core/data/detail_dto.dart'; +import 'package:test/test.dart'; + +import '../../../../helpers/mocks.dart'; + +void main() { + final isar = MockIsar(); + final isarDb = MockIsarDatabase(); + final isarCollection = MockIsarCollection(); + final detailLocalService = MockDetailLocalService(isarDb: isarDb); + + group( + 'DetailLocalService |', + () { + const fakeDetailDTO = DetailDTO(id: 1, html: 'html'); + + setUpAll(() => when(() => isarDb.instance).thenReturn(isar)); + + test( + 'should instantiate IsarDatabase() when not injected', + () => expect(MockDetailLocalService(), isNotNull), + ); + + test( + 'The method `upsertDetail` should either update or insert the passed ' + 'DetailDTO', + () async { + when( + () => isar.writeTxn( + any(that: isA()), + silent: any(named: 'silent', that: isA()), + ), + ).thenAnswer((_) async => unit); + + final result = + await detailLocalService.upsertDetail(fakeDetailDTO).run(); + + expect(result, isA()); + }, + ); + + group( + 'The method `getDetail`', + () { + setUpAll( + () => when(() => isar.detailDTOs).thenReturn(isarCollection), + ); + test( + 'should return DetailDTO object for the passed id', + () async { + when(() => isarCollection.get(any(that: isA()))) + .thenAnswer((_) async => fakeDetailDTO); + + final result = await detailLocalService.getDetail(1).run(); + + expect(result, fakeDetailDTO); + }, + ); + + test( + 'should return null if invalid id is passed', + () async { + when(() => isarCollection.get(any(that: isA()))) + .thenAnswer((_) async => null); + + final result = await detailLocalService.getDetail(1).run(); + + expect(result, isNull); + }, + ); + }, + ); + }, + ); +} From cff84f3609efcbc18bcac7ffb4bd2db22fda37d6 Mon Sep 17 00:00:00 2001 From: Biplab Dutta Date: Fri, 8 Dec 2023 04:15:13 +0545 Subject: [PATCH 07/11] test: add a unit test for `detail_remote_service.dart` --- .../core/data/detail_remote_service_test.dart | 174 ++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 examples/now_in_dart_flutter/test/features/detail/core/data/detail_remote_service_test.dart diff --git a/examples/now_in_dart_flutter/test/features/detail/core/data/detail_remote_service_test.dart b/examples/now_in_dart_flutter/test/features/detail/core/data/detail_remote_service_test.dart new file mode 100644 index 0000000..29311d2 --- /dev/null +++ b/examples/now_in_dart_flutter/test/features/detail/core/data/detail_remote_service_test.dart @@ -0,0 +1,174 @@ +import 'package:dio/dio.dart'; +import 'package:fpdart/fpdart.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:now_in_dart_flutter/core/data/github_header.dart'; +import 'package:now_in_dart_flutter/core/data/remote_response.dart'; +import 'package:now_in_dart_flutter/core/domain/failure.dart'; +import 'package:test/test.dart'; + +import '../../../../helpers/mocks.dart'; +import '../../../../helpers/register_multiple_fallback_values.dart'; + +void main() { + final dio = MockDio(); + final response = MockResponse(); + final headers = MockHeaders(); + final headerCache = MockHeaderCache(); + final detailRemoteService = MockDetailRemoteService( + dio: dio, + headerCache: headerCache, + ); + + setUpAll(() { + registerMultipleFallbackValues([ + FakeUri(), + FakeOptions(), + FakeGithubHeader(), + ]); + }); + + group( + 'DetailRemoteService |', + () { + const fakeGithubHeader = GithubHeader( + id: 1, + eTag: '12345', + path: '/path', + ); + + test( + 'should instantiate Dio() and HeaderCache() when not injected', + () => expect(MockDetailRemoteService(), isNotNull), + ); + group( + 'The method `getDetail`', + () { + setUpAll( + () => when( + () => headerCache.getHeader(any(that: isA())), + ).thenReturn(Task(() async => fakeGithubHeader)), + ); + test( + 'should return right of TaskEither>' + ' i.e. ModifiedRemoteResponse if the status code is 200', + () async { + when(() => headers.map).thenReturn( + { + 'ETag': ['12345'], + }, + ); + + when(() => response.statusCode).thenReturn(200); + when(() => response.data).thenReturn('html'); + when(() => response.headers).thenReturn(headers); + + when( + () => dio.getUri( + any(that: isA()), + options: any(named: 'options', that: isA()), + ), + ).thenAnswer((_) async => response); + + when( + () => headerCache.saveHeader(any(that: isA())), + ).thenReturn(Task(() async => unit)); + + final result = + await detailRemoteService.getDetail(1, '/path').run(); + + expect( + result, + right>( + const ModifiedRemoteResponse('html'), + ), + ); + }, + ); + + test( + 'should return right of TaskEither>' + ' i.e. UnModifiedRemoteResponse if the status code is 304', + () async { + when(() => response.statusCode).thenReturn(304); + + when( + () => dio.getUri( + any(that: isA()), + options: any(named: 'options', that: isA()), + ), + ).thenAnswer((_) async => response); + + final result = + await detailRemoteService.getDetail(1, '/path').run(); + + expect( + result, + right>( + const UnModifiedRemoteResponse(), + ), + ); + }, + ); + + test( + 'should return right of TaskEither>' + ' i.e. NoConnectionRemoteResponse if DioException.connectionError ' + 'is thrown', + () async { + when( + () => dio.getUri( + any(that: isA()), + options: any(named: 'options', that: isA()), + ), + ).thenThrow( + DioException.connectionError( + requestOptions: RequestOptions(), + reason: '', + ), + ); + + final result = + await detailRemoteService.getDetail(1, '/path').run(); + + expect( + result, + right>( + const NoConnectionRemoteResponse(), + ), + ); + }, + ); + + test( + 'should return left of TaskEither>' + ' i.e. ApiFailure if network request is unsuccessful', + () async { + const errorMessage = 'Error on network request'; + + when( + () => dio.getUri( + any(that: isA()), + options: any(named: 'options', that: isA()), + ), + ).thenThrow(Exception()); + + final result = + await detailRemoteService.getDetail(1, '/path').run(); + + expect( + result.match( + (failure) { + final isApiFailure = failure is ApiFailure; + return isApiFailure && failure.message == errorMessage; + }, + (_) => false, + ), + isTrue, + ); + }, + ); + }, + ); + }, + ); +} From 65f702d83dcefb98639442eb0a51dbb7ad5225f7 Mon Sep 17 00:00:00 2001 From: Biplab Dutta Date: Fri, 8 Dec 2023 04:15:38 +0545 Subject: [PATCH 08/11] add mocks and fakes used by the tests --- .../test/helpers/fakes.dart | 7 +++++ .../test/helpers/mocks.dart | 30 +++++++++++++++++++ .../register_multiple_fallback_values.dart | 7 +++++ 3 files changed, 44 insertions(+) create mode 100644 examples/now_in_dart_flutter/test/helpers/fakes.dart create mode 100644 examples/now_in_dart_flutter/test/helpers/mocks.dart create mode 100644 examples/now_in_dart_flutter/test/helpers/register_multiple_fallback_values.dart diff --git a/examples/now_in_dart_flutter/test/helpers/fakes.dart b/examples/now_in_dart_flutter/test/helpers/fakes.dart new file mode 100644 index 0000000..aff7c53 --- /dev/null +++ b/examples/now_in_dart_flutter/test/helpers/fakes.dart @@ -0,0 +1,7 @@ +part of 'mocks.dart'; + +class FakeUri extends Fake implements Uri {} + +class FakeOptions extends Fake implements Options {} + +class FakeGithubHeader extends Fake implements GithubHeader {} diff --git a/examples/now_in_dart_flutter/test/helpers/mocks.dart b/examples/now_in_dart_flutter/test/helpers/mocks.dart new file mode 100644 index 0000000..1c32059 --- /dev/null +++ b/examples/now_in_dart_flutter/test/helpers/mocks.dart @@ -0,0 +1,30 @@ +import 'package:dio/dio.dart'; +import 'package:isar/isar.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:now_in_dart_flutter/core/data/data.dart'; +import 'package:now_in_dart_flutter/features/detail/core/data/detail_local_service.dart'; +import 'package:now_in_dart_flutter/features/detail/core/data/detail_remote_service.dart'; + +part 'fakes.dart'; + +class MockDio extends Mock implements Dio {} + +class MockResponse extends Mock implements Response {} + +class MockHeaders extends Mock implements Headers {} + +class MockHeaderCache extends Mock implements HeaderCache {} + +class MockDetailRemoteService extends DetailRemoteService { + MockDetailRemoteService({super.dio, super.headerCache}); +} + +class MockDetailLocalService extends DetailLocalService { + MockDetailLocalService({super.isarDb}); +} + +class MockIsarDatabase extends Mock implements IsarDatabase {} + +class MockIsar extends Mock implements Isar {} + +class MockIsarCollection extends Mock implements IsarCollection {} diff --git a/examples/now_in_dart_flutter/test/helpers/register_multiple_fallback_values.dart b/examples/now_in_dart_flutter/test/helpers/register_multiple_fallback_values.dart new file mode 100644 index 0000000..2e990d4 --- /dev/null +++ b/examples/now_in_dart_flutter/test/helpers/register_multiple_fallback_values.dart @@ -0,0 +1,7 @@ +import 'package:mocktail/mocktail.dart'; + +void registerMultipleFallbackValues(List values) { + for (final value in values) { + registerFallbackValue(value); + } +} From 701295ed3c07aeffda3137cb946bd93f8167db05 Mon Sep 17 00:00:00 2001 From: Biplab Dutta Date: Fri, 8 Dec 2023 04:15:53 +0545 Subject: [PATCH 09/11] doc: add a new README --- examples/now_in_dart_flutter/README.md | 72 ++++++++++++++++++++++++-- 1 file changed, 67 insertions(+), 5 deletions(-) diff --git a/examples/now_in_dart_flutter/README.md b/examples/now_in_dart_flutter/README.md index 5acbeae..1b923a9 100644 --- a/examples/now_in_dart_flutter/README.md +++ b/examples/now_in_dart_flutter/README.md @@ -1,11 +1,73 @@ # now_in_dart_flutter -### Online App Demo +This app serves the purpose of staying up-to-date with the latest changes across Dart and Flutter ecosystem. The way this app works is by fetching markdowns from Dart and Flutter's official Github repo via REST API and rendering the information in a `WebView` widget. -https://user-images.githubusercontent.com/63902683/192081298-78bb3e31-10ed-4150-b726-e9181b240346.mp4 +## Features -### Offline App Demo +- Display Dart CHANGELOG +- Display Flutter What's New +- Display Flutter Release Notes +- Offline Support -https://user-images.githubusercontent.com/63902683/192081334-09f4be61-e836-4661-9aa9-cacce7c50739.mp4 +## Application Demo -New Readme is being added soon... +
+ + + + + +
+ + + +
+
+ +## Architecture + +The app follows a simple but effective architecture. It relies on a feature-driven architecture with some sub-features. + +Inside [lib/features/](./lib/features/) directory, you can find two sub-features: `detail` and `home`. + +The [home](./lib/features/home/) sub-feature is responsible for showing a scaffold with a bottom navigation bar. It also contains logic for maintaining bottom navigation bar's state. + +The [detail](./lib/features/detail/) feature is divided into two sub-features: [dart_detail](./lib/features/detail/dart_detail/) and [flutter_detail](./lib/features/detail/flutter_detail/). Each of these sub-features has similar structure and is divided into three layers. + +- **Application Layer**: Contains state management logic and acts as a mediator between the presentation and the data layer. +- **Data Layer**: Responsible for making all the necessary API calls and local cache operations. +- **Presentation Layer**: Associated with the UI + +To check the `fpdart` implementation, consider taking a look at the `data` layer of each of the sub-features. + +## State Management +The project uses [flutter_bloc](https://pub.dev/packages/flutter_bloc) for managing the app's state. + +## Storage +The app stores fetched data locally on user's device for offline support. The project uses the latest [isar](https://pub.dev/packages/isar) plugin for local storage. + +## Test +The unit test has been written based on the `fpdart` refactoring and can be found in the [test](./test/) directory. +- [detail_local_service_test.dart](./test/features/detail/core/data/detail_local_service_test.dart) +- [detail_remote_service_test.dart](./test/features/detail/core/data/detail_remote_service_test.dart) + +Dependencies +The project makes use of few third-party packages for rapid development. Some of them are listed below: +- [dio](https://pub.dev/packages/dio) (To perform network calls) +- [equatable](https://pub.dev/packages/equatable) (To achieve value equality) +- [flash](https://pub.dev/packages/flash) (To display customizable toast) +- [flutter_bloc](https://pub.dev/packages/flutter_bloc) (State Management) +- [fpdart](https://pub.dev/packages/fpdart) (Functional Programming) +- [isar](https://pub.dev/packages/isar) (Local Storage) +- [url_launcher](https://pub.dev/packages/url_launcher) (To display information from hyperlinks in a browser interface within the app) +- [webview_flutter](https://pub.dev/packages/webview_flutter) (To display markdowns) +- [mocktail](https://pub.dev/packages/mocktail) (As a mocking library) + +## Types used from `fpdart` +- `TaskEither`: Used instead of `Future` to make async request that may fail +- `IOEither`: Used to represent a synchronous computation that may fail +- `Do` Notation: Used to write functional code that looks like normal imperative code and to avoid methods chaining \ No newline at end of file From 29f6d1999648d49d1f63914c0a61a480ff618b9b Mon Sep 17 00:00:00 2001 From: Biplab Dutta Date: Sat, 23 Dec 2023 18:58:39 +0545 Subject: [PATCH 10/11] refactor: remove impure function in `detail_local_service.dart` --- .../detail/core/data/detail_local_service.dart | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/examples/now_in_dart_flutter/lib/features/detail/core/data/detail_local_service.dart b/examples/now_in_dart_flutter/lib/features/detail/core/data/detail_local_service.dart index fc51ac4..04794e3 100644 --- a/examples/now_in_dart_flutter/lib/features/detail/core/data/detail_local_service.dart +++ b/examples/now_in_dart_flutter/lib/features/detail/core/data/detail_local_service.dart @@ -16,15 +16,17 @@ abstract class DetailLocalService { @protected @visibleForTesting Task upsertDetail(DetailDTO detailDTO) { - final txn = _isar.writeTxn( - () async { - await _isar.detailDTOs.put(detailDTO); - return unit; + return Task( + () { + return _isar.writeTxn( + () async { + await _isar.detailDTOs.put(detailDTO); + return unit; + }, + silent: true, + ); }, - silent: true, ); - - return Task(() => txn); } @protected From 9311ac5949c3d1d0981a81549ec21e8c31891389 Mon Sep 17 00:00:00 2001 From: Biplab Dutta Date: Sat, 23 Dec 2023 18:59:52 +0545 Subject: [PATCH 11/11] use null operator to return empty string if the data received is null --- .../lib/features/detail/core/data/detail_remote_service.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/now_in_dart_flutter/lib/features/detail/core/data/detail_remote_service.dart b/examples/now_in_dart_flutter/lib/features/detail/core/data/detail_remote_service.dart index 99d3284..cb66593 100644 --- a/examples/now_in_dart_flutter/lib/features/detail/core/data/detail_remote_service.dart +++ b/examples/now_in_dart_flutter/lib/features/detail/core/data/detail_remote_service.dart @@ -62,7 +62,7 @@ abstract class DetailRemoteService { await _(_headerCache.saveHeader(header).toTaskEither()); - final html = response.data!; + final html = response.data ?? ''; return right(ModifiedRemoteResponse(html)); }