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 🎁
[](https://pub.dev/packages/flutter_alice)
[](https://github.com/hautvfami/flutter-alice)
[](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
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ Your browser does not support the video tag.
+
+
+
+
+
-
-**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