diff --git a/.flutter-plugins b/.flutter-plugins deleted file mode 100644 index 174bfcd..0000000 --- a/.flutter-plugins +++ /dev/null @@ -1,10 +0,0 @@ -# This is a generated file; do not edit or check into version control. -path_provider=/Users/hautran/.pub-cache/hosted/pub.dev/path_provider-2.1.5/ -path_provider_android=/Users/hautran/.pub-cache/hosted/pub.dev/path_provider_android-2.2.15/ -path_provider_foundation=/Users/hautran/.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.1/ -path_provider_linux=/Users/hautran/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/ -path_provider_windows=/Users/hautran/.pub-cache/hosted/pub.dev/path_provider_windows-2.3.0/ -share_plus=/Users/hautran/.pub-cache/hosted/pub.dev/share_plus-10.1.4/ -url_launcher_linux=/Users/hautran/.pub-cache/hosted/pub.dev/url_launcher_linux-3.2.1/ -url_launcher_web=/Users/hautran/.pub-cache/hosted/pub.dev/url_launcher_web-2.3.3/ -url_launcher_windows=/Users/hautran/.pub-cache/hosted/pub.dev/url_launcher_windows-3.1.4/ diff --git a/.gitignore b/.gitignore index d373d28..e8b97e4 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,8 @@ build/ .idea/ pubspec.lock -**/.flutter-plugins-dependencies \ No newline at end of file +**/.flutter-plugins-dependencies +Flutter.podspec +flutter_lldb_helper.py +example/ios/Flutter/ephemeral/flutter_lldbinit +flutter_export_environment.sh \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index cc94b00..98f29dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +## 2.1.0-beta.1 +* **Breaking Changes**: Major refactoring of setup process. Introduced `AliceInspector` widget for simplified integration, replacing manual navigator key setup. Removed dependency on `OverlaySupport` package. +* **Implementation Changes**: Refactored core components for better modularity. Updated JSON viewer with performance optimizations and UI improvements. Enhanced floating inspector button with better positioning and animations. +* **Improvements**: Added support for multiple Dio instances in `AliceInspector`. Improved error handling and logging. Updated dependencies for better compatibility. Enhanced documentation with video demo and clearer installation guides. + +## 2.1.0 +* Added JetBrains Mono font asset for improved code display +* Improved iOS build configuration and project settings +* Refactored core components: renamed debug_pop_up.dart to alice_inspector.dart, alice_conversion_helper.dart to alice_convert_helper.dart, alice_base_call_details_widget.dart to alice_base_mixin.dart +* Added alice_theme.dart for centralized theme management +* Enhanced UI widgets and pages for better user experience +* Updated pubspec.yaml with new dependencies and configurations +* Minor bug fixes and code optimizations + ## 2.0.0 * Major version upgrade with breaking changes * Migrated to Flutter 3.16 diff --git a/README.md b/README.md index 2385699..e7bc57c 100644 --- a/README.md +++ b/README.md @@ -1,114 +1,125 @@ -# A ⭐ star on [GitHub repo](https://github.com/hautvfami/flutter-alice) is the greatest motivation for me -# to keep improving this project! 💖 -# Alice +# Alice - HTTP Inspector for Flutter 🎁 [![pub package](https://img.shields.io/pub/v/flutter_alice.svg)](https://pub.dev/packages/flutter_alice) [![pub package](https://img.shields.io/github/license/hautvfami/flutter-alice.svg?style=flat)](https://github.com/hautvfami/flutter-alice) [![pub package](https://img.shields.io/badge/platform-flutter-blue.svg)](https://github.com/hautvfami/flutter-alice) -Alice is an HTTP Inspector tool for Flutter which helps debugging http requests. -It catches and stores http requests and responses, which can be viewed via simple UI. -It is inspired from Chuck (https://github.com/jgilfelt/chuck) and Chucker (https://github.com/ChuckerTeam/chucker). +**Alice** is a powerful HTTP Inspector tool for Flutter developers. It captures, stores, and displays HTTP requests and responses in a user-friendly interface, making debugging network calls effortless. Alice supports popular Dart HTTP clients like Dio, HttpClient, and the http package. +Perfect for Flutter app development, API debugging, network monitoring, and troubleshooting HTTP issues. -Overlay bubble version of Alice: https://github.com/jhomlala/alice +Friendly version of [Alice](https://github.com/jhomlala/alice) by [Jakub](https://github.com/jhomlala). +Inspired from [Chuck](https://github.com/jgilfelt/chuck) and [Chucker](https://github.com/ChuckerTeam/chucker). + +## 🚀 Key Features + +- **Detailed HTTP Logs**: View complete request and response details +- **Intuitive Inspector UI**: Easy-to-navigate interface for HTTP calls +- **Statistics Dashboard**: Analyze network performance +- **Multi-Client Support**: Works with Dio, HttpClient, and http package +- **Error Handling**: Catch and inspect failed requests +- **Search Functionality**: Quickly find specific HTTP calls +- **Bubble Overlay**: Floating inspector access +- **JSON Viewer**: Built-in JSON response viewer for easy parsing +- **Alice Console**: Log messages for debugging in release environments where console access is limited + +## 📸 Demo Video & Screenshots - - - - - - - - - - - - - - + + + + -
- - - - - - - - - - - -
- - - - - - - - - - - + +
-**Supported Dart http client plugins:** - -- Dio -- HttpClient from dart:io package -- Http from http/http package +[ >> VIDEO](https://raw.githubusercontent.com/hautvfami/flutter-alice/task/improve/media/ScreenRecording_11-22-2025%2015-55-39_1.webm) -[//]: # (- Chopper) -- Generic HTTP client +## 🛠️ Supported HTTP Clients -**Features:** -✔️ Detailed logs for each HTTP calls (HTTP Request, HTTP Response) -✔️ Inspector UI for viewing HTTP calls -✔️ Statistics -✔️ Support for top used HTTP clients in Dart -✔️ Error handling -✔️ HTTP calls search -✔️ Bubble overlay entry +- **Dio**: Popular HTTP client for Flutter +- **HttpClient** (dart:io): Built-in Dart HTTP client +- **Http Package**: Standard http/http library +- **Generic HTTP Client**: Flexible integration -## Install +## 📦 Installation -1. Add this to your **pubspec.yaml** file: +1. Add Alice to your `pubspec.yaml`: ```yaml dependencies: - flutter_alice: ^1.0.1 + flutter_alice: ^2.1.0-beta.1 ``` -2. Install it +2. Run the following command: ```bash -$ flutter pub get +flutter pub get ``` -3. Import it +3. Import Alice in your Dart code: ```dart import 'package:flutter_alice/alice.dart'; ``` -## Usage -### Alice configuration -1. Create Alice instance: +## 🚀 Quick Start + +### Recommended: Using AliceInspector Widget + +For the easiest setup, wrap your app with `AliceInspector`. This automatically configures Alice, adds Dio interceptors, and provides a floating inspector button. + +```dart +import 'package:flutter_alice/alice.dart'; + +final alice = Alice(); +final navigatorKey = GlobalKey(); +final dio = Dio(); + +void main() { + alice.log("Alice is running!", color: Colors.yellow); + runApp(MyApp()); +} + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return AliceInspector( + debug: true, // Enable debug mode + dios: [dio], // List of Dio instances to intercept + navigatorKey: navigatorKey, + child: MaterialApp( + navigatorKey: navigatorKey, + debugShowCheckedModeBanner: false, + home: HomePage(), + ), + ); + } +} +``` + +### Alternative: Manual Alice Setup + +If you prefer manual control, create an Alice instance and configure it step-by-step. + +#### 1. Configure Alice ```dart // Define a navigator key final navigatorKey = GlobalKey(); -// Create Alice with the navigator key +// Create Alice instance final alice = Alice(navigatorKey: navigatorKey); ``` -2. Add navigator key to your application: +#### 2. Set Up Your App ```dart MaterialApp( @@ -117,81 +128,53 @@ MaterialApp( ) ``` -You need to add this navigator key in order to show inspector UI. +#### 3. Integrate HTTP Clients -3. Optional: To use bubble overlay, wrap your app with OverlaySupport: - -```dart -// Don't forget to import overlay_support package -import 'package:overlay_support/overlay_support.dart'; - -OverlaySupport( - child: MaterialApp( - navigatorKey: navigatorKey, - home: YourHomeWidget(), - ), -) -``` - -### HTTP Client configuration -#### For Dio -Add interceptor to your Dio instance: +##### Dio Integration ```dart final dio = Dio(); dio.interceptors.add(alice.getDioInterceptor()); ``` -#### For HTTP package -You can use extension methods for cleaner code: +##### Http Package Integration + +Using extensions for cleaner code: ```dart -// Import extensions import 'package:flutter_alice/core/alice_http_extensions.dart'; -// Use extension methods http .get(Uri.parse('https://jsonplaceholder.typicode.com/posts')) .interceptWithAlice(alice); -// For POST requests with body +// For POST with body http .post(Uri.parse('https://jsonplaceholder.typicode.com/posts'), body: body) .interceptWithAlice(alice, body: body); ``` -Or use the standard approach: +Or standard approach: ```dart http.get(Uri.parse('https://jsonplaceholder.typicode.com/posts')).then((response) { alice.onHttpResponse(response); }); - -// For POST requests with body -http.post(Uri.parse('https://jsonplaceholder.typicode.com/posts'), body: body).then((response) { - alice.onHttpResponse(response, body: body); -}); ``` -#### For HttpClient from dart:io -You can use extension methods: +##### HttpClient Integration + +Using extensions: ```dart -// Import extensions import 'package:flutter_alice/core/alice_http_client_extensions.dart'; -// Use extension methods httpClient .getUrl(Uri.parse("https://jsonplaceholder.typicode.com/posts")) .interceptWithAlice(alice); - -// For POST requests with body -httpClient - .postUrl(Uri.parse("https://jsonplaceholder.typicode.com/posts")) - .interceptWithAlice(alice, body: body, headers: Map()); ``` -Or use the standard approach: +Or standard approach: ```dart httpClient @@ -204,30 +187,44 @@ httpClient }); ``` -[//]: # (#### For Chopper) +#### 4. Open Inspector + +```dart +// Button to open inspector +ElevatedButton( + child: Text("Open HTTP Inspector"), + onPressed: alice.showInspector, +) + +// Or call from anywhere +alice.showInspector(); +``` -[//]: # (Add interceptor to your ChopperClient:) +## 📝 Alice Console -[//]: # () -[//]: # (```dart) +Alice provides a built-in console for logging messages, especially useful in release builds where console access is limited. -[//]: # (final chopper = ChopperClient() +```dart +// Log a message with default color +alice.log("This is a log message"); -[//]: # ( interceptors: alice.getChopperInterceptor(),) +// Log with custom color +alice.log("Error occurred", color: Colors.red); -[//]: # ();) +// View logs in the inspector +alice.showInspector(); // Logs are displayed in the Alice UI ``` -### Opening the Inspector -You can open the inspector UI in different ways: +## 🤝 Contributing -```dart -// Open directly -ElevatedButton( - child: Text("Open Inspector"), - onPressed: alice.showInspector, -) +We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details. -// Or call from anywhere in your code -alice.showInspector(); -``` +## 📄 License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. + +## ⭐ Support + +If you find Alice helpful, please give it a ⭐ on [GitHub](https://github.com/hautvfami/flutter-alice)! Your support motivates us to keep improving. + +For issues or questions, open an issue on GitHub. diff --git a/assets/JetBrainsMono.ttf b/assets/JetBrainsMono.ttf new file mode 100644 index 0000000..4c96e79 Binary files /dev/null and b/assets/JetBrainsMono.ttf differ diff --git a/example/.metadata b/example/.metadata index 07763f7..a2aec35 100644 --- a/example/.metadata +++ b/example/.metadata @@ -4,7 +4,33 @@ # This file should be version controlled and should not be manually edited. version: - revision: 8661d8aecd626f7f57ccbcb735553edc05a2e713 - channel: stable + revision: "edada7c56edf4a183c1735310e123c7f923584f1" + channel: "stable" project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: edada7c56edf4a183c1735310e123c7f923584f1 + base_revision: edada7c56edf4a183c1735310e123c7f923584f1 + - platform: android + create_revision: edada7c56edf4a183c1735310e123c7f923584f1 + base_revision: edada7c56edf4a183c1735310e123c7f923584f1 + - platform: ios + create_revision: edada7c56edf4a183c1735310e123c7f923584f1 + base_revision: edada7c56edf4a183c1735310e123c7f923584f1 + - platform: web + create_revision: edada7c56edf4a183c1735310e123c7f923584f1 + base_revision: edada7c56edf4a183c1735310e123c7f923584f1 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/example/analysis_options.yaml b/example/analysis_options.yaml new file mode 100644 index 0000000..d4a714a --- /dev/null +++ b/example/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +# include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/example/android/.gitignore b/example/android/.gitignore new file mode 100644 index 0000000..be3943c --- /dev/null +++ b/example/android/.gitignore @@ -0,0 +1,14 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java +.cxx/ + +# Remember to never publicly share your keystore. +# See https://flutter.dev/to/reference-keystore +key.properties +**/*.keystore +**/*.jks diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle deleted file mode 100644 index 7e3bd9d..0000000 --- a/example/android/app/build.gradle +++ /dev/null @@ -1,67 +0,0 @@ -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 - - sourceSets { - main.java.srcDirs += 'src/main/kotlin' - } - - lintOptions { - disable 'InvalidPackage' - } - - defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.jhomlala.alice_example" - minSdkVersion 22 - targetSdkVersion 30 - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" - } - - 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" - testImplementation 'junit:junit:4.12' - androidTestImplementation 'com.android.support.test:runner:1.0.2' - androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' -} diff --git a/example/android/app/build.gradle.kts b/example/android/app/build.gradle.kts new file mode 100644 index 0000000..64f064f --- /dev/null +++ b/example/android/app/build.gradle.kts @@ -0,0 +1,44 @@ +plugins { + id("com.android.application") + id("kotlin-android") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "com.example.alice_example" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11.toString() + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "vn.alice" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.getByName("debug") + } + } +} + +flutter { + source = "../.." +} diff --git a/example/android/app/src/debug/AndroidManifest.xml b/example/android/app/src/debug/AndroidManifest.xml index 1b21b04..399f698 100644 --- a/example/android/app/src/debug/AndroidManifest.xml +++ b/example/android/app/src/debug/AndroidManifest.xml @@ -1,6 +1,6 @@ - - diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index 0ea0471..a321e75 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -1,39 +1,45 @@ - - - - - + - + - + android:name="io.flutter.embedding.android.NormalTheme" + android:resource="@style/NormalTheme" + /> + + + + + + + + + diff --git a/example/android/app/src/main/kotlin/com/example/alice_example/MainActivity.kt b/example/android/app/src/main/kotlin/com/example/alice_example/MainActivity.kt new file mode 100644 index 0000000..1bf8c52 --- /dev/null +++ b/example/android/app/src/main/kotlin/com/example/alice_example/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.alice_example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/example/android/app/src/main/kotlin/com/jhomlala/alice_example/MainActivity.kt b/example/android/app/src/main/kotlin/com/jhomlala/alice_example/MainActivity.kt deleted file mode 100644 index dac54f8..0000000 --- a/example/android/app/src/main/kotlin/com/jhomlala/alice_example/MainActivity.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.jhomlala.alice_example - -import io.flutter.embedding.android.FlutterActivity -import android.os.Bundle -import io.flutter.embedding.engine.FlutterEngine - import io.flutter.embedding.android.FlutterFragmentActivity; - -class MainActivity: FlutterFragmentActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - } -} diff --git a/example/android/app/src/main/res/drawable-v21/launch_background.xml b/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png index d9326d7..db77bb4 100644 Binary files a/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png index 5d11e4c..17987b7 100644 Binary files a/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png index e305136..09d4391 100644 Binary files a/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index e2f445c..d5f1c8d 100644 Binary files a/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index 5c203f9..4d6372e 100644 Binary files a/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/example/android/app/src/main/res/values-night/styles.xml b/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/example/android/app/src/main/res/values/styles.xml b/example/android/app/src/main/res/values/styles.xml index 00fa441..cb1ef88 100644 --- a/example/android/app/src/main/res/values/styles.xml +++ b/example/android/app/src/main/res/values/styles.xml @@ -1,8 +1,18 @@ - + + diff --git a/example/android/app/src/main/res/xml/network_security_config.xml b/example/android/app/src/main/res/xml/network_security_config.xml deleted file mode 100644 index f18e1f0..0000000 --- a/example/android/app/src/main/res/xml/network_security_config.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/example/android/app/src/profile/AndroidManifest.xml b/example/android/app/src/profile/AndroidManifest.xml index 1b21b04..399f698 100644 --- a/example/android/app/src/profile/AndroidManifest.xml +++ b/example/android/app/src/profile/AndroidManifest.xml @@ -1,6 +1,6 @@ - - diff --git a/example/android/build.gradle b/example/android/build.gradle deleted file mode 100644 index 724686c..0000000 --- a/example/android/build.gradle +++ /dev/null @@ -1,31 +0,0 @@ -buildscript { - ext.kotlin_version = '1.8.10' - repositories { - google() - jcenter() - } - - dependencies { - classpath 'com.android.tools.build:gradle:7.1.2' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - -allprojects { - repositories { - google() - jcenter() - } -} - -rootProject.buildDir = '../build' -subprojects { - project.buildDir = "${rootProject.buildDir}/${project.name}" -} -subprojects { - project.evaluationDependsOn(':app') -} - -tasks.register("clean", Delete) { - delete rootProject.buildDir -} diff --git a/example/android/build.gradle.kts b/example/android/build.gradle.kts new file mode 100644 index 0000000..89176ef --- /dev/null +++ b/example/android/build.gradle.kts @@ -0,0 +1,21 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/example/android/gradle.properties b/example/android/gradle.properties index a673820..f018a61 100644 --- a/example/android/gradle.properties +++ b/example/android/gradle.properties @@ -1,4 +1,3 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true -android.enableR8=true diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties index 8958ae2..ac3b479 100644 --- a/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Fri Jun 23 08:50:38 CEST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https://services.gradle.org/distributions/gradle-7.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip diff --git a/example/android/settings.gradle b/example/android/settings.gradle deleted file mode 100644 index 5a2f14f..0000000 --- a/example/android/settings.gradle +++ /dev/null @@ -1,15 +0,0 @@ -include ':app' - -def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() - -def plugins = new Properties() -def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') -if (pluginsFile.exists()) { - pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } -} - -plugins.each { name, path -> - def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() - include ":$name" - project(":$name").projectDir = pluginDirectory -} diff --git a/example/android/settings.gradle.kts b/example/android/settings.gradle.kts new file mode 100644 index 0000000..ab39a10 --- /dev/null +++ b/example/android/settings.gradle.kts @@ -0,0 +1,25 @@ +pluginManagement { + val flutterSdkPath = run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.7.3" apply false + id("org.jetbrains.kotlin.android") version "2.1.0" apply false +} + +include(":app") diff --git a/example/assets/green.png b/example/assets/green.png new file mode 100644 index 0000000..728cd3a Binary files /dev/null and b/example/assets/green.png differ diff --git a/example/ios/.gitignore b/example/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/example/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/example/ios/Flutter/Debug.xcconfig b/example/ios/Flutter/Debug.xcconfig index e8efba1..ec97fc6 100644 --- a/example/ios/Flutter/Debug.xcconfig +++ b/example/ios/Flutter/Debug.xcconfig @@ -1,2 +1,2 @@ -#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/example/ios/Flutter/Release.xcconfig b/example/ios/Flutter/Release.xcconfig index 399e934..c4855bf 100644 --- a/example/ios/Flutter/Release.xcconfig +++ b/example/ios/Flutter/Release.xcconfig @@ -1,2 +1,2 @@ -#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/example/ios/Flutter/ephemeral/flutter_lldb_helper.py b/example/ios/Flutter/ephemeral/flutter_lldb_helper.py new file mode 100644 index 0000000..a88caf9 --- /dev/null +++ b/example/ios/Flutter/ephemeral/flutter_lldb_helper.py @@ -0,0 +1,32 @@ +# +# Generated file, do not edit. +# + +import lldb + +def handle_new_rx_page(frame: lldb.SBFrame, bp_loc, extra_args, intern_dict): + """Intercept NOTIFY_DEBUGGER_ABOUT_RX_PAGES and touch the pages.""" + base = frame.register["x0"].GetValueAsAddress() + page_len = frame.register["x1"].GetValueAsUnsigned() + + # Note: NOTIFY_DEBUGGER_ABOUT_RX_PAGES will check contents of the + # first page to see if handled it correctly. This makes diagnosing + # misconfiguration (e.g. missing breakpoint) easier. + data = bytearray(page_len) + data[0:8] = b'IHELPED!' + + error = lldb.SBError() + frame.GetThread().GetProcess().WriteMemory(base, data, error) + if not error.Success(): + print(f'Failed to write into {base}[+{page_len}]', error) + return + +def __lldb_init_module(debugger: lldb.SBDebugger, _): + target = debugger.GetDummyTarget() + # Caveat: must use BreakpointCreateByRegEx here and not + # BreakpointCreateByName. For some reasons callback function does not + # get carried over from dummy target for the later. + bp = target.BreakpointCreateByRegex("^NOTIFY_DEBUGGER_ABOUT_RX_PAGES$") + bp.SetScriptCallbackFunction('{}.handle_new_rx_page'.format(__name__)) + bp.SetAutoContinue(True) + print("-- LLDB integration loaded --") diff --git a/example/ios/Flutter/ephemeral/flutter_lldbinit b/example/ios/Flutter/ephemeral/flutter_lldbinit new file mode 100644 index 0000000..e3ba6fb --- /dev/null +++ b/example/ios/Flutter/ephemeral/flutter_lldbinit @@ -0,0 +1,5 @@ +# +# Generated file, do not edit. +# + +command script import --relative-to-command-file flutter_lldb_helper.py diff --git a/example/ios/Flutter/flutter_export_environment.sh b/example/ios/Flutter/flutter_export_environment.sh index 14549a9..1e1886a 100755 --- a/example/ios/Flutter/flutter_export_environment.sh +++ b/example/ios/Flutter/flutter_export_environment.sh @@ -1,13 +1,13 @@ #!/bin/sh # This is a generated file; do not edit or check into version control. -export "FLUTTER_ROOT=/Users/hautran/fvm/versions/3.24.5" +export "FLUTTER_ROOT=/Users/hautran/fvm/versions/3.32.8" export "FLUTTER_APPLICATION_PATH=/Users/hautran/Dev/1.FlutterProject/3.contribute/flutter-alice/example" export "COCOAPODS_PARALLEL_CODE_SIGN=true" export "FLUTTER_TARGET=/Users/hautran/Dev/1.FlutterProject/3.contribute/flutter-alice/example/lib/main.dart" export "FLUTTER_BUILD_DIR=build" export "FLUTTER_BUILD_NAME=1.0.0" export "FLUTTER_BUILD_NUMBER=1" -export "DART_DEFINES=Zmx1dHRlci5pbnNwZWN0b3Iuc3RydWN0dXJlZEVycm9ycz10cnVl" +export "DART_DEFINES=RkxVVFRFUl9WRVJTSU9OPTMuMzIuOA==,RkxVVFRFUl9DSEFOTkVMPXN0YWJsZQ==,RkxVVFRFUl9HSVRfVVJMPWh0dHBzOi8vZ2l0aHViLmNvbS9mbHV0dGVyL2ZsdXR0ZXIuZ2l0,RkxVVFRFUl9GUkFNRVdPUktfUkVWSVNJT049ZWRhZGE3YzU2ZQ==,RkxVVFRFUl9FTkdJTkVfUkVWSVNJT049ZWYwY2QwMDA5MQ==,RkxVVFRFUl9EQVJUX1ZFUlNJT049My44LjE=" export "DART_OBFUSCATION=false" export "TRACK_WIDGET_CREATION=true" export "TREE_SHAKE_ICONS=false" diff --git a/example/ios/Podfile b/example/ios/Podfile index 279576f..e549ee2 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -29,9 +29,11 @@ flutter_ios_podfile_setup target 'Runner' do use_frameworks! - use_modular_headers! flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end end post_install do |installer| diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index ce11366..312e38b 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -22,8 +22,8 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - share_plus: 011d6fb4f9d2576b83179a3a5c5e323202cdabcf + share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a -PODFILE CHECKSUM: c4c93c5f6502fe2754f48404d3594bf779584011 +PODFILE CHECKSUM: 4305caec6b40dde0ae97be1573c53de1882a07e5 COCOAPODS: 1.16.2 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 1e39abd..f2b78d9 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -8,15 +8,26 @@ /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 6B958986CAB602C858FE6B59 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EC19C0B6115A9E8939C7C996 /* Pods_Runner.framework */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; - 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; }; 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 */; }; + C01B00B9DF7F63F4D941E78E /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3D4E525D62CCBD2508BE06D9 /* Pods_RunnerTests.framework */; }; + C7FCFC87D28EB4558F816BC4 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EB119E7ABB313798F8BA40B7 /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; @@ -31,15 +42,19 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 08E332AACEB99AD72A3F8619 /* 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 = ""; }; - 25444A9D424391D6E8536DCF /* 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 = ""; }; + 2DF791A87548F4EABE5951DD /* 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 = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 3BFE602774EE702D681FEE86 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + 3D4E525D62CCBD2508BE06D9 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 7104838527ED0366EBEBDDCA /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; 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 = ""; }; - 779DAC330BE08433AF9A74E1 /* 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 = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 7DCD23CB1C6D375D39172260 /* 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; }; @@ -47,32 +62,61 @@ 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 = ""; }; - EC19C0B6115A9E8939C7C996 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 97FEFF0021DE46A0CB178370 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + DD8C49CFC99772A22F0C8753 /* 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 = ""; }; + EB119E7ABB313798F8BA40B7 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 806975876B20777ECD964079 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + C01B00B9DF7F63F4D941E78E /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 6B958986CAB602C858FE6B59 /* Pods_Runner.framework in Frameworks */, + C7FCFC87D28EB4558F816BC4 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 487DE26B11C73764E3C4F6A8 /* Pods */ = { + 0483DD7CE7F0702F6CA3199C /* Frameworks */ = { isa = PBXGroup; children = ( - 08E332AACEB99AD72A3F8619 /* Pods-Runner.debug.xcconfig */, - 779DAC330BE08433AF9A74E1 /* Pods-Runner.release.xcconfig */, - 25444A9D424391D6E8536DCF /* Pods-Runner.profile.xcconfig */, + EB119E7ABB313798F8BA40B7 /* Pods_Runner.framework */, + 3D4E525D62CCBD2508BE06D9 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 21303BF27B78BFF194992E4F /* Pods */ = { + isa = PBXGroup; + children = ( + 2DF791A87548F4EABE5951DD /* Pods-Runner.debug.xcconfig */, + 7DCD23CB1C6D375D39172260 /* Pods-Runner.release.xcconfig */, + DD8C49CFC99772A22F0C8753 /* Pods-Runner.profile.xcconfig */, + 7104838527ED0366EBEBDDCA /* Pods-RunnerTests.debug.xcconfig */, + 97FEFF0021DE46A0CB178370 /* Pods-RunnerTests.release.xcconfig */, + 3BFE602774EE702D681FEE86 /* Pods-RunnerTests.profile.xcconfig */, ); - name = Pods; path = Pods; sourceTree = ""; }; + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -90,8 +134,9 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, - 487DE26B11C73764E3C4F6A8 /* Pods */, - F899CA7FF200E9475C9511C3 /* Frameworks */, + 331C8082294A63A400263BE5 /* RunnerTests */, + 21303BF27B78BFF194992E4F /* Pods */, + 0483DD7CE7F0702F6CA3199C /* Frameworks */, ); sourceTree = ""; }; @@ -99,6 +144,7 @@ isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; @@ -110,7 +156,6 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, - 97C146F11CF9000F007C117D /* Supporting Files */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, @@ -119,36 +164,40 @@ path = Runner; sourceTree = ""; }; - 97C146F11CF9000F007C117D /* Supporting Files */ = { - isa = PBXGroup; - children = ( - ); - name = "Supporting Files"; - sourceTree = ""; - }; - F899CA7FF200E9475C9511C3 /* Frameworks */ = { - isa = PBXGroup; - children = ( - EC19C0B6115A9E8939C7C996 /* Pods_Runner.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 67029E755E4DC85D8827A55B /* [CP] Check Pods Manifest.lock */, + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + 806975876B20777ECD964079 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - BDC52C5570F08DC227515F97 /* [CP] Check Pods Manifest.lock */, + 6C503A9C17F34E55CF5CCC30 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - D378D963650F641F6C85171A /* [CP] Embed Pods Frameworks */, + 3B3126F51E00359E716B3020 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -165,18 +214,23 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; LastUpgradeCheck = 1510; - ORGANIZATIONNAME = "The Chromium Authors"; + ORGANIZATIONNAME = ""; TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; - LastSwiftMigration = 0910; + LastSwiftMigration = 1100; }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, @@ -188,18 +242,25 @@ projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, - 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); @@ -224,22 +285,46 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; - 9740EEB61CF901F6004384FC /* Run Script */ = { + 3B3126F51E00359E716B3020 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; 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; + }; + 67029E755E4DC85D8827A55B /* [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 = ( ); - name = "Run Script"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + 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; }; - BDC52C5570F08DC227515F97 /* [CP] Check Pods Manifest.lock */ = { + 6C503A9C17F34E55CF5CCC30 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -261,29 +346,32 @@ 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; }; - D378D963650F641F6C85171A /* [CP] Embed Pods Frameworks */ = { + 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/path_provider_foundation/path_provider_foundation.framework", - "${BUILT_PRODUCTS_DIR}/share_plus/share_plus.framework", ); - name = "[CP] Embed Pods Frameworks"; + name = "Run Script"; outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider_foundation.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/share_plus.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; - showEnvVarsInLog = 0; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -295,6 +383,14 @@ }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; @@ -319,6 +415,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -328,12 +425,14 @@ 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; @@ -346,6 +445,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -357,6 +457,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -367,33 +468,78 @@ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = S8QB4VV633; + DEVELOPMENT_TEAM = WJ5AB4BFT3; ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.jhomlala.aliceExample; + PRODUCT_BUNDLE_IDENTIFIER = vn.alice; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.0; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Profile; }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7104838527ED0366EBEBDDCA /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = vn.alice; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 97FEFF0021DE46A0CB178370 /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = vn.alice; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 3BFE602774EE702D681FEE86 /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = vn.alice; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -403,12 +549,14 @@ 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; @@ -421,6 +569,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -447,6 +596,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -456,12 +606,14 @@ 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; @@ -474,6 +626,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -485,7 +638,9 @@ IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -498,26 +653,18 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = WJ5AB4BFT3; ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.jhomlala.aliceExample; + PRODUCT_BUNDLE_IDENTIFIER = vn.alice; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_SWIFT3_OBJC_INFERENCE = On; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; @@ -529,25 +676,17 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = WJ5AB4BFT3; ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.jhomlala.aliceExample; + PRODUCT_BUNDLE_IDENTIFIER = vn.alice; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_SWIFT3_OBJC_INFERENCE = On; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; @@ -555,6 +694,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a00db7a..e3773d4 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -26,10 +26,8 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - language = "" + customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit" shouldUseLaunchSchemeArgsEnv = "YES"> - - - - + + + + + + @@ -63,8 +73,6 @@ ReferencedContainer = "container:Runner.xcodeproj"> - - + + + + IDEDidComputeMac32BitWarning + + + diff --git a/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/example/ios/Runner/AppDelegate.swift b/example/ios/Runner/AppDelegate.swift index f1dc486..6266644 100644 --- a/example/ios/Runner/AppDelegate.swift +++ b/example/ios/Runner/AppDelegate.swift @@ -1,11 +1,11 @@ -import UIKit import Flutter +import UIKit @main @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]? + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json index d36b1fa..66aef39 100644 --- a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,122 +1,116 @@ { "images" : [ { - "size" : "20x20", + "filename" : "green-20@2x.png", "idiom" : "iphone", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" + "scale" : "2x", + "size" : "20x20" }, { - "size" : "20x20", + "filename" : "green-20@3x.png", "idiom" : "iphone", - "filename" : "Icon-App-20x20@3x.png", - "scale" : "3x" + "scale" : "3x", + "size" : "20x20" }, { - "size" : "29x29", + "filename" : "green-29@2x.png", "idiom" : "iphone", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" + "scale" : "2x", + "size" : "29x29" }, { - "size" : "29x29", + "filename" : "green-29@3x.png", "idiom" : "iphone", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" + "scale" : "3x", + "size" : "29x29" }, { - "size" : "29x29", + "filename" : "green-40@2x.png", "idiom" : "iphone", - "filename" : "Icon-App-29x29@3x.png", - "scale" : "3x" + "scale" : "2x", + "size" : "40x40" }, { - "size" : "40x40", + "filename" : "green-40@3x.png", "idiom" : "iphone", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" + "scale" : "3x", + "size" : "40x40" }, { - "size" : "40x40", + "filename" : "green-60@2x.png", "idiom" : "iphone", - "filename" : "Icon-App-40x40@3x.png", - "scale" : "3x" + "scale" : "2x", + "size" : "60x60" }, { - "size" : "60x60", + "filename" : "green-60@3x.png", "idiom" : "iphone", - "filename" : "Icon-App-60x60@2x.png", - "scale" : "2x" + "scale" : "3x", + "size" : "60x60" }, { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@3x.png", - "scale" : "3x" - }, - { - "size" : "20x20", + "filename" : "green-20.png", "idiom" : "ipad", - "filename" : "Icon-App-20x20@1x.png", - "scale" : "1x" + "scale" : "1x", + "size" : "20x20" }, { - "size" : "20x20", + "filename" : "green-20@2x.png", "idiom" : "ipad", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" + "scale" : "2x", + "size" : "20x20" }, { - "size" : "29x29", + "filename" : "green-29.png", "idiom" : "ipad", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" + "scale" : "1x", + "size" : "29x29" }, { - "size" : "29x29", + "filename" : "green-29@2x.png", "idiom" : "ipad", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" + "scale" : "2x", + "size" : "29x29" }, { - "size" : "40x40", + "filename" : "green-40.png", "idiom" : "ipad", - "filename" : "Icon-App-40x40@1x.png", - "scale" : "1x" + "scale" : "1x", + "size" : "40x40" }, { - "size" : "40x40", + "filename" : "green-40@2x.png", "idiom" : "ipad", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" + "scale" : "2x", + "size" : "40x40" }, { - "size" : "76x76", + "filename" : "green-76.png", "idiom" : "ipad", - "filename" : "Icon-App-76x76@1x.png", - "scale" : "1x" + "scale" : "1x", + "size" : "76x76" }, { - "size" : "76x76", + "filename" : "green-76@2x.png", "idiom" : "ipad", - "filename" : "Icon-App-76x76@2x.png", - "scale" : "2x" + "scale" : "2x", + "size" : "76x76" }, { - "size" : "83.5x83.5", + "filename" : "green-83.5@2x.png", "idiom" : "ipad", - "filename" : "Icon-App-83.5x83.5@2x.png", - "scale" : "2x" + "scale" : "2x", + "size" : "83.5x83.5" }, { - "size" : "1024x1024", + "filename" : "green-1024.png", "idiom" : "ios-marketing", - "filename" : "Icon-App-1024x1024@1x.png", - "scale" : "1x" + "scale" : "1x", + "size" : "1024x1024" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } } diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png deleted file mode 100644 index 3d43d11..0000000 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and /dev/null differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png deleted file mode 100644 index 28c6bf0..0000000 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and /dev/null differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png deleted file mode 100644 index 2ccbfd9..0000000 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and /dev/null differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png deleted file mode 100644 index f091b6b..0000000 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and /dev/null differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png deleted file mode 100644 index 4cde121..0000000 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and /dev/null differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png deleted file mode 100644 index d0ef06e..0000000 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and /dev/null differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png deleted file mode 100644 index dcdc230..0000000 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and /dev/null differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png deleted file mode 100644 index 2ccbfd9..0000000 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and /dev/null differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png deleted file mode 100644 index c8f9ed8..0000000 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and /dev/null differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png deleted file mode 100644 index a6d6b86..0000000 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and /dev/null differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png deleted file mode 100644 index a6d6b86..0000000 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and /dev/null differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png deleted file mode 100644 index 75b2d16..0000000 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and /dev/null differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png deleted file mode 100644 index c4df70d..0000000 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and /dev/null differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png deleted file mode 100644 index 6a84f41..0000000 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and /dev/null differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png deleted file mode 100644 index d0e1f58..0000000 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and /dev/null differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/green-1024.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/green-1024.png new file mode 100644 index 0000000..e0777b7 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/green-1024.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/green-20.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/green-20.png new file mode 100644 index 0000000..6225f1d Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/green-20.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/green-20@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/green-20@2x.png new file mode 100644 index 0000000..e2ca813 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/green-20@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/green-20@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/green-20@3x.png new file mode 100644 index 0000000..6cccac8 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/green-20@3x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/green-29.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/green-29.png new file mode 100644 index 0000000..9af3958 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/green-29.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/green-29@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/green-29@2x.png new file mode 100644 index 0000000..d15c498 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/green-29@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/green-29@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/green-29@3x.png new file mode 100644 index 0000000..f28914a Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/green-29@3x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/green-40.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/green-40.png new file mode 100644 index 0000000..e2ca813 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/green-40.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/green-40@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/green-40@2x.png new file mode 100644 index 0000000..2d82bee Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/green-40@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/green-40@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/green-40@3x.png new file mode 100644 index 0000000..340da81 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/green-40@3x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/green-60@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/green-60@2x.png new file mode 100644 index 0000000..340da81 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/green-60@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/green-60@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/green-60@3x.png new file mode 100644 index 0000000..18e75f3 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/green-60@3x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/green-76.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/green-76.png new file mode 100644 index 0000000..873d1e5 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/green-76.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/green-76@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/green-76@2x.png new file mode 100644 index 0000000..a6d2b29 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/green-76@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/green-83.5@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/green-83.5@2x.png new file mode 100644 index 0000000..eec4481 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/green-83.5@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/Contents.json b/example/ios/Runner/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/example/ios/Runner/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist index 150c820..e610c05 100644 --- a/example/ios/Runner/Info.plist +++ b/example/ios/Runner/Info.plist @@ -3,7 +3,9 @@ CFBundleDevelopmentRegion - en + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Alice CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -11,7 +13,7 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleName - alice_example + Alice CFBundlePackageType APPL CFBundleShortVersionString @@ -39,8 +41,6 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - UIViewControllerBasedStatusBarAppearance - CADisableMinimumFrameDurationOnPhone UIApplicationSupportsIndirectInputEvents diff --git a/example/ios/Runner/Runner-Bridging-Header.h b/example/ios/Runner/Runner-Bridging-Header.h index 7335fdf..308a2a5 100644 --- a/example/ios/Runner/Runner-Bridging-Header.h +++ b/example/ios/Runner/Runner-Bridging-Header.h @@ -1 +1 @@ -#import "GeneratedPluginRegistrant.h" \ No newline at end of file +#import "GeneratedPluginRegistrant.h" diff --git a/example/ios/RunnerTests/RunnerTests.swift b/example/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/example/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/example/lib/home_page.dart b/example/lib/home_page.dart new file mode 100644 index 0000000..eb300ce --- /dev/null +++ b/example/lib/home_page.dart @@ -0,0 +1,305 @@ +import 'dart:convert'; + +import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; + +import 'main.dart'; +import 'package:http/http.dart' as http; +import 'package:flutter_alice/core/alice_http_client_extensions.dart'; +import 'package:flutter_alice/core/alice_http_extensions.dart'; +// import 'package:overlay_support/overlay_support.dart'; + +class HomePage extends StatelessWidget { + const HomePage({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + appBar: AppBar( + title: const Text('Alice Inspector'), + backgroundColor: Colors.white, + ), + body: SingleChildScrollView( + padding: EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const SizedBox(height: 24), + Image.asset('assets/green.png', height: 200), + const SizedBox(height: 24), + _textWidget( + "Click \"TEST\" button to attach Alice interceptor to Dio\n" + " Click \"Open Alice Inspector\" or green bubble to show inspector.", + ), + ElevatedButton( + child: Text("TEST", style: TextStyle(color: Colors.white)), + style: _primaryButtonStyle, + onPressed: _runDioRequests, + ), + ElevatedButton( + style: _secondaryButtonStyle, + child: Text("Open Alice Inspector"), + onPressed: alice.showInspector, + ), + + /// Generate sample request + const SizedBox(height: 64), + _textWidget("Click buttons below to generate sample data."), + ElevatedButton( + child: Text("Run Dio HTTP Requests"), + style: _secondaryButtonStyle, + onPressed: _runDioRequests, + ), + ElevatedButton( + child: Text("Run http/http HTTP Requests"), + style: _secondaryButtonStyle, + onPressed: _runHttpHttpRequests, + ), + ElevatedButton( + child: Text("Run HttpClient Requests"), + style: _secondaryButtonStyle, + onPressed: _runHttpHttpClientRequests, + ), + // ElevatedButton( + // child: Text("Run Chopper HTTP Requests"), + // onPressed: _runChopperHttpRequests, + // ), + ].separatedBy(const SizedBox(height: 8)), + ), + ), + ); + } + + Widget _textWidget(String text) { + return Text( + text, + style: TextStyle(fontSize: 14), + textAlign: TextAlign.center, + ); + } + + ButtonStyle get _primaryButtonStyle { + return ButtonStyle( + minimumSize: WidgetStateProperty.all(Size(double.infinity, 48)), + backgroundColor: WidgetStateProperty.all(Colors.green), + shape: WidgetStateProperty.all(_shape), + textStyle: WidgetStateProperty.all( + TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), + ); + } + + ButtonStyle get _secondaryButtonStyle { + return _primaryButtonStyle.copyWith( + backgroundColor: WidgetStateProperty.all(Colors.white), + foregroundColor: WidgetStateProperty.all(Colors.green), + ); + } + + RoundedRectangleBorder get _shape => RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ); + + // void _runChopperHttpRequests() async { + // Map body = {"title": "foo", "body": "bar", "userId": "1"}; + // _postsService.getPost("1"); + // _postsService.postPost(body); + // _postsService.putPost("1", body); + // _postsService.putPost("1231923", body); + // _postsService.putPost("1", null); + // _postsService.postPost(null); + // _postsService.getPost("123456"); + // } + + void _runDioRequests() async { + Map body = {"title": "foo", "body": "bar", "userId": "1"}; + dio.get( + "https://api.themoviedb.org/3/search/movie?query=Jack+Reacher", + queryParameters: {"abc": 123}, + data: {"data": "data"}, + options: Options(headers: {"app-id": 1}), + ); + dio.get("https://httpbin.org/redirect-to?url=https%3A%2F%2Fhttpbin.org"); + dio.delete("https://httpbin.org/status/500"); + dio.delete("https://httpbin.org/status/400"); + dio.delete("https://httpbin.org/status/300"); + dio.delete("https://httpbin.org/status/200"); + dio.delete("https://httpbin.org/status/100"); + dio.post("https://jsonplaceholder.typicode.com/posts", data: body); + dio.get("https://jsonplaceholder.typicode.com/posts", + queryParameters: {"test": 1}); + dio.put("https://jsonplaceholder.typicode.com/posts/1", data: body); + dio.put("https://jsonplaceholder.typicode.com/posts/1", data: body); + dio.delete("https://jsonplaceholder.typicode.com/posts/1"); + dio.get("http://jsonplaceholder.typicode.com/test/test"); + + dio.get("https://jsonplaceholder.typicode.com/photos"); + dio.get( + "https://icons.iconarchive.com/icons/paomedia/small-n-flat/256/sign-info-icon.png"); + dio.get( + "https://images.unsplash.com/photo-1542736705-53f0131d1e98?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&w=1000&q=80"); + dio.get( + "https://findicons.com/files/icons/1322/world_of_aqua_5/128/bluetooth.png"); + dio.get( + "https://upload.wikimedia.org/wikipedia/commons/4/4e/Pleiades_large.jpg"); + dio.get("http://techslides.com/demos/sample-videos/small.mp4"); + + dio.get("https://www.cse.wustl.edu/~jain/cis677-97/ftp/e_3dlc2.pdf"); + + dio.get("http://dummy.restapiexample.com/api/v1/employees"); + dio.get( + "https://api.lyrics.ovh/v1/Coldplay/Adventure of a Lifetime?artist=Coldplay&title=Adventure of a Lifetime"); + } + + void _runHttpHttpRequests() async { + Map body = {"title": "foo", "body": "bar", "userId": "1"}; + http + .post(Uri.parse('https://jsonplaceholder.typicode.com/posts'), + body: body) + .interceptWithAlice(alice, body: body); + + http + .get(Uri.parse('https://jsonplaceholder.typicode.com/posts')) + .interceptWithAlice(alice); + + http + .put(Uri.parse('https://jsonplaceholder.typicode.com/posts/1'), + body: body) + .interceptWithAlice(alice, body: body); + + http + .patch(Uri.parse('https://jsonplaceholder.typicode.com/posts/1'), + body: body) + .interceptWithAlice(alice, body: body); + + http + .delete(Uri.parse('https://jsonplaceholder.typicode.com/posts/1')) + .interceptWithAlice(alice, body: body); + + http + .get(Uri.parse('https://jsonplaceholder.typicode.com/test/test')) + .interceptWithAlice(alice); + + http + .post(Uri.parse('https://jsonplaceholder.typicode.com/posts'), + body: body) + .then((response) { + alice.onHttpResponse(response, body: body); + }); + + http + .get(Uri.parse('https://jsonplaceholder.typicode.com/posts')) + .then((response) { + alice.onHttpResponse(response); + }); + + http + .put(Uri.parse('https://jsonplaceholder.typicode.com/posts/1'), + body: body) + .then((response) { + alice.onHttpResponse(response, body: body); + }); + + http + .patch(Uri.parse('https://jsonplaceholder.typicode.com/posts/1'), + body: body) + .then((response) { + alice.onHttpResponse(response, body: body); + }); + + http + .delete(Uri.parse('https://jsonplaceholder.typicode.com/posts/1')) + .then((response) { + alice.onHttpResponse(response); + }); + + http + .get(Uri.parse('https://jsonplaceholder.typicode.com/test/test')) + .then((response) { + alice.onHttpResponse(response); + }); + } + + void _runHttpHttpClientRequests() { + Map body = {"title": "foo", "body": "bar", "userId": "1"}; + httpClient + .getUrl(Uri.parse("https://jsonplaceholder.typicode.com/posts")) + .interceptWithAlice(alice); + + httpClient + .postUrl(Uri.parse("https://jsonplaceholder.typicode.com/posts")) + .interceptWithAlice(alice, body: body, headers: Map()); + + httpClient + .putUrl(Uri.parse("https://jsonplaceholder.typicode.com/posts/1")) + .interceptWithAlice(alice, body: body); + + httpClient + .getUrl(Uri.parse("https://jsonplaceholder.typicode.com/test/test/")) + .interceptWithAlice(alice); + + httpClient + .postUrl(Uri.parse("https://jsonplaceholder.typicode.com/posts")) + .then((request) async { + alice.onHttpClientRequest(request, body: body); + request.write(body); + var httpResponse = await request.close(); + var responseBody = await utf8.decoder.bind(httpResponse).join(); + alice.onHttpClientResponse(httpResponse, request, body: responseBody); + }); + + httpClient + .putUrl(Uri.parse("https://jsonplaceholder.typicode.com/posts/1")) + .then((request) async { + alice.onHttpClientRequest(request, body: body); + request.write(body); + var httpResponse = await request.close(); + var responseBody = await utf8.decoder.bind(httpResponse).join(); + alice.onHttpClientResponse(httpResponse, request, body: responseBody); + }); + + httpClient + .patchUrl(Uri.parse("https://jsonplaceholder.typicode.com/posts/1")) + .then((request) async { + alice.onHttpClientRequest(request, body: body); + request.write(body); + var httpResponse = await request.close(); + var responseBody = await utf8.decoder.bind(httpResponse).join(); + alice.onHttpClientResponse(httpResponse, request, body: responseBody); + }); + + httpClient + .deleteUrl(Uri.parse("https://jsonplaceholder.typicode.com/posts/1")) + .then((request) async { + alice.onHttpClientRequest(request); + var httpResponse = await request.close(); + var responseBody = await utf8.decoder.bind(httpResponse).join(); + alice.onHttpClientResponse(httpResponse, request, body: responseBody); + }); + + httpClient + .getUrl(Uri.parse("https://jsonplaceholder.typicode.com/test/test/")) + .then((request) async { + alice.onHttpClientRequest(request); + var httpResponse = await request.close(); + var responseBody = await utf8.decoder.bind(httpResponse).join(); + alice.onHttpClientResponse(httpResponse, request, body: responseBody); + }); + } +} + +extension WidgetListExtension on List { + List separatedBy(Widget separator) { + if (length < 2) return this; + List separatedList = []; + for (int i = 0; i < length; i++) { + separatedList.add(this[i]); + if (i < length - 1) { + separatedList.add(separator); + } + } + return separatedList; + } +} diff --git a/example/lib/main.dart b/example/lib/main.dart index 0721797..301cfc9 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,33 +1,23 @@ -import 'dart:convert'; import 'dart:io'; // import 'package:chopper/chopper.dart' hide Options; +import 'package:alice_example/home_page.dart'; import 'package:dio/dio.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_alice/alice.dart'; -import 'package:flutter_alice/core/alice_http_client_extensions.dart'; -import 'package:flutter_alice/core/alice_http_extensions.dart'; -import 'package:http/http.dart' as http; -import 'package:overlay_support/overlay_support.dart'; - -// import 'posts_service.dart'; // Navigator key final navigatorKey = GlobalKey(); -final alice = Alice(navigatorKey: navigatorKey); +final alice = Alice(); final dio = Dio(BaseOptions(followRedirects: false)); final httpClient = HttpClient(); -// late final PostsService _postsService; -// late final ChopperClient? _chopper; void main() { // Set up Alice to use the navigator key // alice.setNavigatorKey(navigatorKey); // Alice Capture all HTTP requests and responses in debug mode // Attach alice into Dio (only onetime) - if (kDebugMode) dio.interceptors.add(alice.getDioInterceptor()); // // // Capture for chopper // _chopper = ChopperClient( @@ -35,268 +25,27 @@ void main() { // ); // _postsService = PostsService.create(_chopper); - runApp(MyApp()); -} + // if (kDebugMode) dio.interceptors.add(alice.getDioInterceptor()); -class MyApp extends StatefulWidget { - @override - _MyAppState createState() => _MyAppState(); + // alice.clearLogs(); + alice.log("Alice is running!", color: Colors.yellow); + alice.log(StackTrace.current.toString(), color: Colors.yellow); + + runApp(MyApp()); } -class _MyAppState extends State { +class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { - /// Using OverlaySupport to show alice bubble - /// You need wrap your material app with OverlaySupport - return OverlaySupport( + return AliceInspector( + debug: true, + dios: [dio], + navigatorKey: navigatorKey, child: MaterialApp( navigatorKey: navigatorKey, - home: Scaffold( - appBar: AppBar(title: const Text('Alice Inspector')), - body: Padding( - padding: EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const SizedBox(height: 24), - _textWidget( - "Click \"TEST\" button to attach Alice interceptor to Dio\n" - " Click \"Open Alice Inspector\" or green bubble to show inspector.", - ), - ElevatedButton( - child: Text("TEST", style: TextStyle(color: Colors.white)), - style: ButtonStyle( - backgroundColor: WidgetStateProperty.all(Colors.green), - ), - onPressed: () { - _runDioRequests(); - }, - ), - ElevatedButton( - child: Text("Open Alice Inspector"), - onPressed: alice.showInspector, - ), - - /// Generate sample request - const SizedBox(height: 64), - _textWidget("Click buttons below to generate sample data."), - ElevatedButton( - child: Text("Run Dio HTTP Requests"), - onPressed: _runDioRequests, - ), - ElevatedButton( - child: Text("Run http/http HTTP Requests"), - onPressed: _runHttpHttpRequests, - ), - ElevatedButton( - child: Text("Run HttpClient Requests"), - onPressed: _runHttpHttpClientRequests, - ), - // ElevatedButton( - // child: Text("Run Chopper HTTP Requests"), - // onPressed: _runChopperHttpRequests, - // ), - ], - ), - ), - ), + debugShowCheckedModeBanner: false, + home: HomePage(), ), ); } - - Widget _textWidget(String text) { - return Text( - text, - style: TextStyle(fontSize: 14), - textAlign: TextAlign.center, - ); - } - - // void _runChopperHttpRequests() async { - // Map body = {"title": "foo", "body": "bar", "userId": "1"}; - // _postsService.getPost("1"); - // _postsService.postPost(body); - // _postsService.putPost("1", body); - // _postsService.putPost("1231923", body); - // _postsService.putPost("1", null); - // _postsService.postPost(null); - // _postsService.getPost("123456"); - // } - - void _runDioRequests() async { - Map body = {"title": "foo", "body": "bar", "userId": "1"}; - dio.get( - "https://api.themoviedb.org/3/search/movie?query=Jack+Reacher", - queryParameters: {"abc": 123}, - data: {"data": "data"}, - options: Options(headers: {"app-id": 1}), - ); - dio.get("https://httpbin.org/redirect-to?url=https%3A%2F%2Fhttpbin.org"); - dio.delete("https://httpbin.org/status/500"); - dio.delete("https://httpbin.org/status/400"); - dio.delete("https://httpbin.org/status/300"); - dio.delete("https://httpbin.org/status/200"); - dio.delete("https://httpbin.org/status/100"); - dio.post("https://jsonplaceholder.typicode.com/posts", data: body); - dio.get("https://jsonplaceholder.typicode.com/posts", - queryParameters: {"test": 1}); - dio.put("https://jsonplaceholder.typicode.com/posts/1", data: body); - dio.put("https://jsonplaceholder.typicode.com/posts/1", data: body); - dio.delete("https://jsonplaceholder.typicode.com/posts/1"); - dio.get("http://jsonplaceholder.typicode.com/test/test"); - - dio.get("https://jsonplaceholder.typicode.com/photos"); - dio.get( - "https://icons.iconarchive.com/icons/paomedia/small-n-flat/256/sign-info-icon.png"); - dio.get( - "https://images.unsplash.com/photo-1542736705-53f0131d1e98?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&w=1000&q=80"); - dio.get( - "https://findicons.com/files/icons/1322/world_of_aqua_5/128/bluetooth.png"); - dio.get( - "https://upload.wikimedia.org/wikipedia/commons/4/4e/Pleiades_large.jpg"); - dio.get("http://techslides.com/demos/sample-videos/small.mp4"); - - dio.get("https://www.cse.wustl.edu/~jain/cis677-97/ftp/e_3dlc2.pdf"); - - dio.get("http://dummy.restapiexample.com/api/v1/employees"); - dio.get( - "https://api.lyrics.ovh/v1/Coldplay/Adventure of a Lifetime?artist=Coldplay&title=Adventure of a Lifetime"); - } - - void _runHttpHttpRequests() async { - Map body = {"title": "foo", "body": "bar", "userId": "1"}; - http - .post(Uri.parse('https://jsonplaceholder.typicode.com/posts'), - body: body) - .interceptWithAlice(alice, body: body); - - http - .get(Uri.parse('https://jsonplaceholder.typicode.com/posts')) - .interceptWithAlice(alice); - - http - .put(Uri.parse('https://jsonplaceholder.typicode.com/posts/1'), - body: body) - .interceptWithAlice(alice, body: body); - - http - .patch(Uri.parse('https://jsonplaceholder.typicode.com/posts/1'), - body: body) - .interceptWithAlice(alice, body: body); - - http - .delete(Uri.parse('https://jsonplaceholder.typicode.com/posts/1')) - .interceptWithAlice(alice, body: body); - - http - .get(Uri.parse('https://jsonplaceholder.typicode.com/test/test')) - .interceptWithAlice(alice); - - http - .post(Uri.parse('https://jsonplaceholder.typicode.com/posts'), - body: body) - .then((response) { - alice.onHttpResponse(response, body: body); - }); - - http - .get(Uri.parse('https://jsonplaceholder.typicode.com/posts')) - .then((response) { - alice.onHttpResponse(response); - }); - - http - .put(Uri.parse('https://jsonplaceholder.typicode.com/posts/1'), - body: body) - .then((response) { - alice.onHttpResponse(response, body: body); - }); - - http - .patch(Uri.parse('https://jsonplaceholder.typicode.com/posts/1'), - body: body) - .then((response) { - alice.onHttpResponse(response, body: body); - }); - - http - .delete(Uri.parse('https://jsonplaceholder.typicode.com/posts/1')) - .then((response) { - alice.onHttpResponse(response); - }); - - http - .get(Uri.parse('https://jsonplaceholder.typicode.com/test/test')) - .then((response) { - alice.onHttpResponse(response); - }); - } - - void _runHttpHttpClientRequests() { - Map body = {"title": "foo", "body": "bar", "userId": "1"}; - httpClient - .getUrl(Uri.parse("https://jsonplaceholder.typicode.com/posts")) - .interceptWithAlice(alice); - - httpClient - .postUrl(Uri.parse("https://jsonplaceholder.typicode.com/posts")) - .interceptWithAlice(alice, body: body, headers: Map()); - - httpClient - .putUrl(Uri.parse("https://jsonplaceholder.typicode.com/posts/1")) - .interceptWithAlice(alice, body: body); - - httpClient - .getUrl(Uri.parse("https://jsonplaceholder.typicode.com/test/test/")) - .interceptWithAlice(alice); - - httpClient - .postUrl(Uri.parse("https://jsonplaceholder.typicode.com/posts")) - .then((request) async { - alice.onHttpClientRequest(request, body: body); - request.write(body); - var httpResponse = await request.close(); - var responseBody = await utf8.decoder.bind(httpResponse).join(); - alice.onHttpClientResponse(httpResponse, request, body: responseBody); - }); - - httpClient - .putUrl(Uri.parse("https://jsonplaceholder.typicode.com/posts/1")) - .then((request) async { - alice.onHttpClientRequest(request, body: body); - request.write(body); - var httpResponse = await request.close(); - var responseBody = await utf8.decoder.bind(httpResponse).join(); - alice.onHttpClientResponse(httpResponse, request, body: responseBody); - }); - - httpClient - .patchUrl(Uri.parse("https://jsonplaceholder.typicode.com/posts/1")) - .then((request) async { - alice.onHttpClientRequest(request, body: body); - request.write(body); - var httpResponse = await request.close(); - var responseBody = await utf8.decoder.bind(httpResponse).join(); - alice.onHttpClientResponse(httpResponse, request, body: responseBody); - }); - - httpClient - .deleteUrl(Uri.parse("https://jsonplaceholder.typicode.com/posts/1")) - .then((request) async { - alice.onHttpClientRequest(request); - var httpResponse = await request.close(); - var responseBody = await utf8.decoder.bind(httpResponse).join(); - alice.onHttpClientResponse(httpResponse, request, body: responseBody); - }); - - httpClient - .getUrl(Uri.parse("https://jsonplaceholder.typicode.com/test/test/")) - .then((request) async { - alice.onHttpClientRequest(request); - var httpResponse = await request.close(); - var responseBody = await utf8.decoder.bind(httpResponse).join(); - alice.onHttpClientResponse(httpResponse, request, body: responseBody); - }); - } } diff --git a/example/pubspec.yaml b/example/pubspec.yaml index ff4dbad..44acbe7 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -14,7 +14,7 @@ dependencies: dio: ^5.1.1 chopper: ^7.0.6 overlay_support: ^2.1.0 - share_plus: ^9.0.0 + share_plus: ^11.0.0 rxdart: ^0.27.1 dev_dependencies: @@ -35,9 +35,8 @@ flutter: uses-material-design: true # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg + assets: + - assets/ # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.io/assets-and-images/#resolution-aware. diff --git a/example/web/favicon.png b/example/web/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/example/web/favicon.png differ diff --git a/example/web/icons/Icon-192.png b/example/web/icons/Icon-192.png new file mode 100644 index 0000000..b749bfe Binary files /dev/null and b/example/web/icons/Icon-192.png differ diff --git a/example/web/icons/Icon-512.png b/example/web/icons/Icon-512.png new file mode 100644 index 0000000..88cfd48 Binary files /dev/null and b/example/web/icons/Icon-512.png differ diff --git a/example/web/icons/Icon-maskable-192.png b/example/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000..eb9b4d7 Binary files /dev/null and b/example/web/icons/Icon-maskable-192.png differ diff --git a/example/web/icons/Icon-maskable-512.png b/example/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000..d69c566 Binary files /dev/null and b/example/web/icons/Icon-maskable-512.png differ diff --git a/example/web/index.html b/example/web/index.html new file mode 100644 index 0000000..d5c8111 --- /dev/null +++ b/example/web/index.html @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + alice_example + + + + + + diff --git a/example/web/manifest.json b/example/web/manifest.json new file mode 100644 index 0000000..cb89988 --- /dev/null +++ b/example/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "alice_example", + "short_name": "alice_example", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/lib/alice.dart b/lib/alice.dart index 35ea6e9..6dab7f9 100644 --- a/lib/alice.dart +++ b/lib/alice.dart @@ -2,7 +2,6 @@ import 'dart:io'; // import 'package:chopper/chopper.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; // import 'package:flutter_alice/core/alice_chopper_response_interceptor.dart'; import 'package:flutter_alice/core/alice_core.dart'; import 'package:flutter_alice/core/alice_dio_interceptor.dart'; @@ -10,42 +9,16 @@ import 'package:flutter_alice/core/alice_http_adapter.dart'; import 'package:flutter_alice/core/alice_http_client_adapter.dart'; import 'package:flutter_alice/model/alice_http_call.dart'; import 'package:http/http.dart' as http; +export 'package:flutter_alice/core/alice_inspector.dart'; class Alice { - /// Should user be notified with notification if there's new request catched - /// by Alice - final bool showNotification; - - /// Should inspector be opened on device shake (works only with physical - /// with sensors) - final bool showInspectorOnShake; - - /// Should inspector use dark theme - final bool darkTheme; - - /// Icon url for notification - final String notificationIcon; - - GlobalKey? _navigatorKey; - late AliceCore _aliceCore; + final AliceCore _aliceCore = AliceCore(); late AliceHttpClientAdapter _httpClientAdapter; late AliceHttpAdapter _httpAdapter; /// Creates alice instance. - Alice( - {GlobalKey? navigatorKey, - this.showNotification = true, - this.showInspectorOnShake = false, - this.darkTheme = false, - this.notificationIcon = "@mipmap/ic_launcher"}) { - _navigatorKey = navigatorKey ?? GlobalKey(); - _aliceCore = AliceCore( - _navigatorKey, - showNotification, - showInspectorOnShake, - darkTheme, - notificationIcon, - ); + Alice({GlobalKey? navigatorKey}) { + if (navigatorKey != null) _aliceCore.setNavigatorKey(navigatorKey); _httpClientAdapter = AliceHttpClientAdapter(_aliceCore); _httpAdapter = AliceHttpAdapter(_aliceCore); } @@ -55,15 +28,8 @@ class Alice { _aliceCore.setNavigatorKey(navigatorKey); } - /// Get currently used navigation key - GlobalKey? getNavigatorKey() { - return _navigatorKey; - } - /// Get Dio interceptor which should be applied to Dio instance. - AliceDioInterceptor getDioInterceptor() { - return AliceDioInterceptor(_aliceCore); - } + AliceDioInterceptor getDioInterceptor() => _aliceCore.getDioInterceptor(); /// Handle request from HttpClient void onHttpClientRequest(HttpClientRequest request, {dynamic body}) { @@ -84,9 +50,7 @@ class Alice { /// Opens Http calls inspector. This will navigate user to the new fullscreen /// page where all listened http calls can be viewed. - void showInspector() { - _aliceCore.navigateToCallListScreen(); - } + void showInspector() => _aliceCore.navigateToCallListScreen(); // /// Get chopper interceptor. This should be added to Chopper instance. // List getChopperInterceptor() { @@ -99,4 +63,10 @@ class Alice { assert(aliceHttpCall.response != null, "Http call response can't be null"); _aliceCore.addCall(aliceHttpCall); } + + void log(String message, {Color color = Colors.white}) { + _aliceCore.log(message, color: color); + } + + void clearLogs() => _aliceCore.logs.clear(); } diff --git a/lib/core/alice_core.dart b/lib/core/alice_core.dart index f396d4b..86d1c84 100644 --- a/lib/core/alice_core.dart +++ b/lib/core/alice_core.dart @@ -1,179 +1,85 @@ import 'dart:async'; import 'package:flutter/material.dart'; -import 'package:flutter_alice/core/debug_pop_up.dart'; +import 'package:flutter_alice/core/alice_dio_interceptor.dart'; import 'package:flutter_alice/model/alice_http_call.dart'; import 'package:flutter_alice/model/alice_http_error.dart'; import 'package:flutter_alice/model/alice_http_response.dart'; import 'package:flutter_alice/ui/page/alice_calls_list_screen.dart'; -import 'package:overlay_support/overlay_support.dart'; import 'package:rxdart/rxdart.dart'; +// import 'package:collection/collection.dart'; +/// This class not exported outside package. It contains core logic +/// for alice inspector. class AliceCore { - /// Should user be notified with notification if there's new request catched - /// by Alice - final bool showNotification; + static AliceCore inst = AliceCore._(); - /// Should inspector be opened on device shake (works only with physical - /// with sensors) - final bool showInspectorOnShake; + factory AliceCore({GlobalKey? navigatorKey}) { + if (navigatorKey != null) inst.setNavigatorKey(navigatorKey); + return inst; + } - /// Should inspector use dark theme - final bool darkTheme; + /// Creates alice core instance + AliceCore._() { + _callsSubscription = callsSubject.listen(_onCallsChanged); + } /// Rx subject which contains all intercepted http calls - final BehaviorSubject> callsSubject = - BehaviorSubject.seeded([]); - - /// Icon url for notification - final String notificationIcon; + final callsSubject = BehaviorSubject.seeded([]); GlobalKey? _navigatorKey; - Brightness _brightness = Brightness.light; bool _isInspectorOpened = false; StreamSubscription? _callsSubscription; - String? _notificationMessage; - String? _notificationMessageShown; - bool _notificationProcessing = false; + bool isShowedBubble = false; - static AliceCore? _singleton; + final logsSubject = BehaviorSubject>.seeded([]); + List _logs = ['hautv.fami@gmail.com']; + List get logs => _logs; + int limitLogs = 1000; - factory AliceCore( - _navigatorKey, - showNotification, - showInspectorOnShake, - darkTheme, - notificationIcon, - ) { - _singleton ??= AliceCore._( - _navigatorKey, - showNotification, - showInspectorOnShake, - darkTheme, - notificationIcon, - ); - return _singleton!; - } + // Thêm getter cho stream: + Stream> get logsStream => logsSubject.stream; - /// Creates alice core instance - AliceCore._( - this._navigatorKey, - this.showNotification, - this.showInspectorOnShake, - this.darkTheme, - this.notificationIcon, - ) { - if (showNotification) { - _callsSubscription = callsSubject.listen((_) => _onCallsChanged()); - } - _brightness = darkTheme ? Brightness.dark : Brightness.light; + /// Get context from navigator key. Used to open inspector route. + BuildContext? get context => _navigatorKey?.currentState?.overlay?.context; + + /// Get Dio interceptor which should be applied to Dio instance. + AliceDioInterceptor getDioInterceptor() => AliceDioInterceptor(this); + + /// Set custom navigation key. This will help if there's route library. + void setNavigatorKey(GlobalKey navigatorKey) { + this._navigatorKey = navigatorKey; } + GlobalKey? get navigatorKey => _navigatorKey; + /// Dispose subjects and subscriptions void dispose() { callsSubject.close(); - //_shakeDetector?.stopListening(); _callsSubscription?.cancel(); } - /// Get currently used brightness - Brightness get brightness => _brightness; - - void _onCallsChanged() async { - if (callsSubject.value.length > 0) { - _notificationMessage = _getNotificationMessage(); - if (_notificationMessage != _notificationMessageShown && - !_notificationProcessing) { - await _showLocalNotification(); - _onCallsChanged(); - } + void _onCallsChanged(List _) { + if (callsSubject.value.isNotEmpty && !isShowedBubble) { + showDebugAnimNotification(); } } - /// Set custom navigation key. This will help if there's route library. - void setNavigatorKey(GlobalKey navigatorKey) { - this._navigatorKey = navigatorKey; - } - /// Opens Http calls inspector. This will navigate user to the new fullscreen /// page where all listened http calls can be viewed. void navigateToCallListScreen() { - var context = getContext(); if (context == null) { - print( - "Cant start Alice HTTP Inspector. Please add NavigatorKey to your application"); + print("Cant start Alice HTTP Inspector. Please add NavigatorKey"); return; } if (!_isInspectorOpened) { _isInspectorOpened = true; - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => AliceCallsListScreen(this), - ), + push( + (context) => AliceCallsListScreen(this), ).then((onValue) => _isInspectorOpened = false); } } - /// Get context from navigator key. Used to open inspector route. - BuildContext? getContext() => _navigatorKey?.currentState?.overlay?.context; - - String _getNotificationMessage() { - List? calls = callsSubject.value; - int successCalls = calls - .where((call) => - call.response != null && - (call.response?.status ?? 0) >= 200 && - (call.response?.status ?? 0) < 300) - .toList() - .length; - - int redirectCalls = calls - .where((call) => - call.response != null && - (call.response?.status ?? 0) >= 300 && - (call.response?.status ?? 0) < 400) - .toList() - .length; - - int errorCalls = calls - .where((call) => - call.response != null && - (call.response?.status ?? 0) >= 400 && - (call.response?.status ?? 0) < 600) - .toList() - .length; - - int loadingCalls = calls.where((call) => call.loading).toList().length; - - StringBuffer notificationsMessage = StringBuffer(); - if (loadingCalls > 0) { - notificationsMessage.write("Loading: $loadingCalls"); - notificationsMessage.write(" | "); - } - if (successCalls > 0) { - notificationsMessage.write("Success: $successCalls"); - notificationsMessage.write(" | "); - } - if (redirectCalls > 0) { - notificationsMessage.write("Redirect: $redirectCalls"); - notificationsMessage.write(" | "); - } - if (errorCalls > 0) { - notificationsMessage.write("Error: $errorCalls"); - } - return notificationsMessage.toString(); - } - - Future _showLocalNotification() async { - _notificationProcessing = true; - String? message = _notificationMessage; - showDebugAnimNotification(); - _notificationMessageShown = message; - _notificationProcessing = false; - return; - } - /// Add alice http call to calls subject void addCall(AliceHttpCall call) { callsSubject.add([call, ...callsSubject.value]); @@ -181,8 +87,7 @@ class AliceCore { /// Add error to exisng alice http call void addError(AliceHttpError error, int requestId) { - AliceHttpCall? selectedCall = _selectCall(requestId); - + final selectedCall = _selectCall(requestId); if (selectedCall == null) { print("Selected call is null"); return; @@ -194,8 +99,7 @@ class AliceCore { /// Add response to existing alice http call void addResponse(AliceHttpResponse response, int requestId) { - AliceHttpCall? selectedCall = _selectCall(requestId); - + final selectedCall = _selectCall(requestId); if (selectedCall == null) { print("Selected call is null"); return; @@ -216,41 +120,58 @@ class AliceCore { } /// Remove all calls from calls subject - void removeCalls() { - callsSubject.add([]); - } + void clears() => callsSubject.add([]); - AliceHttpCall? _selectCall(int requestId) => - callsSubject.value.firstWhereOrNull( - (call) => call.id == requestId, - ); + AliceHttpCall? _selectCall(int requestId) { + return callsSubject.value.firstWhereOrNull( + (call) => call.id == requestId, + ); + } - bool isShowedBubble = false; + @optionalTypeArgs + Future push(Widget Function(BuildContext) builder) { + if (context == null) { + print("Cant start Alice HTTP Inspector. Please add NavigatorKey"); + return Future.value(null); + } + return Navigator.push(context!, MaterialPageRoute(builder: builder)); + } void showDebugAnimNotification() { if (isShowedBubble) return; - - final context = getContext(); if (context == null) return; isShowedBubble = true; - showOverlay((context, t) { - return Opacity( - opacity: t, - child: DebugPopUp( - callsSubscription: callsSubject.stream, - onClicked: () { - navigateToCallListScreen(); - }, - aliceCore: this, - ), - ); - }, duration: Duration.zero); + // showOverlay((context, t) { + // return Opacity( + // opacity: t, + // child: AliceInspector( + // // onClicked: navigateToCallListScreen, + // aliceCore: this, + // limitLogs: limitLogs, + // ), + // ); + // }, duration: Duration.zero); + } + + void log(String message, {Color color = Colors.white}) { + if (_logs.length > limitLogs) _logs.removeLast(); + final colorString = '0x${color.toARGB32().toRadixString(16)}'.toUpperCase(); + + print("$message"); + + _logs.insert(0, '$colorString$message'); + logsSubject.add(List.from(_logs)); + } + + void clearLogs() { + _logs.clear(); + logsSubject.add([]); } } -extension IterableExtension on Iterable { - T? firstWhereOrNull(bool Function(T element) test) { +extension ListExtension on List { + T? firstWhereOrNull(bool Function(T) test) { for (var element in this) { if (test(element)) return element; } diff --git a/lib/core/alice_inspector.dart b/lib/core/alice_inspector.dart new file mode 100644 index 0000000..4f06d94 --- /dev/null +++ b/lib/core/alice_inspector.dart @@ -0,0 +1,134 @@ +import 'dart:math'; + +import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_alice/core/alice_dio_interceptor.dart'; +import 'package:flutter_alice/model/alice_http_call.dart'; +import 'package:flutter_alice/ui/page/alice_stats_screen.dart'; + +import 'alice_core.dart'; +import 'expandable_fab.dart'; + +class AliceInspector extends StatefulWidget { + final Widget? child; + final bool debug; + final List? dios; + final int limitLogs; + final GlobalKey? navigatorKey; + + const AliceInspector({ + Key? key, + this.child, + this.debug = true, + this.dios, + this.limitLogs = 1000, + this.navigatorKey, + }) : super(key: key); + + @override + _AliceInspectorState createState() => _AliceInspectorState(); +} + +class _AliceInspectorState extends State { + final aliceCore = AliceCore.inst; + final _expandedDistance = 80.0; + late double _rightSide = _expandedDistance + kToolbarHeight + 20; + Offset _offset = Offset.zero; + + @override + void initState() { + aliceCore.limitLogs = widget.limitLogs; + if (widget.navigatorKey != null) { + aliceCore.setNavigatorKey(widget.navigatorKey!); + } else if (aliceCore.navigatorKey == null) { + print("AliceInspector: NavigatorKey is not set. " + "Please provide a navigatorKey to AliceInspector or AliceCore."); + throw Exception( + 'AliceInspector: NavigatorKey is not set. 🤬\n' + 'Please provide a navigatorKey to AliceInspector or AliceCore.\n' + 'navigatorKey: navigatorKey, or alice.setNavigatorKey(navigatorKey)', + ); + } + + if (widget.dios != null && widget.debug) { + final interceptor = aliceCore.getDioInterceptor(); + for (final dio in widget.dios!) { + dio.interceptors.removeWhere((e) => e is AliceDioInterceptor); + dio.interceptors.add(interceptor); + } + } + + WidgetsBinding.instance.addPostFrameCallback((_) { + final _size = MediaQuery.of(context).size; + setState(() { + _offset = Offset( + _size.width - _rightSide, + _size.height / 3 - _expandedDistance, + ); + }); + }); + + super.initState(); + } + + @override + Widget build(BuildContext context) { + if (!widget.debug) return widget.child ?? SizedBox.shrink(); + + return Directionality( + textDirection: TextDirection.ltr, + child: Stack( + children: [ + if (widget.child != null) widget.child!, + Positioned( + left: _offset.dx, + top: _offset.dy, + child: SafeArea( + child: GestureDetector( + onPanUpdate: (del) => setState(() => _offset += del.delta), + child: _buildDraggyWidget(), + ), + ), + ), + ], + ), + ); + } + + Widget _buildDraggyWidget() { + return ExpandableFab( + distance: _expandedDistance, + bigButton: Opacity( + opacity: 0.6, + child: FloatingActionButton( + child: StreamBuilder>( + initialData: [], + stream: aliceCore.callsSubject.stream, + builder: (_, sns) { + final counter = min(sns.data?.length ?? 0, 99); + return Text("$counter"); + }, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(32), + ), + backgroundColor: Colors.green, + foregroundColor: Colors.white, + onPressed: aliceCore.navigateToCallListScreen, + mini: true, + enableFeedback: true, + ), + ), + children: [ + ActionButton( + onPressed: aliceCore.clears, + icon: Icon(Icons.delete, color: Colors.white), + ), + ActionButton( + onPressed: () => aliceCore.push((_) => AliceStatsScreen(aliceCore)), + icon: Icon(Icons.insert_chart, color: Colors.white), + ), + ], + ); + } +} diff --git a/lib/core/debug_pop_up.dart b/lib/core/debug_pop_up.dart deleted file mode 100644 index d15c209..0000000 --- a/lib/core/debug_pop_up.dart +++ /dev/null @@ -1,125 +0,0 @@ -import 'dart:async'; -import 'dart:math'; - -import 'package:flutter/material.dart'; -import 'package:flutter_alice/model/alice_http_call.dart'; -import 'package:flutter_alice/ui/page/alice_stats_screen.dart'; - -import 'alice_core.dart'; -import 'expandable_fab.dart'; -// import 'flutter_expandable_fab/expandable_fab.dart'; - -class DebugPopUp extends StatefulWidget { - final VoidCallback onClicked; - final Stream> callsSubscription; - final AliceCore aliceCore; - - ///class widget to show overlay bubble describes the number request count and is a place to navigate to alice inspector. - ///[onClicked] call back when user clicked in debug point - ///[callsSubscription] the stream to listen how many request in app - const DebugPopUp({ - Key? key, - required this.onClicked, - required this.callsSubscription, - required this.aliceCore, - }) : super(key: key); - - @override - _DebugPopUpState createState() => _DebugPopUpState(); -} - -class _DebugPopUpState extends State { - final _expandedDistance = 80.0; - late Size _size = MediaQuery.of(context).size; - late double _rightSide = _expandedDistance + kToolbarHeight + 20; - Offset _offset = Offset.zero; - - @override - void initState() { - super.initState(); - - WidgetsBinding.instance.addPostFrameCallback((_) { - _offset = Offset( - _size.width - _rightSide, - _size.height / 2 - _expandedDistance, - ); - }); - } - - @override - Widget build(BuildContext context) { - //wrap with SafeArea to support edge screen - return SafeArea( - child: Stack( - children: [ - Positioned( - left: _offset.dx, - top: _offset.dy, - child: GestureDetector( - onPanUpdate: (details) { - setState(() => _offset += details.delta); - }, - child: _buildDraggyWidget( - widget.onClicked, - widget.callsSubscription, - ), - ), - ), - ], - ), - ); - } - - Widget _buildDraggyWidget( - VoidCallback onClicked, - Stream> stream, - ) { - return ExpandableFab( - distance: _expandedDistance, - bigButton: Opacity( - opacity: 0.6, - child: FloatingActionButton( - child: StreamBuilder>( - initialData: [], - stream: stream, - builder: (_, sns) { - final counter = min(sns.data?.length ?? 0, 99); - return Text("$counter"); - }, - ), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(32), - ), - backgroundColor: Colors.green, - foregroundColor: Colors.white, - onPressed: onClicked, - mini: true, - enableFeedback: true, - ), - ), - children: [ - ActionButton( - onPressed: () { - widget.aliceCore.removeCalls(); - }, - icon: Icon(Icons.delete, color: Colors.white), - ), - ActionButton( - onPressed: () { - _showStatsScreen(); - }, - icon: Icon(Icons.insert_chart, color: Colors.white), - ), - ], - ); - } - - void _showStatsScreen() { - Navigator.push( - widget.aliceCore.getContext()!, - MaterialPageRoute( - builder: (_) => AliceStatsScreen(widget.aliceCore), - ), - ); - } -} diff --git a/lib/core/expandable_fab.dart b/lib/core/expandable_fab.dart index 8c355f6..8d95a27 100644 --- a/lib/core/expandable_fab.dart +++ b/lib/core/expandable_fab.dart @@ -6,6 +6,7 @@ import 'dart:math' as math; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; @immutable class ExpandableFab extends StatefulWidget { @@ -15,12 +16,20 @@ class ExpandableFab extends StatefulWidget { required this.distance, required this.children, required this.bigButton, + this.animationDuration = const Duration(milliseconds: 250), + this.curve = Curves.fastOutSlowIn, + this.reverseCurve = Curves.easeOutQuad, + this.enableHapticFeedback = true, }) : super(key: key); final bool? initialOpen; final double distance; final List children; final Widget bigButton; + final Duration animationDuration; + final Curve curve; + final Curve reverseCurve; + final bool enableHapticFeedback; @override _ExpandableFabState createState() => _ExpandableFabState(); @@ -38,12 +47,12 @@ class _ExpandableFabState extends State _open = widget.initialOpen ?? false; _controller = AnimationController( value: _open ? 1.0 : 0.0, - duration: const Duration(milliseconds: 250), + duration: widget.animationDuration, vsync: this, ); _expandAnimation = CurvedAnimation( - curve: Curves.fastOutSlowIn, - reverseCurve: Curves.easeOutQuad, + curve: widget.curve, + reverseCurve: widget.reverseCurve, parent: _controller, ); } @@ -58,7 +67,14 @@ class _ExpandableFabState extends State setState(() { _open = !_open; }); - _open ? _controller.forward() : _controller.reverse(); + if (_open) { + _controller.forward(); + } else { + _controller.reverse(); + } + if (widget.enableHapticFeedback) { + HapticFeedback.lightImpact(); + } } @override @@ -104,6 +120,8 @@ class _ExpandableFabState extends State List _buildExpandingActionButtons() { final children = []; final count = widget.children.length; + if (count == 0) return children; + final step = 90.0 / (count - 1); for (var i = 0, angleInDegrees = 0.0; i < count; @@ -116,9 +134,11 @@ class _ExpandableFabState extends State child: GestureDetector( onTap: () { _toggle(); - (widget.children[i] as ActionButton).onPressed?.call(); + if (widget.children[i] is ActionButton) { + (widget.children[i] as ActionButton).onPressed?.call(); + } }, - child: widget.children[i] as ActionButton, + child: widget.children[i], ), ), ); @@ -136,12 +156,12 @@ class _ExpandableFabState extends State _open ? 0.7 : 1.0, 1.0, ), - duration: Duration(milliseconds: 250), - curve: Interval(0.0, 0.5, curve: Curves.easeOut), + duration: widget.animationDuration, + curve: Interval(0.0, 0.5, curve: widget.curve), child: AnimatedOpacity( opacity: _open ? 0.0 : 1.0, curve: const Interval(0.25, 1.0, curve: Curves.easeInOut), - duration: const Duration(milliseconds: 250), + duration: widget.animationDuration, child: GestureDetector( onLongPress: _toggle, child: SizedBox( @@ -211,7 +231,7 @@ class ActionButton extends StatelessWidget { padding: const EdgeInsets.all(16.0), decoration: BoxDecoration( borderRadius: BorderRadius.circular(32), - color: Colors.black45, + color: Colors.green.withValues(alpha: 0.9), ), child: icon, ); diff --git a/lib/core/flutter_expandable_fab/action_button_builder.dart b/lib/core/flutter_expandable_fab/action_button_builder.dart deleted file mode 100644 index a26395d..0000000 --- a/lib/core/flutter_expandable_fab/action_button_builder.dart +++ /dev/null @@ -1,195 +0,0 @@ -// import 'dart:math' as math; - -// import 'package:flutter/material.dart'; - -// /// The size of the expanded FAB. -// enum ExpandableFabSize { small, regular, large } - -// /// A builder class for creating a customized Floating Action Button (FAB). -// /// -// /// ```dart -// /// FloatingActionButtonBuilder( -// /// size: 56, -// /// builder: (BuildContext context, void Function()? onPressed, Animation progress) { -// /// return IconButton( -// /// onPressed: onPressed, -// /// icon: const Icon( -// /// Icons.menu_open, -// /// size: 40, -// /// ), -// /// ); -// /// }, -// /// ) -// /// ``` -// /// -// class FloatingActionButtonBuilder { -// /// Creates a `FloatingActionButtonBuilder` with the specified parameters. -// /// -// /// - [size]: The size of the FAB. It must be a non-null value. -// /// - [builder]: A callback function that defines the appearance and behavior of the FAB. -// const FloatingActionButtonBuilder({ -// required this.size, -// required this.builder, -// }); - -// /// The callback function used to build the FAB. -// /// -// /// This function takes three parameters: -// /// - [context]: The build context. -// /// - [onPressed]: A callback that will be executed when the FAB is pressed. -// /// - [progress]: Animation representing the expansion and contraction of the FAB. -// final Widget Function(BuildContext context, VoidCallback? onPressed, -// Animation progress) builder; - -// /// The size of the FAB. Used for position calculations and animations. -// final double size; -// } - -// /// A default implementation of `FloatingActionButtonBuilder` to create a standard FAB. -// class DefaultFloatingActionButtonBuilder extends FloatingActionButtonBuilder { -// /// Creates a `DefaultFloatingActionButtonBuilder` with the specified optional parameters. -// /// -// /// - [fabSize]: The size of the FAB, represented by an `ExpandableFabSize` enum value. -// /// - [foregroundColor]: The default foreground color for icons and text within the button. -// /// - [backgroundColor]: The button's background color. -// /// - [shape]: The shape of the FAB's [Material]. -// /// - [heroTag]: The tag to apply to the button's [Hero] widget. -// /// - [elevation]: This controls the size of the shadow below the floating action button. -// /// - [child]: The widget below the button widget in the tree. -// DefaultFloatingActionButtonBuilder({ -// this.fabSize = ExpandableFabSize.regular, -// this.foregroundColor, -// this.backgroundColor, -// this.shape, -// this.heroTag, -// this.elevation, -// this.child, -// }) : super( -// size: _actualSize(fabSize), -// builder: (BuildContext context, VoidCallback? onPressed, -// Animation progress) { -// var func = FloatingActionButton.small; -// switch (fabSize) { -// case ExpandableFabSize.large: -// func = FloatingActionButton.large; -// break; -// case ExpandableFabSize.small: -// break; -// default: -// func = FloatingActionButton.new; -// } -// return func.call( -// autofocus: true, -// foregroundColor: foregroundColor, -// backgroundColor: backgroundColor, -// shape: shape, -// heroTag: heroTag, -// elevation: elevation, -// onPressed: onPressed, -// child: child, -// ); -// }, -// ); - -// /// The size of the FAB, represented by an `ExpandableFabSize` enum value. -// final ExpandableFabSize fabSize; - -// /// The default foreground color for icons and text within the button. -// final Color? foregroundColor; - -// /// The button's background color. -// final Color? backgroundColor; - -// /// The shape of the FAB's [Material]. -// final ShapeBorder? shape; - -// /// The tag to apply to the button's [Hero] widget. -// final Object? heroTag; - -// /// This controls the size of the shadow below the floating action button -// final double? elevation; - -// /// The widget below the button widget in the tree. -// final Widget? child; -// } - -// /// A builder class for creating a customized Floating Action Button (FAB) with rotation animation. -// class RotateFloatingActionButtonBuilder extends FloatingActionButtonBuilder { -// /// Creates a `RotateFloatingActionButtonBuilder` with the specified optional parameters. -// /// -// /// - [fabSize]: The size of the FAB, represented by an `ExpandableFabSize` enum value. -// /// - [foregroundColor]: The default foreground color for icons and text within the button. -// /// - [backgroundColor]: The button's background color. -// /// - [shape]: The shape of the FAB's [Material]. -// /// - [heroTag]: The tag to apply to the button's [Hero] widget. -// /// - [elevation]: This controls the size of the shadow below the floating action button. -// /// - [child]: The widget below the button widget in the tree. -// /// - [angle]: The angle of rotation to apply to the FAB, in radians. -// RotateFloatingActionButtonBuilder({ -// this.fabSize = ExpandableFabSize.regular, -// this.foregroundColor, -// this.backgroundColor, -// this.shape, -// this.heroTag, -// this.elevation, -// this.child, -// this.angle = math.pi / 2, -// }) : super( -// size: _actualSize(fabSize), -// builder: (BuildContext context, VoidCallback? onPressed, -// Animation progress) { -// return AnimatedBuilder( -// animation: progress, -// builder: (context, _) { -// return Transform.rotate( -// angle: progress.value * angle, -// child: DefaultFloatingActionButtonBuilder( -// foregroundColor: foregroundColor, -// backgroundColor: backgroundColor, -// shape: shape, -// heroTag: heroTag, -// elevation: elevation, -// fabSize: fabSize, -// child: child, -// ).builder(context, onPressed, progress), -// ); -// }, -// ); -// }, -// ); - -// /// The size of the FAB. -// final ExpandableFabSize fabSize; - -// /// The default foreground color for icons and text within the button. -// final Color? foregroundColor; - -// /// The button's background color. -// final Color? backgroundColor; - -// /// The shape of the FAB's [Material]. -// final ShapeBorder? shape; - -// /// The tag to apply to the button's [Hero] widget. -// final Object? heroTag; - -// /// This controls the size of the shadow below the floating action button -// final double? elevation; - -// /// The widget below the button widget in the tree. -// final Widget? child; - -// /// The angle of rotation to be applied to the animation, in radians. -// final double angle; -// } - -// double _actualSize(ExpandableFabSize fabSize) { -// switch (fabSize) { -// case ExpandableFabSize.large: -// return 96.0; -// case ExpandableFabSize.small: -// return 40.0; -// default: -// return 56.0; -// } -// } diff --git a/lib/core/flutter_expandable_fab/expandable_fab.dart b/lib/core/flutter_expandable_fab/expandable_fab.dart deleted file mode 100644 index 0a1546a..0000000 --- a/lib/core/flutter_expandable_fab/expandable_fab.dart +++ /dev/null @@ -1,518 +0,0 @@ -// import 'dart:math' as math; -// import 'dart:ui'; - -// import 'package:flutter/material.dart'; -// import 'action_button_builder.dart'; - -// /// The type of behavior of this widget. -// enum ExpandableFabType { fan, up, side } - -// /// The position options for the FAB on the screen. -// enum ExpandableFabPos { right, left, center } - -// /// Animation Type -// enum ExpandableFabAnimation { none, rotate } - -// /// Style configuration for the overlay displayed behind the Expandable FAB. -// @immutable -// class ExpandableFabOverlayStyle { -// /// Creates an `ExpandableFabOverlayStyle` with the specified optional parameters. -// /// -// /// - [color]: The color to paint behind the FAB. -// /// - [blur]: The strength of the blur behind the FAB. -// /// -// /// Only one of [color] or [blur] can be specified; both cannot be non-null at the same time. -// const ExpandableFabOverlayStyle({ -// this.color, -// this.blur, -// }); - -// /// The color to paint behind the Fab. -// final Color? color; - -// /// The strength of the blur behind Fab. -// final double? blur; -// } - -// /// A FloatingActionButton that can show/hide multiple action buttons with animation. -// /// -// /// ```dart -// /// Scaffold( -// /// floatingActionButtonLocation: ExpandableFab.location, -// /// floatingActionButton: ExpandableFab( -// /// children: [ -// /// FloatingActionButton.small( -// /// heroTag: null, -// /// child: const Icon(Icons.edit), -// /// onPressed: () {}, -// /// ), -// /// FloatingActionButton.small( -// /// heroTag: null, -// /// child: const Icon(Icons.search), -// /// onPressed: () {}, -// /// ), -// /// ], -// /// ), -// /// ); -// /// ``` -// /// -// @immutable -// class ExpandableFab extends StatefulWidget { -// /// The location of the ExpandableFab on the screen. -// static final FloatingActionButtonLocation location = _ExpandableFabLocation(); - -// const ExpandableFab({ -// this.distance = 100, -// this.duration = const Duration(milliseconds: 250), -// this.fanAngle = 90, -// this.initialOpen = false, -// this.type = ExpandableFabType.fan, -// this.pos = ExpandableFabPos.right, -// this.childrenAnimation = ExpandableFabAnimation.rotate, -// this.closeButtonBuilder, -// this.openButtonBuilder, -// this.childrenOffset = Offset.zero, -// required this.children, -// this.onOpen, -// this.afterOpen, -// this.onClose, -// this.afterClose, -// this.overlayStyle, -// this.openCloseStackAlignment = Alignment.center, -// this.elevation, -// this.margin = const EdgeInsets.all(0), -// }); -// // Margin for the FAB -// final EdgeInsets margin; - -// /// Distance from children. -// final double distance; - -// /// Animation duration. -// final Duration duration; - -// /// Angle of opening when fan type. -// final double fanAngle; - -// /// Open at initial display. -// final bool initialOpen; - -// /// The type of behavior of this widget. -// final ExpandableFabType type; - -// /// The position of the ExpandableFab on the screen -// final ExpandableFabPos pos; - -// /// A builder for the custom close button. -// final FloatingActionButtonBuilder? closeButtonBuilder; - -// /// A builder for the custom open button. -// final FloatingActionButtonBuilder? openButtonBuilder; - -// /// Types of animations for Children. -// final ExpandableFabAnimation childrenAnimation; - -// /// For positioning of children widgets. -// final Offset childrenOffset; - -// /// The widgets below this widget in the tree. -// final List children; - -// /// Will be called before opening the menu. -// final VoidCallback? onOpen; - -// /// Will be called after opening the menu. -// final VoidCallback? afterOpen; - -// /// Will be called before the menu closes. -// final VoidCallback? onClose; - -// /// Will be called after the menu closes. -// final VoidCallback? afterClose; - -// /// Provides the style for overlay. No overlay when null. -// final ExpandableFabOverlayStyle? overlayStyle; - -// /// Defines how [openButtonBuilder] and [closeButtonBuilder] are aligned in a [Stack]. -// /// Useful when the buttons have different sizes and need specific alignment adjustments. -// final Alignment openCloseStackAlignment; - -// /// This controls the size of the shadow below the floating action button -// final double? elevation; - -// /// The state from the closest instance of this class that encloses the given context. -// static ExpandableFabState of(BuildContext context) { -// ExpandableFabState? state; -// if (context is StatefulElement && context.state is ExpandableFabState) { -// state = context.state as ExpandableFabState; -// } -// state = context.findRootAncestorStateOfType() ?? state; - -// assert(() { -// if (state == null) { -// throw FlutterError( -// 'ExpandableFab operation requested with a context that does not include a ExpandableFab.'); -// } -// return true; -// }()); -// return state!; -// } - -// @override -// State createState() => ExpandableFabState(); -// } - -// class ExpandableFabState extends State -// with SingleTickerProviderStateMixin { -// final _defaultOpenButtonBuilder = RotateFloatingActionButtonBuilder( -// child: const Icon(Icons.menu), -// ); -// final _defaultCloseButtonBuilder = DefaultFloatingActionButtonBuilder( -// fabSize: ExpandableFabSize.small, -// child: const Icon(Icons.close), -// ); - -// late final AnimationController _controller; -// late final Animation _expandAnimation; -// late FloatingActionButtonBuilder _openButtonBuilder = -// _defaultOpenButtonBuilder; -// late FloatingActionButtonBuilder _closeButtonBuilder = -// _defaultCloseButtonBuilder; -// bool _open = false; - -// /// Returns whether the menu is open -// bool get isOpen => _open; - -// /// Display or hide the menu. -// void toggle() { -// setState(() { -// _open = !_open; -// if (_open) { -// widget.onOpen?.call(); -// _controller.forward().then((value) { -// widget.afterOpen?.call(); -// }); -// } else { -// widget.onClose?.call(); -// _controller.reverse().then((value) { -// widget.afterClose?.call(); -// }); -// } -// }); -// } - -// @override -// void initState() { -// super.initState(); -// _open = widget.initialOpen; -// _controller = AnimationController( -// value: _open ? 1.0 : 0.0, -// duration: widget.duration, -// vsync: this, -// ); -// _expandAnimation = CurvedAnimation( -// curve: Curves.fastOutSlowIn, -// reverseCurve: Curves.easeOutQuad, -// parent: _controller, -// ); -// if (widget.openButtonBuilder != null) { -// _openButtonBuilder = widget.openButtonBuilder!; -// } -// if (widget.closeButtonBuilder != null) { -// _closeButtonBuilder = widget.closeButtonBuilder!; -// } -// } - -// @override -// void didUpdateWidget(covariant ExpandableFab oldWidget) { -// super.didUpdateWidget(oldWidget); -// _open = widget.initialOpen; -// _controller.duration = widget.duration; -// _openButtonBuilder = widget.openButtonBuilder ?? _defaultOpenButtonBuilder; -// _closeButtonBuilder = -// widget.closeButtonBuilder ?? _defaultCloseButtonBuilder; -// } - -// @override -// void dispose() { -// _controller.dispose(); -// super.dispose(); -// } - -// @override -// Widget build(BuildContext context) { -// final location = ExpandableFab.location as _ExpandableFabLocation; -// Offset? offset; -// Widget? cache; -// final ScaffoldState? scaffold = -// context.findAncestorStateOfType(); - -// return ValueListenableBuilder( -// valueListenable: location.scaffoldGeometry, -// builder: ((context, geometry, child) { -// if (geometry == null) { -// if (scaffold == null) { -// double dx = 0; -// if (widget.pos == ExpandableFabPos.right) { -// dx = 16; -// } else if (widget.pos == ExpandableFabPos.left) { -// dx = -16; -// } -// return _buildButtons(Offset(dx, 16)); -// } else { -// return const SizedBox.shrink(); -// } -// } -// double x = 0; -// if (widget.pos == ExpandableFabPos.right) { -// x = kFloatingActionButtonMargin + geometry.minInsets.right; -// } else if (widget.pos == ExpandableFabPos.left) { -// x = -kFloatingActionButtonMargin - geometry.minInsets.left; -// } -// final bottomContentHeight = -// geometry.scaffoldSize.height - geometry.contentBottom; -// final y = kFloatingActionButtonMargin + -// math.max(geometry.minViewPadding.bottom, bottomContentHeight); -// if (offset != Offset(x, y)) { -// offset = Offset(x, y); -// cache = _buildButtons(offset!); -// } -// return _open ? FocusScope(child: cache!) : cache!; -// }), -// ); -// } - -// Widget _buildButtons(Offset offset) { -// final blur = widget.overlayStyle?.blur; -// final overlayColor = widget.overlayStyle?.color; -// final adjustedOffset = Offset( -// offset.dx + -// (widget.pos == ExpandableFabPos.left -// ? widget.margin.left -// : widget.pos == ExpandableFabPos.center -// ? 0 -// : widget.margin.right), -// offset.dy + widget.margin.bottom, -// ); -// final Alignment alignment; -// switch (widget.pos) { -// case ExpandableFabPos.left: -// alignment = Alignment.bottomLeft; -// break; -// case ExpandableFabPos.center: -// alignment = Alignment.bottomCenter; -// break; -// default: -// alignment = Alignment.bottomRight; -// } -// return GestureDetector( -// onTap: () => toggle(), -// child: Stack( -// alignment: alignment, -// children: [ -// Container(), -// if (overlayColor != null) -// IgnorePointer( -// ignoring: !_open, -// child: FadeTransition( -// opacity: _expandAnimation, -// child: Container( -// color: overlayColor, -// ), -// ), -// ), -// if (blur != null) -// IgnorePointer( -// ignoring: !_open, -// child: TweenAnimationBuilder( -// tween: Tween( -// begin: _open ? 0.0 : blur, -// end: _open ? blur : 0.0, -// ), -// duration: widget.duration, -// curve: Curves.easeInOut, -// builder: (_, value, child) { -// if (value < 0.001) { -// return child!; -// } -// return ClipRect( -// child: BackdropFilter( -// filter: ImageFilter.blur(sigmaX: value, sigmaY: value), -// child: child, -// ), -// ); -// }, -// child: Container(color: Colors.transparent), -// ), -// ), -// ..._buildExpandingActionButtons(adjustedOffset), -// Transform.translate( -// offset: -adjustedOffset, -// child: Stack( -// alignment: widget.openCloseStackAlignment, -// children: [ -// FadeTransition( -// opacity: _expandAnimation, -// child: _closeButtonBuilder.builder( -// context, toggle, _expandAnimation), -// ), -// _buildTapToOpenFab(), -// ], -// ), -// ), -// ], -// ), -// ); -// } - -// List _buildExpandingActionButtons(Offset offset) { -// final children = []; -// final count = widget.children.length; -// var buttonOffset = 0.0; -// if (_openButtonBuilder.size > _closeButtonBuilder.size) { -// buttonOffset = (_openButtonBuilder.size - _closeButtonBuilder.size) / 2; -// } -// var totalOffset = offset; -// switch (widget.pos) { -// case ExpandableFabPos.left: -// totalOffset += Offset(-widget.childrenOffset.dx - buttonOffset, -// widget.childrenOffset.dy + buttonOffset); -// break; -// case ExpandableFabPos.center: -// final screenSize = MediaQuery.of(context).size; -// totalOffset = Offset( -// screenSize.width / 2 - _closeButtonBuilder.size / 2, -// offset.dy + buttonOffset); -// break; -// default: -// totalOffset += -// widget.childrenOffset + Offset(buttonOffset, buttonOffset); -// } -// for (var i = 0; i < count; i++) { -// final double dist; -// double dir; -// switch (widget.type) { -// case ExpandableFabType.fan: -// final half = (90 - widget.fanAngle) / 2; -// if (count > 1) { -// dir = widget.fanAngle / (count - 1) * i + half; -// } else { -// dir = widget.fanAngle + half; -// } -// if (widget.pos == ExpandableFabPos.center) { -// dir += 45; -// } -// dist = widget.distance; -// break; -// case ExpandableFabType.up: -// dir = 90; -// dist = widget.distance * (i + 1); -// break; -// case ExpandableFabType.side: -// dir = 0; -// dist = widget.distance * (i + 1); -// break; -// } -// children.add( -// _ExpandingActionButton( -// directionInDegrees: dir, -// maxDistance: dist, -// progress: _expandAnimation, -// offset: totalOffset, -// fabPos: widget.pos, -// animation: widget.childrenAnimation, -// child: widget.children[i], -// ), -// ); -// } -// return children; -// } - -// Widget _buildTapToOpenFab() { -// final transformValues = _closeButtonBuilder.size / _openButtonBuilder.size; -// final reverse = ReverseAnimation(_expandAnimation); - -// return IgnorePointer( -// ignoring: _open, -// child: ScaleTransition( -// scale: Tween(begin: transformValues, end: 1.0).animate(reverse), -// child: FadeTransition( -// opacity: reverse, -// child: _openButtonBuilder.builder(context, toggle, _expandAnimation), -// ), -// ), -// ); -// } -// } - -// class _ExpandableFabLocation extends StandardFabLocation { -// final ValueNotifier scaffoldGeometry = -// ValueNotifier(null); - -// @override -// double getOffsetX( -// ScaffoldPrelayoutGeometry scaffoldGeometry, double adjustment) { -// Future.microtask(() { -// this.scaffoldGeometry.value = scaffoldGeometry; -// }); -// return 0; -// } - -// @override -// double getOffsetY( -// ScaffoldPrelayoutGeometry scaffoldGeometry, double adjustment) { -// return -scaffoldGeometry.snackBarSize.height; -// } -// } - -// @immutable -// class _ExpandingActionButton extends StatelessWidget { -// const _ExpandingActionButton({ -// required this.directionInDegrees, -// required this.maxDistance, -// required this.progress, -// required this.child, -// required this.fabPos, -// required this.offset, -// required this.animation, -// }); - -// final double directionInDegrees; -// final double maxDistance; -// final Animation progress; -// final Offset offset; -// final ExpandableFabPos fabPos; -// final Widget child; -// final ExpandableFabAnimation animation; - -// @override -// Widget build(BuildContext context) { -// return AnimatedBuilder( -// animation: progress, -// builder: (context, child) { -// final pos = Offset.fromDirection( -// directionInDegrees * (math.pi / 180.0), -// progress.value * maxDistance, -// ); -// return Positioned( -// right: fabPos == ExpandableFabPos.left ? null : offset.dx + pos.dx, -// left: fabPos == ExpandableFabPos.left ? -offset.dx + pos.dx : null, -// bottom: offset.dy + pos.dy, -// child: Transform.rotate( -// angle: animation == ExpandableFabAnimation.rotate -// ? (1.0 - progress.value) * math.pi / 2 -// : 0, -// child: IgnorePointer( -// ignoring: progress.value != 1, -// child: child, -// ), -// ), -// ); -// }, -// child: FadeTransition( -// opacity: progress, -// child: child, -// ), -// ); -// } -// } diff --git a/lib/helper/alice_alert_helper.dart b/lib/helper/alice_alert_helper.dart index cf0714c..ab992e0 100644 --- a/lib/helper/alice_alert_helper.dart +++ b/lib/helper/alice_alert_helper.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_alice/ui/utils/alice_theme.dart'; class AliceAlertHelper { ///Helper method used to open alarm with given title and description. @@ -17,9 +18,7 @@ class AliceAlertHelper { ElevatedButton( child: Text(firstButtonTitle), onPressed: () { - if (firstButtonAction != null) { - firstButtonAction(); - } + if (firstButtonAction != null) firstButtonAction(); Navigator.of(context).pop(); }, ), @@ -29,9 +28,7 @@ class AliceAlertHelper { ElevatedButton( child: Text(secondButtonTitle), onPressed: () { - if (secondButtonAction != null) { - secondButtonAction(); - } + if (secondButtonAction != null) secondButtonAction(); Navigator.of(context).pop(); }, ), @@ -39,18 +36,14 @@ class AliceAlertHelper { } showDialog( context: context, - builder: (BuildContext buildContext) { - return Theme( - data: ThemeData( - brightness: brightness ?? Brightness.light, - ), - child: AlertDialog( - title: Text(title), - content: Text(description), - actions: actions, - ), - ); - }, + builder: (_) => Theme( + data: aliceTheme, + child: AlertDialog( + title: Text(title), + content: Text(description), + actions: actions, + ), + ), ); } } diff --git a/lib/helper/alice_conversion_helper.dart b/lib/helper/alice_convert_helper.dart similarity index 97% rename from lib/helper/alice_conversion_helper.dart rename to lib/helper/alice_convert_helper.dart index a5114aa..b1554f8 100644 --- a/lib/helper/alice_conversion_helper.dart +++ b/lib/helper/alice_convert_helper.dart @@ -1,4 +1,4 @@ -class AliceConversionHelper { +class AliceConvertHelper { static int _kilobyteAsByte = 1000; static int _megabyteAsByte = 1000000; static int _secondAsMillisecond = 1000; diff --git a/lib/helper/alice_json_viewer.dart b/lib/helper/alice_json_viewer.dart new file mode 100644 index 0000000..802fb3f --- /dev/null +++ b/lib/helper/alice_json_viewer.dart @@ -0,0 +1,408 @@ +library flutter_json_widget; + +import 'package:flutter/material.dart'; + +class AliceJsonViewer extends StatelessWidget { + final dynamic jsonObj; + const AliceJsonViewer({super.key, required this.jsonObj}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + appBar: AppBar( + title: Text('Flutter JSON Viewer'), + centerTitle: true, + backgroundColor: Colors.green, + foregroundColor: Colors.white, + scrolledUnderElevation: 0, + ), + body: SingleChildScrollView( + padding: EdgeInsets.all(8.0), + child: SafeArea(child: JsonViewer(jsonObj)), + ), + ); + } +} + +class JsonViewer extends StatefulWidget { + final dynamic jsonObj; + JsonViewer(this.jsonObj); + @override + _JsonViewerState createState() => _JsonViewerState(); +} + +class _JsonViewerState extends State { + @override + Widget build(BuildContext context) { + final content = widget.jsonObj; + if (content == null) return Text('{}'); + if (content is List) return JsonArrayViewer(content, notRoot: false); + return JsonObjectViewer(content, notRoot: false); + } +} + +class JsonObjectViewer extends StatefulWidget { + final Map jsonObj; + final bool notRoot; + + JsonObjectViewer(this.jsonObj, {this.notRoot = false}); + + @override + JsonObjectViewerState createState() => new JsonObjectViewerState(); +} + +class JsonObjectViewerState extends State { + Map openFlag = Map(); + + @override + Widget build(BuildContext context) { + if (widget.notRoot) { + return Container( + padding: EdgeInsets.only(left: 14.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: _getList(), + ), + ); + } + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: _getList(), + ); + } + + List _getList() { + List list = []; + for (MapEntry entry in widget.jsonObj.entries) { + bool ex = isExtensible(entry.value); + bool ink = isInkWell(entry.value); + list.add( + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ex + ? openFlag[entry.key] ?? false + ? Icon( + Icons.arrow_drop_down, + size: 20, + color: Colors.black54, + ) + : Icon( + Icons.arrow_right, + size: 20, + color: Colors.black54, + ) + : const Icon( + Icons.arrow_right, + color: Colors.black54, + size: 20, + ), + (ex && ink) + ? InkWell( + child: Text( + entry.key, + style: TextStyle( + color: Colors.deepPurple, + fontWeight: FontWeight.w600, + ), + ), + onTap: () => _toggleOpenFlag(entry.key), + ) + : Text( + entry.key, + style: TextStyle( + color: entry.value == null + ? Colors.black54 + : Colors.deepPurple, + fontWeight: FontWeight.w600, + ), + ), + Text( + ':', + style: TextStyle(color: Colors.black54), + ), + const SizedBox(width: 3), + getValueWidget(entry) + ], + ), + ); + list.add(const SizedBox(height: 4)); + if (openFlag[entry.key] ?? false) { + list.add(getContentWidget(entry.value)); + } + } + return list; + } + + static Widget getContentWidget(dynamic content) { + if (content is List) { + return JsonArrayViewer(content, notRoot: true); + } else { + return JsonObjectViewer(content, notRoot: true); + } + } + + static isInkWell(dynamic content) { + if (content == null) { + return false; + } else if (content is int) { + return false; + } else if (content is String) { + return false; + } else if (content is bool) { + return false; + } else if (content is double) { + return false; + } else if (content is List) { + if (content.isEmpty) { + return false; + } else { + return true; + } + } + return true; + } + + Widget getValueWidget(MapEntry entry) { + if (entry.value == null) { + return Expanded( + child: Text('undefined', style: TextStyle(color: Colors.grey)), + ); + } else if (entry.value is int) { + return Expanded( + child: Text( + entry.value.toString(), + style: TextStyle(color: Colors.teal), + ), + ); + } else if (entry.value is String) { + return Expanded( + child: Text( + '\"' + entry.value + '\"', + style: TextStyle(color: Colors.redAccent), + ), + ); + } else if (entry.value is bool) { + return Expanded( + child: Text( + entry.value.toString(), + style: TextStyle(color: Colors.purple), + ), + ); + } else if (entry.value is double) { + return Expanded( + child: Text( + entry.value.toString(), + style: TextStyle(color: Colors.teal), + ), + ); + } else if (entry.value is List) { + if (entry.value.isEmpty) { + return Text( + 'Array[0]', + style: TextStyle(color: Colors.black54), + ); + } else { + return InkWell( + child: Text( + 'Array<${getTypeName(entry.value[0])}>[${entry.value.length}]', + style: TextStyle(color: Colors.black54), + ), + onTap: () => _toggleOpenFlag(entry.key), + ); + } + } + return InkWell( + child: Text('Object', style: TextStyle(color: Colors.black54)), + onTap: () => _toggleOpenFlag(entry.key), + ); + } + + static isExtensible(dynamic content) { + if (content == null) { + return false; + } else if (content is int) { + return false; + } else if (content is String) { + return false; + } else if (content is bool) { + return false; + } else if (content is double) { + return false; + } + return true; + } + + static getTypeName(dynamic content) { + if (content is int) { + return 'int'; + } else if (content is String) { + return 'String'; + } else if (content is bool) { + return 'bool'; + } else if (content is double) { + return 'double'; + } else if (content is List) { + return 'List'; + } + return 'Object'; + } + + void _toggleOpenFlag(dynamic key) { + setState(() { + openFlag[key] = !(openFlag[key] ?? false); + }); + } +} + +class JsonArrayViewer extends StatefulWidget { + final List jsonArray; + + final bool notRoot; + + JsonArrayViewer(this.jsonArray, {this.notRoot = false}); + + @override + _JsonArrayViewerState createState() => new _JsonArrayViewerState(); +} + +class _JsonArrayViewerState extends State { + late List openFlag; + + @override + Widget build(BuildContext context) { + if (widget.notRoot) { + return Container( + padding: EdgeInsets.only(left: 14.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: _getList(), + ), + ); + } + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: _getList(), + ); + } + + @override + void initState() { + super.initState(); + openFlag = List.filled(widget.jsonArray.length, false); + openFlag[0] = true; + } + + _getList() { + List list = []; + int i = 0; + for (dynamic content in widget.jsonArray) { + bool ex = JsonObjectViewerState.isExtensible(content); + bool ink = JsonObjectViewerState.isInkWell(content); + list.add(Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ex + ? openFlag[i] + ? Icon( + Icons.arrow_drop_down, + size: 20, + color: Colors.grey[700], + ) + : Icon(Icons.arrow_right, size: 20, color: Colors.grey[700]) + : const Icon( + Icons.arrow_right, + color: Color.fromARGB(0, 0, 0, 0), + size: 20, + ), + (ex && ink) + ? getInkWell(i) + : Text( + '[$i]', + style: TextStyle( + color: content == null ? Colors.grey : Colors.deepPurple, + ), + ), + Text(':', style: TextStyle(color: Colors.grey)), + const SizedBox(width: 3), + getValueWidget(content, i) + ], + )); + list.add(const SizedBox(height: 4)); + if (openFlag[i]) { + list.add(JsonObjectViewerState.getContentWidget(content)); + } + i++; + } + return list; + } + + Widget getInkWell(int index) { + return InkWell( + child: Text( + '[$index]', + style: TextStyle( + color: Colors.deepPurple, + fontWeight: FontWeight.w600, + ), + ), + onTap: () => _toggleOpenFlag(index), + ); + } + + Widget getValueWidget(dynamic content, int index) { + if (content == null) { + return Expanded( + child: Text('undefined', style: TextStyle(color: Colors.grey)), + ); + } else if (content is int) { + return Expanded( + child: Text(content.toString(), style: TextStyle(color: Colors.teal)), + ); + } else if (content is String) { + return Expanded( + child: Text( + '\"' + content + '\"', + style: TextStyle(color: Colors.redAccent), + ), + ); + } else if (content is bool) { + return Expanded( + child: Text( + content.toString(), + style: TextStyle(color: Colors.purple), + ), + ); + } else if (content is double) { + return Expanded( + child: Text( + content.toString(), + style: TextStyle(color: Colors.teal), + ), + ); + } else if (content is List) { + if (content.isEmpty) { + return Text('Array[0]', style: TextStyle(color: Colors.grey)); + } else { + return InkWell( + child: Text( + 'Array<${JsonObjectViewerState.getTypeName(content)}>[${content.length}]', + style: TextStyle(color: Colors.grey), + ), + onTap: () => _toggleOpenFlag(index), + ); + } + } + return InkWell( + child: Text('Object', style: TextStyle(color: Colors.black54)), + onTap: () => _toggleOpenFlag(index), + ); + } + + void _toggleOpenFlag(int index) { + setState(() { + openFlag[index] = !openFlag[index]; + }); + } +} diff --git a/lib/helper/alice_save_helper.dart b/lib/helper/alice_save_helper.dart index 82a616b..2fdda6e 100644 --- a/lib/helper/alice_save_helper.dart +++ b/lib/helper/alice_save_helper.dart @@ -1,6 +1,6 @@ import 'dart:convert'; -import 'package:flutter_alice/helper/alice_conversion_helper.dart'; +import 'package:flutter_alice/helper/alice_convert_helper.dart'; import 'package:flutter_alice/model/alice_http_call.dart'; import 'package:flutter_alice/ui/utils/alice_parser.dart'; @@ -28,7 +28,7 @@ class AliceSaveHelper { stringBuffer.write("Endpoint: ${call.endpoint} \n"); stringBuffer.write("Client: ${call.client} \n"); stringBuffer - .write("Duration ${AliceConversionHelper.formatTime(call.duration)}\n"); + .write("Duration ${AliceConvertHelper.formatTime(call.duration)}\n"); stringBuffer.write("Secured connection: ${call.secure}\n"); stringBuffer.write("Completed: ${!call.loading} \n"); stringBuffer.write("--------------------------------------------\n"); @@ -41,7 +41,7 @@ class AliceSaveHelper { stringBuffer .write("Request headers: ${_encoder.convert(call.request!.headers)}\n"); stringBuffer.write( - "Request size: ${AliceConversionHelper.formatBytes(call.request!.size)}\n"); + "Request size: ${AliceConvertHelper.formatBytes(call.request!.size)}\n"); stringBuffer.write( "Request body: ${AliceParser.formatBody(call.request!.body, AliceParser.getContentType(call.request!.headers))}\n"); stringBuffer.write("--------------------------------------------\n"); @@ -50,7 +50,7 @@ class AliceSaveHelper { stringBuffer.write("Response time: ${call.response!.time}\n"); stringBuffer.write("Response status: ${call.response!.status}\n"); stringBuffer.write( - "Response size: ${AliceConversionHelper.formatBytes(call.response!.size)}\n"); + "Response size: ${AliceConvertHelper.formatBytes(call.response!.size)}\n"); stringBuffer.write( "Response headers: ${_encoder.convert(call.response!.headers)}\n"); stringBuffer.write( diff --git a/lib/ui/page/alice_about.dart b/lib/ui/page/alice_about.dart new file mode 100644 index 0000000..674a19b --- /dev/null +++ b/lib/ui/page/alice_about.dart @@ -0,0 +1,264 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +// import 'package:url_launcher/url_launcher.dart'; // Add url_launcher dependency if needed + +class AliceAbout extends StatefulWidget { + const AliceAbout({super.key}); + + @override + State createState() => _AliceAboutState(); +} + +class _AliceAboutState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + // backgroundColor: Colors.white, + appBar: AppBar( + title: const Text('About Alice'), + backgroundColor: Colors.green, + foregroundColor: Colors.white, + ), + body: ListView( + padding: const EdgeInsets.all(16.0), + children: [ + // Introduction + Card( + color: Colors.green[50], + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Alice - HTTP Inspector for Flutter', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Colors.green[800], + ), + ), + const SizedBox(height: 8), + const Text( + 'Alice is a powerful HTTP Inspector tool for Flutter developers. It captures, stores, and displays HTTP requests and responses in a user-friendly interface.', + style: TextStyle(fontSize: 16), + ), + ], + ), + ), + ), + const SizedBox(height: 20), + + // Contact Information + const Text( + 'Contact Information', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.green, + ), + ), + const SizedBox(height: 10), + Card( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + ListTile( + leading: const Icon(Icons.email, color: Colors.green), + title: const Text('Email'), + subtitle: const SelectableText('hautv.fami@gmail.com'), + ), + ListTile( + leading: const Icon(Icons.phone, color: Colors.green), + title: const Text('Phone'), + subtitle: const SelectableText('+84 888 866 930'), + ), + const SizedBox(height: 10), + Text( + 'Feel free to contact us for support, guidance, or any questions about Alice.', + style: TextStyle(fontSize: 14, color: Colors.grey[600]), + ), + ], + ), + ), + ), + const SizedBox(height: 20), + + // Support Us + const Text( + 'Support Us', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.green, + ), + ), + const SizedBox(height: 10), + Card( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + // PayPal + ElevatedButton.icon( + onPressed: () => _launchUrl('https://paypal.me/hautvfami'), + icon: const Icon(Icons.payment), + label: const Text('Donate via PayPal'), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.green, + foregroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8.0), + ), + ), + ), + const SizedBox(height: 10), + Text( + 'Support our development with a donation.', + style: TextStyle(fontSize: 14, color: Colors.grey[600]), + ), + const Divider(height: 20), + + // Rate Our Apps + const Text( + 'Rate Our Apps on Google Play\n(5 stars appreciated!)', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500), + textAlign: TextAlign.center, + ), + const SizedBox(height: 10), + Wrap( + spacing: 10, + alignment: WrapAlignment.center, + children: [ + _buildAppButton( + 'Drunk Deck', + 'https://play.google.com/store/apps/details?id=com.stark.drunk_deck&hl=en&gl=us', + ), + _buildAppButton( + 'QR Scan', + 'https://play.google.com/store/apps/details?id=com.stark.qr_scan&hl=en&gl=us', + ), + _buildAppButton( + 'Drinking Games', + 'https://play.google.com/store/apps/details?id=com.stark.drinking&hl=en&gl=us', + ), + ], + ), + const Divider(height: 20), + + // Star the Repo + ElevatedButton.icon( + onPressed: () => _launchUrl( + 'https://github.com/hautvfami/flutter-alice'), + icon: const Icon(Icons.star), + label: const Text('Star on GitHub'), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.green, + foregroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8.0), + ), + ), + ), + const SizedBox(height: 10), + Text( + 'Give us a star to show your support!', + style: TextStyle(fontSize: 14, color: Colors.grey[600]), + ), + ], + ), + ), + ), + const SizedBox(height: 20), + + // Closing + SafeArea( + child: Center( + child: Text( + 'We appreciate any support! 🎉', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.green[700], + ), + ), + ), + ), + ], + ), + ); + } + + Widget _buildAppButton(String appName, String url) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4.0), + child: ElevatedButton( + onPressed: () => _launchUrl(url), + child: Text('Rate $appName'), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.white, + foregroundColor: Colors.green, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8.0), + ), + ), + ), + ); + } + + void _launchUrl(String url) async { + _showLinkDialog(context, url, 'Open Link'); + } + + void _showLinkDialog(BuildContext context, String url, String title) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text(title, textAlign: TextAlign.center), + backgroundColor: Colors.white, + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Image.network( + 'https://api.qrserver.com/v1/create-qr-code/?size=150x150&data=${Uri.encodeComponent(url)}', + width: 150, + height: 150, + errorBuilder: (context, error, stackTrace) => + Icon(Icons.qr_code, size: 150, color: Colors.grey), + ), + const SizedBox(height: 10), + SelectableText( + url, + style: TextStyle(fontSize: 14), + ), + const SizedBox(height: 10), + Text( + 'Scan the QR code or copy the link above and paste it into your browser.', + style: TextStyle(fontSize: 12, color: Colors.grey), + textAlign: TextAlign.center, + ), + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: Text('Close'), + ), + ElevatedButton( + onPressed: () { + Clipboard.setData(ClipboardData(text: url)); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Copied to clipboard: $url')), + ); + Navigator.of(context).pop(); + }, + child: Text('Copy'), + ), + ], + ); + }, + ); + } +} diff --git a/lib/ui/page/alice_call_details_screen.dart b/lib/ui/page/alice_call_details_screen.dart index 38ef228..4c4ec83 100644 --- a/lib/ui/page/alice_call_details_screen.dart +++ b/lib/ui/page/alice_call_details_screen.dart @@ -1,17 +1,18 @@ -import 'dart:developer'; +import 'dart:convert'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_alice/core/alice_core.dart'; import 'package:flutter_alice/helper/alice_save_helper.dart'; import 'package:flutter_alice/model/alice_http_call.dart'; import 'package:flutter_alice/model/alice_http_request.dart'; -import 'package:flutter_alice/ui/utils/alice_constants.dart'; +import 'package:flutter_alice/ui/utils/alice_theme.dart'; import 'package:flutter_alice/ui/widget/alice_call_error_widget.dart'; import 'package:flutter_alice/ui/widget/alice_call_overview_widget.dart'; import 'package:flutter_alice/ui/widget/alice_call_request_widget.dart'; import 'package:flutter_alice/ui/widget/alice_call_response_widget.dart'; import 'package:share_plus/share_plus.dart'; +// import 'package:collection/collection.dart'; class AliceCallDetailsScreen extends StatefulWidget { final AliceHttpCall call; @@ -20,41 +21,27 @@ class AliceCallDetailsScreen extends StatefulWidget { AliceCallDetailsScreen(this.call, this.core); @override - _AliceCallDetailsScreenState createState() => _AliceCallDetailsScreenState(); + State createState() => _AliceCallDetailsScreenState(); } -class _AliceCallDetailsScreenState extends State - with SingleTickerProviderStateMixin { - AliceHttpCall get call => widget.call; - - @override - void initState() { - super.initState(); - } +class _AliceCallDetailsScreenState extends State { + bool isLoading = false; @override Widget build(BuildContext context) { return Theme( - data: ThemeData( - brightness: widget.core.brightness, - primarySwatch: Colors.green, - ), + data: aliceTheme, child: StreamBuilder>( stream: widget.core.callsSubject, initialData: [widget.call], - builder: (context, callsSnapshot) { - if (callsSnapshot.hasData) { - AliceHttpCall? call = callsSnapshot.data?.firstWhere( - (snapshotCall) => snapshotCall.id == widget.call.id, - orElse: null); - if (call != null) { - return _buildMainWidget(); - } else { - return _buildErrorWidget(); - } - } else { - return _buildErrorWidget(); - } + builder: (context, snapshot) { + if (!snapshot.hasData) return _buildErrorWidget(context); + final callSns = snapshot.data?.firstWhereOrNull( + (c) => c.id == widget.call.id, + ); + if (callSns == null) return _buildErrorWidget(context); + + return _buildMainWidget(); }, ), ); @@ -65,67 +52,87 @@ class _AliceCallDetailsScreenState extends State length: 4, child: Scaffold( floatingActionButton: FloatingActionButton( - backgroundColor: AliceConstants.lightRed, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(32), - ), key: Key('share_key'), - onPressed: () async { - await Clipboard.setData( - ClipboardData(text: await _getSharableResponseString()), - ); - Share.share( - await _getSharableResponseString(), - subject: 'Request Details', - ); - }, - child: Icon(Icons.share, color: Colors.white), + onPressed: _onShare, + child: isLoading + ? Center(child: CircularProgressIndicator()) + : Icon(Icons.share, color: Colors.white), ), appBar: AppBar( + centerTitle: true, + foregroundColor: Colors.white, bottom: TabBar( - indicatorColor: AliceConstants.lightRed, - tabs: _getTabBars(), + indicatorColor: Colors.white, + labelColor: Colors.white, + unselectedLabelColor: Colors.white70, + labelStyle: TextStyle(fontWeight: FontWeight.bold), + tabs: _tabBars, ), - title: Text('Alice - HTTP Call Details'), + title: Text('Alice - Call Details'), ), - body: TabBarView( - children: _getTabBarViewList(), + body: Padding( + padding: const EdgeInsets.all(8.0), + child: TabBarView(children: _tabBarViews), ), ), ); } - Widget _buildErrorWidget() { - return Center(child: Text("Failed to load data")); + Widget _buildErrorWidget(BuildContext context) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.error_outline, size: 64, color: Colors.redAccent), + SizedBox(height: 16), + Text( + "Failed to load data", + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w500, + color: Theme.of(context).textTheme.bodyLarge?.color, + ), + ), + ], + ), + ); } - Future _getSharableResponseString() async { - log(widget.call.getCurlCommand(), name: 'CURL'); - return AliceSaveHelper.buildCallLog(widget.call); + List get _tabBars { + return [ + Tab(icon: Icon(Icons.visibility), text: "Overview"), + Tab(icon: Icon(Icons.call_made), text: "Request"), + Tab(icon: Icon(Icons.call_received), text: "Response"), + Tab(icon: Icon(Icons.error), text: "Error"), + ]; } - List _getTabBars() { - List widgets = []; - widgets.add(Tab(icon: Icon(Icons.info_outline), text: "Overview")); - widgets.add(Tab(icon: Icon(Icons.arrow_upward), text: "Request")); - widgets.add(Tab(icon: Icon(Icons.arrow_downward), text: "Response")); - widgets.add( - Tab( - icon: Icon(Icons.warning), - text: "Error", - ), - ); - return widgets; + List get _tabBarViews { + return [ + AliceCallOverviewWidget(widget.call), + AliceCallRequestWidget(widget.call.request ?? AliceHttpRequest()), + AliceCallResponseWidget(widget.call), + AliceCallErrorWidget(widget.call), + ]; } - List _getTabBarViewList() { - List widgets = []; - widgets.add(AliceCallOverviewWidget(widget.call)); - widgets.add( - AliceCallRequestWidget(widget.call.request ?? AliceHttpRequest()), + Future _onShare() async { + setState(() => isLoading = true); + final text = await compute(AliceSaveHelper.buildCallLog, widget.call); + final data = await compute(utf8.encode, text); + + final XFile file = XFile.fromData( + data, + mimeType: 'text/plain', + name: 'alice.txt', ); - widgets.add(AliceCallResponseWidget(widget.call)); - widgets.add(AliceCallErrorWidget(widget.call)); - return widgets; + final params = ShareParams( + files: [file], + text: 'Please check!', + subject: 'Alice HTTP Call Log', + ); + + await SharePlus.instance.share(params); + setState(() => isLoading = false); } } diff --git a/lib/ui/page/alice_calls_list_screen.dart b/lib/ui/page/alice_calls_list_screen.dart index 23ff168..dbcf217 100644 --- a/lib/ui/page/alice_calls_list_screen.dart +++ b/lib/ui/page/alice_calls_list_screen.dart @@ -1,13 +1,19 @@ +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_alice/core/alice_core.dart'; -import 'package:flutter_alice/helper/alice_alert_helper.dart'; +// import 'package:flutter_alice/helper/alice_alert_helper.dart'; import 'package:flutter_alice/model/alice_http_call.dart'; -import 'package:flutter_alice/model/alice_menu_item.dart'; +// import 'package:flutter_alice/model/alice_menu_item.dart'; +import 'package:flutter_alice/ui/page/alice_about.dart'; import 'package:flutter_alice/ui/page/alice_call_details_screen.dart'; +import 'package:flutter_alice/ui/page/alice_logs_screen.dart'; import 'package:flutter_alice/ui/utils/alice_constants.dart'; +import 'package:flutter_alice/ui/utils/alice_theme.dart'; import 'package:flutter_alice/ui/widget/alice_call_list_item_widget.dart'; +import 'package:flutter_alice/ui/widget/alice_logs_widget.dart'; +// import 'package:flutter_alice/ui/widget/alice_menu_dialog.dart'; -import 'alice_stats_screen.dart'; +// import 'alice_stats_screen.dart'; class AliceCallsListScreen extends StatefulWidget { final AliceCore _aliceCore; @@ -15,38 +21,64 @@ class AliceCallsListScreen extends StatefulWidget { AliceCallsListScreen(this._aliceCore); @override - _AliceCallsListScreenState createState() => _AliceCallsListScreenState(); + State createState() => _AliceCallsListScreenState(); } class _AliceCallsListScreenState extends State { AliceCore get aliceCore => widget._aliceCore; bool _searchEnabled = false; - final TextEditingController _queryTextEditingController = - TextEditingController(); - List _menuItems = []; - - _AliceCallsListScreenState() { - _menuItems.add(AliceMenuItem("Delete", Icons.delete)); - _menuItems.add(AliceMenuItem("Stats", Icons.insert_chart)); - _menuItems.add(AliceMenuItem("Save", Icons.save)); - } + final _textController = TextEditingController(); @override Widget build(BuildContext context) { return Theme( - data: ThemeData( - brightness: widget._aliceCore.brightness, - primarySwatch: Colors.green, - ), + data: aliceTheme, child: Scaffold( appBar: AppBar( - title: _searchEnabled ? _buildSearchField() : _buildTitleWidget(), + title: _buildTitle(), + centerTitle: true, actions: [ _buildSearchButton(), _buildMenuButton(), ], ), - body: _buildCallsListWrapper(), + body: Column( + children: [ + Expanded(child: _buildCallsListWrapper()), + if (aliceCore.logs.isNotEmpty) + GestureDetector( + onTap: () { + aliceCore.push((context) => AliceLogsScreen(aliceCore)); + }, + child: Container( + height: MediaQuery.of(context).size.height * 0.15, + decoration: BoxDecoration( + color: Colors.green, + borderRadius: BorderRadius.all( + Radius.circular(16.0), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(left: 16.0, top: 16.0), + child: Text( + '>Console:', + style: TextStyle( + fontSize: 16.0, + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + ), + Expanded(child: AliceLogsWidget(aliceCore: aliceCore)), + ], + ), + ), + ) + ], + ), ), ); } @@ -54,12 +86,12 @@ class _AliceCallsListScreenState extends State { @override void dispose() { super.dispose(); - _queryTextEditingController.dispose(); + _textController.dispose(); } Widget _buildSearchButton() { return IconButton( - icon: Icon(Icons.search), + icon: Icon(Icons.search_rounded), onPressed: _onSearchClicked, ); } @@ -67,82 +99,69 @@ class _AliceCallsListScreenState extends State { void _onSearchClicked() { setState(() { _searchEnabled = !_searchEnabled; - if (!_searchEnabled) { - _queryTextEditingController.text = ""; - } + if (!_searchEnabled) _textController.text = ""; }); } Widget _buildMenuButton() { - return PopupMenuButton( - onSelected: (AliceMenuItem item) => _onMenuItemSelected(item), - itemBuilder: (BuildContext context) { - return _menuItems.map((AliceMenuItem item) { - return PopupMenuItem( - value: item, - child: Row(children: [ - Icon( - item.iconData, - color: AliceConstants.lightRed, - ), - Padding( - padding: EdgeInsets.only(left: 10), - ), - Text(item.title) - ]), - ); - }).toList(); + return IconButton( + onPressed: () { + aliceCore.push((context) => AliceAbout()); + // showDialog( + // context: context, + // builder: (_) => AliceMenuDialog( + // onMenuItemSelected: _onMenuItemSelected, + // ), + // ); }, + icon: Icon(CupertinoIcons.settings), ); } - Widget _buildTitleWidget() { - return Text("Alice - Inspector"); - } + Widget _buildTitle() { + if (!_searchEnabled) return Text("Alice - Inspector"); - Widget _buildSearchField() { return TextField( - controller: _queryTextEditingController, + controller: _textController, autofocus: true, decoration: InputDecoration( hintText: "Search http request...", - hintStyle: TextStyle( - fontSize: 16.0, - color: Colors.white, - ), + hintStyle: TextStyle(fontSize: 16.0, color: Colors.white), // border: InputBorder, ), + onTapOutside: (event) => FocusScope.of(context).unfocus(), style: TextStyle(fontSize: 16.0), - onChanged: _updateSearchQuery, + onChanged: (_) => setState(() {}), ); } - void _onMenuItemSelected(AliceMenuItem menuItem) { - if (menuItem.title == "Delete") { - _showRemoveDialog(); - } - if (menuItem.title == "Stats") { - _showStatsScreen(); - } - } + // void _onMenuItemSelected(AliceMenuItem menuItem) { + // if (menuItem.title == "Delete") _showRemoveDialog(); + // if (menuItem.title == "Stats") { + // aliceCore.push((context) => AliceStatsScreen(aliceCore)); + // } + // if (menuItem.title == "About") { + // aliceCore.push((context) => AliceAbout()); + // } + // } Widget _buildCallsListWrapper() { return StreamBuilder>( stream: aliceCore.callsSubject, builder: (context, snapshot) { - List calls = snapshot.data ?? []; - String query = _queryTextEditingController.text.trim(); + List calls = snapshot.data ?? []; + final query = _textController.text.trim(); + if (query.isNotEmpty) { calls = calls - .where((call) => - call.endpoint.toLowerCase().contains(query.toLowerCase())) + .where( + (e) => e.endpoint.toLowerCase().contains(query.toLowerCase())) .toList(); } - if (calls.isNotEmpty) { - return _buildCallsListWidget(calls); - } else { - return _buildEmptyWidget(); - } + + if (calls.isEmpty) return _buildEmptyWidget(); + + return _buildCallsListWidget(calls); }, ); } @@ -151,83 +170,63 @@ class _AliceCallsListScreenState extends State { return Container( margin: EdgeInsets.symmetric(horizontal: 32), child: Center( - child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon( - Icons.error_outline, - color: AliceConstants.orange, - ), - const SizedBox(height: 6), - Text( - "There are no calls to show", - style: TextStyle(fontSize: 18), - ), - const SizedBox(height: 12), - Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - "• Check if you send any http request", - style: TextStyle(fontSize: 12), - textAlign: TextAlign.center, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.error_outline, color: AliceConstants.orange), + const SizedBox(height: 6), + Text("There are no calls to show", style: TextStyle(fontSize: 18)), + const SizedBox(height: 12), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "• Check if you send any http request", + style: TextStyle(fontSize: 12), + textAlign: TextAlign.center, + ), + Text( + "• Check your Alice configuration", + style: TextStyle(fontSize: 12), + textAlign: TextAlign.center, + ), + Text( + "• Check search filters", + style: TextStyle(fontSize: 12), + textAlign: TextAlign.center, + ) + ], ), - Text( - "• Check your Alice configuration", - style: TextStyle(fontSize: 12), - textAlign: TextAlign.center, - ), - Text( - "• Check search filters", - style: TextStyle(fontSize: 12), - textAlign: TextAlign.center, - ) - ]) - ]), + ], + ), ), ); } Widget _buildCallsListWidget(List calls) { - return ListView.builder( + return ListView.separated( itemCount: calls.length, - itemBuilder: (context, index) { - return AliceCallListItemWidget(calls[index], _onListItemClicked); + separatorBuilder: (_, __) => Divider(height: 1, color: Colors.black12), + itemBuilder: (_, index) { + return AliceCallListItemWidget( + calls[index], + (call) => aliceCore.push( + (context) => AliceCallDetailsScreen(call, aliceCore), + ), + ); }, ); } - void _onListItemClicked(AliceHttpCall call) { - Navigator.push( - widget._aliceCore.getContext()!, - MaterialPageRoute( - builder: (context) => AliceCallDetailsScreen(call, widget._aliceCore), - ), - ); - } - - void _showRemoveDialog() { - AliceAlertHelper.showAlert( - context, - "Delete calls", - "Do you want to delete http calls?", - firstButtonTitle: "No", - firstButtonAction: () => {}, - secondButtonTitle: "Yes", - secondButtonAction: () => _removeCalls(), - ); - } - - void _removeCalls() { - aliceCore.removeCalls(); - } - - void _showStatsScreen() { - Navigator.push( - aliceCore.getContext()!, - MaterialPageRoute( - builder: (context) => AliceStatsScreen(widget._aliceCore), - ), - ); - } - - void _updateSearchQuery(String query) { - setState(() {}); - } + // void _showRemoveDialog() { + // AliceAlertHelper.showAlert( + // context, + // "Delete calls", + // "Do you want to delete http calls?", + // firstButtonTitle: "No", + // firstButtonAction: () => {}, + // secondButtonTitle: "Yes", + // secondButtonAction: aliceCore.clears, + // ); + // } } diff --git a/lib/ui/page/alice_logs_screen.dart b/lib/ui/page/alice_logs_screen.dart new file mode 100644 index 0000000..33a52df --- /dev/null +++ b/lib/ui/page/alice_logs_screen.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_alice/core/alice_core.dart'; +import 'package:flutter_alice/ui/widget/alice_logs_widget.dart'; + +class AliceLogsScreen extends StatelessWidget { + final AliceCore aliceCore; + const AliceLogsScreen( + this.aliceCore, { + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.black, + appBar: AppBar( + centerTitle: true, + backgroundColor: Colors.green, + foregroundColor: Colors.white, + title: Text('Alice Logs'), + actions: [ + IconButton( + icon: Icon(Icons.recycling), + onPressed: aliceCore.clearLogs, + ), + ], + ), + body: Container( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(left: 16.0, top: 16.0), + child: Text( + '>Console:', + style: TextStyle( + fontSize: 16.0, + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + ), + Expanded(child: AliceLogsWidget(aliceCore: aliceCore)), + ], + ), + ), + ); + } +} diff --git a/lib/ui/page/alice_stats_screen.dart b/lib/ui/page/alice_stats_screen.dart index 35894b7..448f799 100644 --- a/lib/ui/page/alice_stats_screen.dart +++ b/lib/ui/page/alice_stats_screen.dart @@ -1,7 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_alice/core/alice_core.dart'; -import 'package:flutter_alice/helper/alice_conversion_helper.dart'; +import 'package:flutter_alice/helper/alice_convert_helper.dart'; import 'package:flutter_alice/model/alice_http_call.dart'; +import 'package:flutter_alice/ui/utils/alice_theme.dart'; class AliceStatsScreen extends StatelessWidget { final AliceCore aliceCore; @@ -11,19 +12,12 @@ class AliceStatsScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Theme( - data: ThemeData( - brightness: aliceCore.brightness, - primarySwatch: Colors.green, - ), + data: aliceTheme, child: Scaffold( - appBar: AppBar( - title: Text("Alice - HTTP Inspector - Stats"), - ), - body: Container( + appBar: AppBar(title: Text("Alice - HTTP Inspector - Stats")), + body: ListView( padding: const EdgeInsets.all(8), - child: ListView( - children: _buildMainListWidgets(), - ), + children: _buildMainListWidgets(), ), ), ); @@ -36,16 +30,15 @@ class AliceStatsScreen extends StatelessWidget { _getRow("Success requests:", "${_getSuccessRequests()}"), _getRow("Redirection requests:", "${_getRedirectionRequests()}"), _getRow("Error requests:", "${_getErrorRequests()}"), - _getRow( - "Bytes send:", AliceConversionHelper.formatBytes(_getBytesSent())), + _getRow("Bytes send:", AliceConvertHelper.formatBytes(_getBytesSent())), _getRow("Bytes received:", - AliceConversionHelper.formatBytes(_getBytesReceived())), + AliceConvertHelper.formatBytes(_getBytesReceived())), _getRow("Average request time:", - "${AliceConversionHelper.formatTime(_getAverageRequestTime())}"), + "${AliceConvertHelper.formatTime(_getAverageRequestTime())}"), _getRow("Max request time:", - "${AliceConversionHelper.formatTime(_getMaxRequestTime())}"), + "${AliceConvertHelper.formatTime(_getMaxRequestTime())}"), _getRow("Min request time:", - "${AliceConversionHelper.formatTime(_getMinRequestTime())}"), + "${AliceConvertHelper.formatTime(_getMinRequestTime())}"), _getRow("Get requests:", "${_getRequests("GET")} "), _getRow("Post requests:", "${_getRequests("POST")} "), _getRow("Delete requests:", "${_getRequests("DELETE")} "), @@ -59,54 +52,42 @@ class AliceStatsScreen extends StatelessWidget { Widget _getRow(String label, String value) { return Row( children: [ - Text( - label, - style: _getLabelTextStyle(), - ), - Padding( - padding: EdgeInsets.only(left: 10), - ), - Text( - value, - style: _getValueTextStyle(), - ) + Text(label, style: TextStyle(fontSize: 16)), + Padding(padding: EdgeInsets.only(left: 10)), + Text(value, style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)) ], ); } - TextStyle _getLabelTextStyle() { - return TextStyle(fontSize: 16); - } - - TextStyle _getValueTextStyle() { - return TextStyle(fontSize: 16, fontWeight: FontWeight.bold); - } - - int _getTotalRequests() { - return calls.length; - } + int _getTotalRequests() => calls.length; int _getSuccessRequests() => calls - .where((call) => - call.response != null && - call.response!.status >= 200 && - call.response!.status < 300) + .where( + (call) => + call.response != null && + call.response!.status >= 200 && + call.response!.status < 300, + ) .toList() .length; int _getRedirectionRequests() => calls - .where((call) => - call.response != null && - call.response!.status >= 300 && - call.response!.status < 400) + .where( + (call) => + call.response != null && + call.response!.status >= 300 && + call.response!.status < 400, + ) .toList() .length; int _getErrorRequests() => calls - .where((call) => - call.response != null && - call.response!.status >= 400 && - call.response!.status < 600) + .where( + (call) => + call.response != null && + call.response!.status >= 400 && + call.response!.status < 600, + ) .toList() .length; @@ -115,18 +96,14 @@ class AliceStatsScreen extends StatelessWidget { int _getBytesSent() { int bytes = 0; - calls.forEach((AliceHttpCall call) { - bytes += call.request!.size; - }); + calls.forEach((call) => bytes += call.request!.size); return bytes; } int _getBytesReceived() { int bytes = 0; - calls.forEach((AliceHttpCall call) { - if (call.response != null) { - bytes += call.response!.size; - } + calls.forEach((call) { + if (call.response != null) bytes += call.response!.size; }); return bytes; } @@ -140,9 +117,7 @@ class AliceStatsScreen extends StatelessWidget { requestsWithDurationCount++; } }); - if (requestTimeSum == 0) { - return 0; - } + if (requestTimeSum == 0) return 0; return requestTimeSum ~/ requestsWithDurationCount; } @@ -161,7 +136,7 @@ class AliceStatsScreen extends StatelessWidget { if (calls.length == 0) { minRequestTime = 0; } else { - calls.forEach((AliceHttpCall call) { + calls.forEach((call) { if (call.duration != 0 && call.duration < minRequestTime) { minRequestTime = call.duration; } diff --git a/lib/ui/utils/alice_parser.dart b/lib/ui/utils/alice_parser.dart index f01e206..0d89767 100644 --- a/lib/ui/utils/alice_parser.dart +++ b/lib/ui/utils/alice_parser.dart @@ -28,19 +28,13 @@ class AliceParser { static String? formatBody(dynamic body, String? contentType) { try { - if (body == null) { - return _emptyBody; - } - + if (body == null) return _emptyBody; String? bodyContent = _emptyBody; if (contentType == null || !contentType.toLowerCase().contains(_applicationJson)) { - var bodyTemp = body.toString(); - - if (bodyTemp.length > 0) { - bodyContent = bodyTemp; - } + final bodyTemp = body.toString(); + if (bodyTemp.length > 0) bodyContent = bodyTemp; } else { if (body is String && body.contains("\n")) { bodyContent = body; @@ -75,4 +69,14 @@ class AliceParser { } return _unknownContentType; } + + static ({int color, String message}) logParser(String data) { + if (data.startsWith('0X')) { + final colorString = data.substring(0, 10); + final message = data.substring(10); + final color = int.parse(colorString); + return (color: color, message: message); + } + return (color: 0XFFFFFFFF, message: data); + } } diff --git a/lib/ui/utils/alice_theme.dart b/lib/ui/utils/alice_theme.dart new file mode 100644 index 0000000..fc7b417 --- /dev/null +++ b/lib/ui/utils/alice_theme.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; + +ThemeData aliceTheme = ThemeData( + brightness: Brightness.light, + primarySwatch: Colors.green, + scaffoldBackgroundColor: Colors.grey[50], + appBarTheme: AppBarTheme( + backgroundColor: Colors.green, + elevation: 4, + shadowColor: Colors.black26, + foregroundColor: Colors.white, + ), + floatingActionButtonTheme: FloatingActionButtonThemeData( + backgroundColor: Colors.green, + elevation: 6, + ), +); diff --git a/lib/ui/widget/alice_base_call_details_widget.dart b/lib/ui/widget/alice_base_call_details_widget.dart deleted file mode 100644 index 463f0d6..0000000 --- a/lib/ui/widget/alice_base_call_details_widget.dart +++ /dev/null @@ -1,41 +0,0 @@ -import 'dart:convert'; - -import 'package:flutter/material.dart'; -import 'package:flutter_alice/helper/alice_conversion_helper.dart'; -import 'package:flutter_alice/ui/utils/alice_parser.dart'; - -abstract class AliceBaseCallDetailsWidgetState - extends State { - final JsonEncoder encoder = new JsonEncoder.withIndent(' '); - - Widget getListRow(String name, String value) { - return Row( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SelectableText(name, style: TextStyle(fontWeight: FontWeight.bold)), - Padding(padding: EdgeInsets.only(left: 5)), - Flexible( - fit: FlexFit.loose, - child: SelectableText( - value, - ), - ), - Padding( - padding: EdgeInsets.only(bottom: 18), - ) - ], - ); - } - - String formatBytes(int bytes) => AliceConversionHelper.formatBytes(bytes); - - String formatDuration(int duration) => - AliceConversionHelper.formatTime(duration); - - String? formatBody(dynamic body, String? contentType) => - AliceParser.formatBody(body, contentType); - - String? getContentType(Map? headers) => - AliceParser.getContentType(headers); -} diff --git a/lib/ui/widget/alice_base_mixin.dart b/lib/ui/widget/alice_base_mixin.dart new file mode 100644 index 0000000..076f2ef --- /dev/null +++ b/lib/ui/widget/alice_base_mixin.dart @@ -0,0 +1,36 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter_alice/helper/alice_convert_helper.dart'; +import 'package:flutter_alice/ui/utils/alice_parser.dart'; + +mixin AliceBaseMixin { + final JsonEncoder encoder = new JsonEncoder.withIndent(' '); + + Widget getListRow(String name, String value) { + return RichText( + text: TextSpan( + children: [ + TextSpan( + text: name == 'Body:' ? '$name\n' : '$name\t\t\t', + style: TextStyle(fontWeight: FontWeight.bold, color: Colors.black), + ), + TextSpan( + text: '$value', + style: TextStyle(color: Colors.black), + ), + ], + )); + } + + String formatBytes(int bytes) => AliceConvertHelper.formatBytes(bytes); + + String formatDuration(int duration) => + AliceConvertHelper.formatTime(duration); + + String? formatBody(dynamic body, String? contentType) => + AliceParser.formatBody(body, contentType); + + String? getContentType(Map? headers) => + AliceParser.getContentType(headers); +} diff --git a/lib/ui/widget/alice_call_error_widget.dart b/lib/ui/widget/alice_call_error_widget.dart index 7ebb56d..30c0402 100644 --- a/lib/ui/widget/alice_call_error_widget.dart +++ b/lib/ui/widget/alice_call_error_widget.dart @@ -1,21 +1,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_alice/model/alice_http_call.dart'; -import 'package:flutter_alice/ui/widget/alice_base_call_details_widget.dart'; +import 'package:flutter_alice/ui/widget/alice_base_mixin.dart'; -class AliceCallErrorWidget extends StatefulWidget { +class AliceCallErrorWidget extends StatelessWidget with AliceBaseMixin { final AliceHttpCall call; AliceCallErrorWidget(this.call); - @override - State createState() { - return _AliceCallErrorWidgetState(); - } -} - -class _AliceCallErrorWidgetState - extends AliceBaseCallDetailsWidgetState { - AliceHttpCall get _call => widget.call; + AliceHttpCall get _call => call; @override Widget build(BuildContext context) { diff --git a/lib/ui/widget/alice_call_list_item_widget.dart b/lib/ui/widget/alice_call_list_item_widget.dart index b18e3ac..0cd0135 100644 --- a/lib/ui/widget/alice_call_list_item_widget.dart +++ b/lib/ui/widget/alice_call_list_item_widget.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:flutter_alice/helper/alice_conversion_helper.dart'; +import 'package:flutter_alice/helper/alice_convert_helper.dart'; import 'package:flutter_alice/model/alice_http_call.dart'; import 'package:flutter_alice/model/alice_http_response.dart'; import 'package:flutter_alice/ui/utils/alice_constants.dart'; @@ -14,31 +14,26 @@ class AliceCallListItemWidget extends StatelessWidget { Widget build(BuildContext context) { return InkWell( onTap: () => itemClickAction(call), - child: Column( - children: [ - Container( - padding: const EdgeInsets.all(8), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Flexible( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildMethodAndEndpointRow(context), - const SizedBox(height: 4), - _buildServerRow(), - const SizedBox(height: 4), - _buildStatsRow() - ], - ), - ), - _buildResponseColumn(context) - ], + child: Container( + padding: const EdgeInsets.all(8), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildMethodAndEndpointRow(context), + const SizedBox(height: 4), + _buildServerRow(), + const SizedBox(height: 4), + _buildStatsRow() + ], + ), ), - ), - _buildDivider() - ], + _buildResponseColumn(context) + ], + ), ), ); } @@ -94,13 +89,13 @@ class AliceCallListItemWidget extends StatelessWidget { style: TextStyle(fontSize: 12))), Flexible( flex: 1, - child: Text("${AliceConversionHelper.formatTime(call.duration)}", + child: Text("${AliceConvertHelper.formatTime(call.duration)}", style: TextStyle(fontSize: 12))), Flexible( flex: 1, child: Text( - "${AliceConversionHelper.formatBytes(call.request!.size)} / " - "${AliceConversionHelper.formatBytes(call.response!.size)}", + "${AliceConvertHelper.formatBytes(call.request!.size)} / " + "${AliceConvertHelper.formatBytes(call.response!.size)}", style: TextStyle(fontSize: 12), ), ) @@ -108,10 +103,6 @@ class AliceCallListItemWidget extends StatelessWidget { ); } - Widget _buildDivider() { - return Container(height: 1, color: AliceConstants.grey); - } - String _formatTime(DateTime time) { return "${formatTimeUnit(time.hour)}:" "${formatTimeUnit(time.minute)}:" @@ -132,10 +123,7 @@ class AliceCallListItemWidget extends StatelessWidget { widgets.add( Text( _getStatus(call.response!), - style: TextStyle( - fontSize: 16, - color: _getStatusTextColor(context), - ), + style: TextStyle(fontSize: 16, color: _getStatusTextColor(context)), ), ); return Container( @@ -194,11 +182,7 @@ class AliceCallListItemWidget extends StatelessWidget { } return Padding( padding: EdgeInsets.only(right: 3), - child: Icon( - iconData, - color: iconColor, - size: 12, - ), + child: Icon(iconData, color: iconColor, size: 12), ); } } diff --git a/lib/ui/widget/alice_call_overview_widget.dart b/lib/ui/widget/alice_call_overview_widget.dart index 1582391..578c41e 100644 --- a/lib/ui/widget/alice_call_overview_widget.dart +++ b/lib/ui/widget/alice_call_overview_widget.dart @@ -1,21 +1,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_alice/model/alice_http_call.dart'; -import 'package:flutter_alice/ui/widget/alice_base_call_details_widget.dart'; +import 'package:flutter_alice/ui/widget/alice_base_mixin.dart'; -class AliceCallOverviewWidget extends StatefulWidget { +class AliceCallOverviewWidget extends StatelessWidget with AliceBaseMixin { final AliceHttpCall call; AliceCallOverviewWidget(this.call); - @override - State createState() { - return _AliceCallOverviewWidget(); - } -} - -class _AliceCallOverviewWidget - extends AliceBaseCallDetailsWidgetState { - AliceHttpCall get _call => widget.call; + AliceHttpCall get _call => call; @override Widget build(BuildContext context) { diff --git a/lib/ui/widget/alice_call_request_widget.dart b/lib/ui/widget/alice_call_request_widget.dart index f4bfe89..4dc06db 100644 --- a/lib/ui/widget/alice_call_request_widget.dart +++ b/lib/ui/widget/alice_call_request_widget.dart @@ -1,37 +1,27 @@ import 'package:flutter/material.dart'; // import 'package:flutter_alice/model/alice_http_call.dart'; import 'package:flutter_alice/model/alice_http_request.dart'; -import 'package:flutter_alice/ui/widget/alice_base_call_details_widget.dart'; +import 'package:flutter_alice/ui/widget/alice_base_mixin.dart'; -class AliceCallRequestWidget extends StatefulWidget { +class AliceCallRequestWidget extends StatelessWidget with AliceBaseMixin { final AliceHttpRequest request; AliceCallRequestWidget(this.request); - @override - State createState() { - return _AliceCallRequestWidget(); - } -} - -class _AliceCallRequestWidget - extends AliceBaseCallDetailsWidgetState { - AliceHttpRequest get _request => widget.request; - @override Widget build(BuildContext context) { List rows = []; - rows.add(getListRow("Started:", _request.time.toString())); - rows.add(getListRow("Bytes sent:", formatBytes(_request.size))); - rows.add(getListRow("Content type:", getContentType(_request.headers)!)); + rows.add(getListRow("Started:", request.time.toString())); + rows.add(getListRow("Bytes sent:", formatBytes(request.size))); + rows.add(getListRow("Content type:", getContentType(request.headers)!)); - var body = _request.body; + var body = request.body; String? bodyContent = "Body is empty"; if (body != null) { - bodyContent = formatBody(body, getContentType(_request.headers)); + bodyContent = formatBody(body, getContentType(request.headers)); } rows.add(getListRow("Body:", bodyContent!)); - var formDataFields = _request.formDataFields; + var formDataFields = request.formDataFields; if (formDataFields?.isNotEmpty == true) { rows.add(getListRow("Form data fields: ", "")); formDataFields!.forEach( @@ -40,7 +30,7 @@ class _AliceCallRequestWidget }, ); } - var formDataFiles = _request.formDataFiles; + var formDataFiles = request.formDataFiles; if (formDataFiles?.isNotEmpty == true) { rows.add(getListRow("Form data files: ", "")); formDataFiles!.forEach( @@ -51,24 +41,24 @@ class _AliceCallRequestWidget ); } - var headers = _request.headers; + var headers = request.headers; var headersContent = "Headers are empty"; if (headers.length > 0) { headersContent = ""; } rows.add(getListRow("Headers: ", headersContent)); - _request.headers.forEach((header, value) { + request.headers.forEach((header, value) { rows.add(getListRow(" • $header:", value.toString())); }); - print("qr: ${_request.queryParameters}"); - var queryParameters = _request.queryParameters; + print("qr: ${request.queryParameters}"); + var queryParameters = request.queryParameters; var queryParametersContent = "Query parameters are empty"; if (queryParameters.length > 0) { queryParametersContent = ""; } rows.add(getListRow("Query Parameters: ", queryParametersContent)); - _request.queryParameters.forEach((query, value) { + request.queryParameters.forEach((query, value) { rows.add(getListRow(" • $query:", value.toString())); }); diff --git a/lib/ui/widget/alice_call_response_widget.dart b/lib/ui/widget/alice_call_response_widget.dart index 5a408fc..3078327 100644 --- a/lib/ui/widget/alice_call_response_widget.dart +++ b/lib/ui/widget/alice_call_response_widget.dart @@ -1,6 +1,10 @@ +import 'dart:convert'; + import 'package:flutter/material.dart'; +import 'package:flutter_alice/core/alice_core.dart'; +import 'package:flutter_alice/helper/alice_json_viewer.dart'; import 'package:flutter_alice/model/alice_http_call.dart'; -import 'package:flutter_alice/ui/widget/alice_base_call_details_widget.dart'; +import 'package:flutter_alice/ui/widget/alice_base_mixin.dart'; class AliceCallResponseWidget extends StatefulWidget { final AliceHttpCall call; @@ -8,13 +12,11 @@ class AliceCallResponseWidget extends StatefulWidget { AliceCallResponseWidget(this.call); @override - State createState() { - return _AliceCallResponseWidgetState(); - } + State createState() => _AliceCallResponseWidgetState(); } -class _AliceCallResponseWidgetState - extends AliceBaseCallDetailsWidgetState { +class _AliceCallResponseWidgetState extends State + with AliceBaseMixin { static const _imageContentType = "image"; static const _videoContentType = "video"; static const _jsonContentType = "json"; @@ -172,9 +174,24 @@ class _AliceCallResponseWidgetState List _buildTextBodyRows() { List rows = []; - var headers = _call.response!.headers; - var bodyContent = - formatBody(_call.response!.body, getContentType(headers))!; + final headers = _call.response!.headers; + final bodyContent = formatBody( + _call.response!.body, + getContentType(headers), + )!; + + if (_getContentTypeOfResponse()!.toLowerCase().contains(_jsonContentType)) { + final json = jsonDecode(bodyContent); + rows.add( + InkWell( + onTap: () => AliceCore.inst.push( + (_) => AliceJsonViewer(jsonObj: json), + ), + child: getListRow("Body:", bodyContent), + ), + ); + return rows; + } rows.add(getListRow("Body:", bodyContent)); return rows; } @@ -184,25 +201,27 @@ class _AliceCallResponseWidgetState var headers = _call.response!.headers; var contentType = getContentType(headers) ?? ""; - if (_showUnsupportedBody) { - var bodyContent = - formatBody(_call.response!.body, getContentType(headers))!; - rows.add(getListRow("Body:", bodyContent)); + if (_showUnsupportedBody || _call.response?.body == '') { + final bodyContent = formatBody( + _call.response!.body, + getContentType(headers), + )!; + final widget = getListRow("Body:", bodyContent); + rows.add(widget); } else { - rows.add(getListRow( + rows.add( + getListRow( "Body:", "Unsupported body. Alice can render video/image/text body. " "Response has Content-Type: $contentType which can't be handled. " "If you're feeling lucky you can try button below to try render body" - " as text, but it may fail.")); + " as text, but it may fail.", + ), + ); rows.add( ElevatedButton( child: Text("Show unsupported body"), - onPressed: () { - setState(() { - _showUnsupportedBody = true; - }); - }, + onPressed: () => setState(() => _showUnsupportedBody = true), ), ); } @@ -213,11 +232,7 @@ class _AliceCallResponseWidgetState Map requestHeaders = Map(); if (_call.request?.headers != null) { requestHeaders.addAll( - _call.request!.headers.map( - (String key, dynamic value) { - return MapEntry(key, value.toString()); - }, - ), + _call.request!.headers.map((k, v) => MapEntry(k, v.toString())), ); } return requestHeaders; diff --git a/lib/ui/widget/alice_logs_widget.dart b/lib/ui/widget/alice_logs_widget.dart new file mode 100644 index 0000000..fae1663 --- /dev/null +++ b/lib/ui/widget/alice_logs_widget.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_alice/core/alice_core.dart'; +import 'package:flutter_alice/ui/utils/alice_parser.dart'; + +class AliceLogsWidget extends StatelessWidget { + const AliceLogsWidget({ + super.key, + required this.aliceCore, + }); + + final AliceCore aliceCore; + + @override + Widget build(BuildContext context) { + return StreamBuilder>( + stream: aliceCore.logsStream, + builder: (context, snapshot) { + final logs = snapshot.data ?? []; + return SafeArea( + child: ListView.separated( + padding: EdgeInsets.all(8), + reverse: true, + itemBuilder: (_, i) { + final log = AliceParser.logParser(logs[i]); + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Text( + log.message, + style: TextStyle(fontSize: 12, color: Color(log.color)), + ), + ); + }, + separatorBuilder: (_, i) => Divider( + height: 1, + color: Colors.white24, + ), + itemCount: logs.length, + ), + ); + }, + ); + } +} diff --git a/lib/ui/widget/alice_menu_dialog.dart b/lib/ui/widget/alice_menu_dialog.dart new file mode 100644 index 0000000..71a08f7 --- /dev/null +++ b/lib/ui/widget/alice_menu_dialog.dart @@ -0,0 +1,44 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_alice/model/alice_menu_item.dart'; + +class AliceMenuDialog extends StatelessWidget { + final void Function(AliceMenuItem) _onMenuItemSelected; + AliceMenuDialog({ + super.key, + required void Function(AliceMenuItem) onMenuItemSelected, + }) : _onMenuItemSelected = onMenuItemSelected; + final _menuItems = [ + AliceMenuItem("Clear requests", CupertinoIcons.trash), + AliceMenuItem("Statistic", CupertinoIcons.chart_bar), + AliceMenuItem("About", Icons.contact_emergency) + ]; + + @override + Widget build(BuildContext context) { + return Dialog( + child: Container( + padding: EdgeInsets.all(8.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: _menuItems + .map( + (item) => ListTile( + leading: Icon( + item.iconData, + color: Colors.green.shade900, + size: 32, + ), + title: Text(item.title.padRight(20)), + onTap: () { + Navigator.of(context).pop(); + _onMenuItemSelected(item); + }, + ), + ) + .toList(), + ), + ), + ); + } +} diff --git a/media/1.png b/media/1.png deleted file mode 100644 index d12e0cd..0000000 Binary files a/media/1.png and /dev/null differ diff --git a/media/10.png b/media/10.png deleted file mode 100644 index c53e5a9..0000000 Binary files a/media/10.png and /dev/null differ diff --git a/media/11.png b/media/11.png deleted file mode 100644 index 041dd30..0000000 Binary files a/media/11.png and /dev/null differ diff --git a/media/12.png b/media/12.png deleted file mode 100644 index 5622fca..0000000 Binary files a/media/12.png and /dev/null differ diff --git a/media/13.png b/media/13.png deleted file mode 100644 index 13d677d..0000000 Binary files a/media/13.png and /dev/null differ diff --git a/media/2.png b/media/2.png deleted file mode 100644 index 0636b58..0000000 Binary files a/media/2.png and /dev/null differ diff --git a/media/3.png b/media/3.png deleted file mode 100644 index 4c41453..0000000 Binary files a/media/3.png and /dev/null differ diff --git a/media/4.png b/media/4.png deleted file mode 100644 index 8d596d2..0000000 Binary files a/media/4.png and /dev/null differ diff --git a/media/5.png b/media/5.png deleted file mode 100644 index 72720a5..0000000 Binary files a/media/5.png and /dev/null differ diff --git a/media/6.png b/media/6.png deleted file mode 100644 index 221ead7..0000000 Binary files a/media/6.png and /dev/null differ diff --git a/media/7.png b/media/7.png deleted file mode 100644 index 2a6d272..0000000 Binary files a/media/7.png and /dev/null differ diff --git a/media/8.png b/media/8.png deleted file mode 100644 index 3e9690d..0000000 Binary files a/media/8.png and /dev/null differ diff --git a/media/9.png b/media/9.png deleted file mode 100644 index 9bb1556..0000000 Binary files a/media/9.png and /dev/null differ diff --git a/media/IMG_7289.PNG b/media/IMG_7289.PNG new file mode 100644 index 0000000..f05b92f Binary files /dev/null and b/media/IMG_7289.PNG differ diff --git a/media/IMG_7290.PNG b/media/IMG_7290.PNG new file mode 100644 index 0000000..83c1912 Binary files /dev/null and b/media/IMG_7290.PNG differ diff --git a/media/IMG_7291.PNG b/media/IMG_7291.PNG new file mode 100644 index 0000000..4f35878 Binary files /dev/null and b/media/IMG_7291.PNG differ diff --git a/media/IMG_7292.PNG b/media/IMG_7292.PNG new file mode 100644 index 0000000..a7db449 Binary files /dev/null and b/media/IMG_7292.PNG differ diff --git a/media/ScreenRecording_11-22-2025 15-55-39_1.webm b/media/ScreenRecording_11-22-2025 15-55-39_1.webm new file mode 100644 index 0000000..829ddd0 Binary files /dev/null and b/media/ScreenRecording_11-22-2025 15-55-39_1.webm differ diff --git a/media/logo.png b/media/logo.png deleted file mode 100644 index 3f83587..0000000 Binary files a/media/logo.png and /dev/null differ diff --git a/pubspec.yaml b/pubspec.yaml index 9a70a89..7d11c14 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,18 +1,41 @@ name: flutter_alice description: Alice is an HTTP Inspector tool which helps debugging http requests. It catches and stores http requests and responses, which can be viewed via simple UI. -version: 2.0.1 +version: 2.1.0-beta.1 homepage: https://github.com/hautvfami/flutter-alice +screenshots: + - description: The Alice. + path: example/assets/green.png +funding: + - https://github.com/sponsors/hautvfami/ +topics: + - alice + - dio + - inspector + - network + - http environment: - sdk: '>=2.15.0 <3.0.0' - flutter: '>=2.0.0' + sdk: '>=3.0.0 <4.0.0' + flutter: '>=3.0.0' dependencies: flutter: sdk: flutter - http: '>=1.1.0' + http: '>=1.1.0 <2.0.0' dio: '>=5.1.1' # chopper: '>=7.0.6' - overlay_support: '>=2.1.0' - share_plus: '>=9.0.0' - rxdart: '>=0.27.1' \ No newline at end of file + # overlay_support: '>=2.1.0' + share_plus: '>=11.0.0' + rxdart: '>=0.27.1' + cupertino_icons: '>=1.0.0' + +# # Font asset for the package +# flutter: +# assets: +# - assets/ +# fonts: +# - family: Mono +# package: flutter_alice +# fonts: +# - asset: assets/JetBrainsMono.ttf +# weight: 400 \ No newline at end of file