Showing
70 changed files
with
2738 additions
and
39 deletions
example/android/.gitignore
0 → 100644
example/android/app/build.gradle
0 → 100644
1 | +def localProperties = new Properties() | ||
2 | +def localPropertiesFile = rootProject.file('local.properties') | ||
3 | +if (localPropertiesFile.exists()) { | ||
4 | + localPropertiesFile.withReader('UTF-8') { reader -> | ||
5 | + localProperties.load(reader) | ||
6 | + } | ||
7 | +} | ||
8 | + | ||
9 | +def flutterRoot = localProperties.getProperty('flutter.sdk') | ||
10 | +if (flutterRoot == null) { | ||
11 | + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") | ||
12 | +} | ||
13 | + | ||
14 | +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') | ||
15 | +if (flutterVersionCode == null) { | ||
16 | + flutterVersionCode = '1' | ||
17 | +} | ||
18 | + | ||
19 | +def flutterVersionName = localProperties.getProperty('flutter.versionName') | ||
20 | +if (flutterVersionName == null) { | ||
21 | + flutterVersionName = '1.0' | ||
22 | +} | ||
23 | + | ||
24 | +apply plugin: 'com.android.application' | ||
25 | +apply plugin: 'kotlin-android' | ||
26 | +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" | ||
27 | + | ||
28 | +android { | ||
29 | + compileSdkVersion 29 | ||
30 | + | ||
31 | + sourceSets { | ||
32 | + main.java.srcDirs += 'src/main/kotlin' | ||
33 | + } | ||
34 | + | ||
35 | + lintOptions { | ||
36 | + disable 'InvalidPackage' | ||
37 | + } | ||
38 | + | ||
39 | + defaultConfig { | ||
40 | + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). | ||
41 | + applicationId "com.example.example" | ||
42 | + minSdkVersion 16 | ||
43 | + targetSdkVersion 29 | ||
44 | + versionCode flutterVersionCode.toInteger() | ||
45 | + versionName flutterVersionName | ||
46 | + } | ||
47 | + | ||
48 | + buildTypes { | ||
49 | + release { | ||
50 | + // TODO: Add your own signing config for the release build. | ||
51 | + // Signing with the debug keys for now, so `flutter run --release` works. | ||
52 | + signingConfig signingConfigs.debug | ||
53 | + } | ||
54 | + } | ||
55 | +} | ||
56 | + | ||
57 | +flutter { | ||
58 | + source '../..' | ||
59 | +} | ||
60 | + | ||
61 | +dependencies { | ||
62 | + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" | ||
63 | +} |
1 | +<manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||
2 | + package="com.example.example"> | ||
3 | + <!-- Flutter needs it to communicate with the running application | ||
4 | + to allow setting breakpoints, to provide hot reload, etc. | ||
5 | + --> | ||
6 | + <uses-permission android:name="android.permission.INTERNET"/> | ||
7 | +</manifest> |
1 | +<manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||
2 | + package="com.example.example"> | ||
3 | + <application | ||
4 | + android:label="example" | ||
5 | + android:icon="@mipmap/ic_launcher"> | ||
6 | + <activity | ||
7 | + android:name=".MainActivity" | ||
8 | + android:launchMode="singleTop" | ||
9 | + android:theme="@style/LaunchTheme" | ||
10 | + android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" | ||
11 | + android:hardwareAccelerated="true" | ||
12 | + android:windowSoftInputMode="adjustResize"> | ||
13 | + <!-- Specifies an Android theme to apply to this Activity as soon as | ||
14 | + the Android process has started. This theme is visible to the user | ||
15 | + while the Flutter UI initializes. After that, this theme continues | ||
16 | + to determine the Window background behind the Flutter UI. --> | ||
17 | + <meta-data | ||
18 | + android:name="io.flutter.embedding.android.NormalTheme" | ||
19 | + android:resource="@style/NormalTheme" | ||
20 | + /> | ||
21 | + <!-- Displays an Android View that continues showing the launch screen | ||
22 | + Drawable until Flutter paints its first frame, then this splash | ||
23 | + screen fades out. A splash screen is useful to avoid any visual | ||
24 | + gap between the end of Android's launch screen and the painting of | ||
25 | + Flutter's first frame. --> | ||
26 | + <meta-data | ||
27 | + android:name="io.flutter.embedding.android.SplashScreenDrawable" | ||
28 | + android:resource="@drawable/launch_background" | ||
29 | + /> | ||
30 | + <intent-filter> | ||
31 | + <action android:name="android.intent.action.MAIN"/> | ||
32 | + <category android:name="android.intent.category.LAUNCHER"/> | ||
33 | + </intent-filter> | ||
34 | + </activity> | ||
35 | + <!-- Don't delete the meta-data below. | ||
36 | + This is used by the Flutter tool to generate GeneratedPluginRegistrant.java --> | ||
37 | + <meta-data | ||
38 | + android:name="flutterEmbedding" | ||
39 | + android:value="2" /> | ||
40 | + </application> | ||
41 | +</manifest> |
1 | +<?xml version="1.0" encoding="utf-8"?> | ||
2 | +<!-- Modify this file to customize your launch splash screen --> | ||
3 | +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> | ||
4 | + <item android:drawable="?android:colorBackground" /> | ||
5 | + | ||
6 | + <!-- You can insert your own image assets here --> | ||
7 | + <!-- <item> | ||
8 | + <bitmap | ||
9 | + android:gravity="center" | ||
10 | + android:src="@mipmap/launch_image" /> | ||
11 | + </item> --> | ||
12 | +</layer-list> |
1 | +<?xml version="1.0" encoding="utf-8"?> | ||
2 | +<!-- Modify this file to customize your launch splash screen --> | ||
3 | +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> | ||
4 | + <item android:drawable="@android:color/white" /> | ||
5 | + | ||
6 | + <!-- You can insert your own image assets here --> | ||
7 | + <!-- <item> | ||
8 | + <bitmap | ||
9 | + android:gravity="center" | ||
10 | + android:src="@mipmap/launch_image" /> | ||
11 | + </item> --> | ||
12 | +</layer-list> |

544 Bytes

442 Bytes

721 Bytes

1.01 KB

1.41 KB
1 | +<?xml version="1.0" encoding="utf-8"?> | ||
2 | +<resources> | ||
3 | + <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on --> | ||
4 | + <style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar"> | ||
5 | + <!-- Show a splash screen on the activity. Automatically removed when | ||
6 | + Flutter draws its first frame --> | ||
7 | + <item name="android:windowBackground">@drawable/launch_background</item> | ||
8 | + </style> | ||
9 | + <!-- Theme applied to the Android Window as soon as the process has started. | ||
10 | + This theme determines the color of the Android Window while your | ||
11 | + Flutter UI initializes, as well as behind your Flutter UI while its | ||
12 | + running. | ||
13 | + | ||
14 | + This Theme is only used starting with V2 of Flutter's Android embedding. --> | ||
15 | + <style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar"> | ||
16 | + <item name="android:windowBackground">?android:colorBackground</item> | ||
17 | + </style> | ||
18 | +</resources> |
1 | +<?xml version="1.0" encoding="utf-8"?> | ||
2 | +<resources> | ||
3 | + <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off --> | ||
4 | + <style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar"> | ||
5 | + <!-- Show a splash screen on the activity. Automatically removed when | ||
6 | + Flutter draws its first frame --> | ||
7 | + <item name="android:windowBackground">@drawable/launch_background</item> | ||
8 | + </style> | ||
9 | + <!-- Theme applied to the Android Window as soon as the process has started. | ||
10 | + This theme determines the color of the Android Window while your | ||
11 | + Flutter UI initializes, as well as behind your Flutter UI while its | ||
12 | + running. | ||
13 | + | ||
14 | + This Theme is only used starting with V2 of Flutter's Android embedding. --> | ||
15 | + <style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar"> | ||
16 | + <item name="android:windowBackground">?android:colorBackground</item> | ||
17 | + </style> | ||
18 | +</resources> |
1 | +<manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||
2 | + package="com.example.example"> | ||
3 | + <!-- Flutter needs it to communicate with the running application | ||
4 | + to allow setting breakpoints, to provide hot reload, etc. | ||
5 | + --> | ||
6 | + <uses-permission android:name="android.permission.INTERNET"/> | ||
7 | +</manifest> |
example/android/build.gradle
0 → 100644
1 | +buildscript { | ||
2 | + ext.kotlin_version = '1.3.50' | ||
3 | + repositories { | ||
4 | + google() | ||
5 | + jcenter() | ||
6 | + } | ||
7 | + | ||
8 | + dependencies { | ||
9 | + classpath 'com.android.tools.build:gradle:3.5.0' | ||
10 | + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" | ||
11 | + } | ||
12 | +} | ||
13 | + | ||
14 | +allprojects { | ||
15 | + repositories { | ||
16 | + google() | ||
17 | + jcenter() | ||
18 | + } | ||
19 | +} | ||
20 | + | ||
21 | +rootProject.buildDir = '../build' | ||
22 | +subprojects { | ||
23 | + project.buildDir = "${rootProject.buildDir}/${project.name}" | ||
24 | +} | ||
25 | +subprojects { | ||
26 | + project.evaluationDependsOn(':app') | ||
27 | +} | ||
28 | + | ||
29 | +task clean(type: Delete) { | ||
30 | + delete rootProject.buildDir | ||
31 | +} |
example/android/gradle.properties
0 → 100644
example/android/settings.gradle
0 → 100644
1 | +include ':app' | ||
2 | + | ||
3 | +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") | ||
4 | +def properties = new Properties() | ||
5 | + | ||
6 | +assert localPropertiesFile.exists() | ||
7 | +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } | ||
8 | + | ||
9 | +def flutterSdkPath = properties.getProperty("flutter.sdk") | ||
10 | +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" | ||
11 | +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" |
1 | -import 'package:dio/dio.dart'; | ||
2 | import 'package:get/get.dart'; | 1 | import 'package:get/get.dart'; |
2 | +import '../data/home_api_provider.dart'; | ||
3 | 3 | ||
4 | import '../data/home_repository.dart'; | 4 | import '../data/home_repository.dart'; |
5 | import '../domain/adapters/repository_adapter.dart'; | 5 | import '../domain/adapters/repository_adapter.dart'; |
@@ -8,8 +8,8 @@ import '../presentation/controllers/home_controller.dart'; | @@ -8,8 +8,8 @@ import '../presentation/controllers/home_controller.dart'; | ||
8 | class HomeBinding extends Bindings { | 8 | class HomeBinding extends Bindings { |
9 | @override | 9 | @override |
10 | void dependencies() { | 10 | void dependencies() { |
11 | - Get.lazyPut(() => Dio()); | ||
12 | - Get.lazyPut<IHomeRepository>(() => HomeRepository(dio: Get.find())); | 11 | + Get.lazyPut<IHomeProvider>(() => HomeProvider()); |
12 | + Get.lazyPut<IHomeRepository>(() => HomeRepository(provider: Get.find())); | ||
13 | Get.lazyPut(() => HomeController(homeRepository: Get.find())); | 13 | Get.lazyPut(() => HomeController(homeRepository: Get.find())); |
14 | } | 14 | } |
15 | } | 15 | } |
1 | +import 'package:get/get.dart'; | ||
2 | +import '../domain/entity/cases_model.dart'; | ||
3 | + | ||
4 | +// ignore: one_member_abstracts | ||
5 | +abstract class IHomeProvider { | ||
6 | + Future<Response<CasesModel>> getCases(String path); | ||
7 | +} | ||
8 | + | ||
9 | +class HomeProvider extends GetConnect implements IHomeProvider { | ||
10 | + @override | ||
11 | + void onInit() { | ||
12 | + httpClient.defaultDecoder = CasesModel.fromJson; | ||
13 | + httpClient.baseUrl = 'https://api.covid19api.com'; | ||
14 | + } | ||
15 | + | ||
16 | + @override | ||
17 | + Future<Response<CasesModel>> getCases(String path) => get(path); | ||
18 | +} |
1 | -import 'package:dio/dio.dart'; | ||
2 | - | ||
3 | import '../domain/adapters/repository_adapter.dart'; | 1 | import '../domain/adapters/repository_adapter.dart'; |
4 | import '../domain/entity/cases_model.dart'; | 2 | import '../domain/entity/cases_model.dart'; |
3 | +import 'home_api_provider.dart'; | ||
5 | 4 | ||
6 | class HomeRepository implements IHomeRepository { | 5 | class HomeRepository implements IHomeRepository { |
7 | - HomeRepository({this.dio}); | ||
8 | - | ||
9 | - final Dio dio; | 6 | + HomeRepository({this.provider}); |
7 | + final IHomeProvider provider; | ||
10 | 8 | ||
11 | @override | 9 | @override |
12 | Future<CasesModel> getCases() async { | 10 | Future<CasesModel> getCases() async { |
13 | - try { | ||
14 | - final response = await dio.get("https://api.covid19api.com/summary"); | ||
15 | - return CasesModel.fromJson(response.data as Map<String, dynamic>); | ||
16 | - } on Exception catch (e) { | ||
17 | - print(e.toString()); | ||
18 | - return Future.error(e.toString()); | 11 | + final cases = await provider.getCases("/summary"); |
12 | + if (cases.status.hasError) { | ||
13 | + return Future.error(cases.statusText); | ||
14 | + } else { | ||
15 | + return cases.body; | ||
19 | } | 16 | } |
20 | } | 17 | } |
21 | } | 18 | } |
@@ -15,12 +15,12 @@ class CasesModel { | @@ -15,12 +15,12 @@ class CasesModel { | ||
15 | this.date, | 15 | this.date, |
16 | }); | 16 | }); |
17 | 17 | ||
18 | - factory CasesModel.fromRawJson(String str) => | 18 | + static CasesModel fromRawJson(String str) => |
19 | CasesModel.fromJson(json.decode(str) as Map<String, dynamic>); | 19 | CasesModel.fromJson(json.decode(str) as Map<String, dynamic>); |
20 | 20 | ||
21 | String toRawJson() => json.encode(toJson()); | 21 | String toRawJson() => json.encode(toJson()); |
22 | 22 | ||
23 | - factory CasesModel.fromJson(Map<String, dynamic> json) => CasesModel( | 23 | + static CasesModel fromJson(dynamic json) => CasesModel( |
24 | global: json["Global"] == null | 24 | global: json["Global"] == null |
25 | ? null | 25 | ? null |
26 | : Global.fromJson(json["Global"] as Map<String, dynamic>), | 26 | : Global.fromJson(json["Global"] as Map<String, dynamic>), |
example/linux/.gitignore
0 → 100644
1 | +flutter/ephemeral |
example/linux/CMakeLists.txt
0 → 100644
1 | +cmake_minimum_required(VERSION 3.10) | ||
2 | +project(runner LANGUAGES CXX) | ||
3 | + | ||
4 | +set(BINARY_NAME "example") | ||
5 | +set(APPLICATION_ID "com.example.example") | ||
6 | + | ||
7 | +cmake_policy(SET CMP0063 NEW) | ||
8 | + | ||
9 | +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") | ||
10 | + | ||
11 | +# Configure build options. | ||
12 | +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) | ||
13 | + set(CMAKE_BUILD_TYPE "Debug" CACHE | ||
14 | + STRING "Flutter build mode" FORCE) | ||
15 | + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS | ||
16 | + "Debug" "Profile" "Release") | ||
17 | +endif() | ||
18 | + | ||
19 | +# Compilation settings that should be applied to most targets. | ||
20 | +function(APPLY_STANDARD_SETTINGS TARGET) | ||
21 | + target_compile_features(${TARGET} PUBLIC cxx_std_14) | ||
22 | + target_compile_options(${TARGET} PRIVATE -Wall -Werror) | ||
23 | + target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>") | ||
24 | + target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>") | ||
25 | +endfunction() | ||
26 | + | ||
27 | +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") | ||
28 | + | ||
29 | +# Flutter library and tool build rules. | ||
30 | +add_subdirectory(${FLUTTER_MANAGED_DIR}) | ||
31 | + | ||
32 | +# System-level dependencies. | ||
33 | +find_package(PkgConfig REQUIRED) | ||
34 | +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) | ||
35 | + | ||
36 | +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") | ||
37 | + | ||
38 | +# Application build | ||
39 | +add_executable(${BINARY_NAME} | ||
40 | + "main.cc" | ||
41 | + "my_application.cc" | ||
42 | + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" | ||
43 | +) | ||
44 | +apply_standard_settings(${BINARY_NAME}) | ||
45 | +target_link_libraries(${BINARY_NAME} PRIVATE flutter) | ||
46 | +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) | ||
47 | +add_dependencies(${BINARY_NAME} flutter_assemble) | ||
48 | +# Only the install-generated bundle's copy of the executable will launch | ||
49 | +# correctly, since the resources must in the right relative locations. To avoid | ||
50 | +# people trying to run the unbundled copy, put it in a subdirectory instead of | ||
51 | +# the default top-level location. | ||
52 | +set_target_properties(${BINARY_NAME} | ||
53 | + PROPERTIES | ||
54 | + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" | ||
55 | +) | ||
56 | + | ||
57 | +# Generated plugin build rules, which manage building the plugins and adding | ||
58 | +# them to the application. | ||
59 | +include(flutter/generated_plugins.cmake) | ||
60 | + | ||
61 | + | ||
62 | +# === Installation === | ||
63 | +# By default, "installing" just makes a relocatable bundle in the build | ||
64 | +# directory. | ||
65 | +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") | ||
66 | +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) | ||
67 | + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) | ||
68 | +endif() | ||
69 | + | ||
70 | +# Start with a clean build bundle directory every time. | ||
71 | +install(CODE " | ||
72 | + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") | ||
73 | + " COMPONENT Runtime) | ||
74 | + | ||
75 | +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") | ||
76 | +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") | ||
77 | + | ||
78 | +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" | ||
79 | + COMPONENT Runtime) | ||
80 | + | ||
81 | +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" | ||
82 | + COMPONENT Runtime) | ||
83 | + | ||
84 | +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" | ||
85 | + COMPONENT Runtime) | ||
86 | + | ||
87 | +if(PLUGIN_BUNDLED_LIBRARIES) | ||
88 | + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" | ||
89 | + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" | ||
90 | + COMPONENT Runtime) | ||
91 | +endif() | ||
92 | + | ||
93 | +# Fully re-copy the assets directory on each build to avoid having stale files | ||
94 | +# from a previous install. | ||
95 | +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") | ||
96 | +install(CODE " | ||
97 | + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") | ||
98 | + " COMPONENT Runtime) | ||
99 | +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" | ||
100 | + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) | ||
101 | + | ||
102 | +# Install the AOT library on non-Debug builds only. | ||
103 | +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") | ||
104 | + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" | ||
105 | + COMPONENT Runtime) | ||
106 | +endif() |
example/linux/flutter/CMakeLists.txt
0 → 100644
1 | +cmake_minimum_required(VERSION 3.10) | ||
2 | + | ||
3 | +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") | ||
4 | + | ||
5 | +# Configuration provided via flutter tool. | ||
6 | +include(${EPHEMERAL_DIR}/generated_config.cmake) | ||
7 | + | ||
8 | +# TODO: Move the rest of this into files in ephemeral. See | ||
9 | +# https://github.com/flutter/flutter/issues/57146. | ||
10 | + | ||
11 | +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), | ||
12 | +# which isn't available in 3.10. | ||
13 | +function(list_prepend LIST_NAME PREFIX) | ||
14 | + set(NEW_LIST "") | ||
15 | + foreach(element ${${LIST_NAME}}) | ||
16 | + list(APPEND NEW_LIST "${PREFIX}${element}") | ||
17 | + endforeach(element) | ||
18 | + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) | ||
19 | +endfunction() | ||
20 | + | ||
21 | +# === Flutter Library === | ||
22 | +# System-level dependencies. | ||
23 | +find_package(PkgConfig REQUIRED) | ||
24 | +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) | ||
25 | +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) | ||
26 | +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) | ||
27 | +pkg_check_modules(BLKID REQUIRED IMPORTED_TARGET blkid) | ||
28 | + | ||
29 | +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") | ||
30 | + | ||
31 | +# Published to parent scope for install step. | ||
32 | +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) | ||
33 | +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) | ||
34 | +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) | ||
35 | +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) | ||
36 | + | ||
37 | +list(APPEND FLUTTER_LIBRARY_HEADERS | ||
38 | + "fl_basic_message_channel.h" | ||
39 | + "fl_binary_codec.h" | ||
40 | + "fl_binary_messenger.h" | ||
41 | + "fl_dart_project.h" | ||
42 | + "fl_engine.h" | ||
43 | + "fl_json_message_codec.h" | ||
44 | + "fl_json_method_codec.h" | ||
45 | + "fl_message_codec.h" | ||
46 | + "fl_method_call.h" | ||
47 | + "fl_method_channel.h" | ||
48 | + "fl_method_codec.h" | ||
49 | + "fl_method_response.h" | ||
50 | + "fl_plugin_registrar.h" | ||
51 | + "fl_plugin_registry.h" | ||
52 | + "fl_standard_message_codec.h" | ||
53 | + "fl_standard_method_codec.h" | ||
54 | + "fl_string_codec.h" | ||
55 | + "fl_value.h" | ||
56 | + "fl_view.h" | ||
57 | + "flutter_linux.h" | ||
58 | +) | ||
59 | +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") | ||
60 | +add_library(flutter INTERFACE) | ||
61 | +target_include_directories(flutter INTERFACE | ||
62 | + "${EPHEMERAL_DIR}" | ||
63 | +) | ||
64 | +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") | ||
65 | +target_link_libraries(flutter INTERFACE | ||
66 | + PkgConfig::GTK | ||
67 | + PkgConfig::GLIB | ||
68 | + PkgConfig::GIO | ||
69 | + PkgConfig::BLKID | ||
70 | +) | ||
71 | +add_dependencies(flutter flutter_assemble) | ||
72 | + | ||
73 | +# === Flutter tool backend === | ||
74 | +# _phony_ is a non-existent file to force this command to run every time, | ||
75 | +# since currently there's no way to get a full input/output list from the | ||
76 | +# flutter tool. | ||
77 | +add_custom_command( | ||
78 | + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} | ||
79 | + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ | ||
80 | + COMMAND ${CMAKE_COMMAND} -E env | ||
81 | + ${FLUTTER_TOOL_ENVIRONMENT} | ||
82 | + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" | ||
83 | + linux-x64 ${CMAKE_BUILD_TYPE} | ||
84 | +) | ||
85 | +add_custom_target(flutter_assemble DEPENDS | ||
86 | + "${FLUTTER_LIBRARY}" | ||
87 | + ${FLUTTER_LIBRARY_HEADERS} | ||
88 | +) |
1 | +// | ||
2 | +// Generated file. Do not edit. | ||
3 | +// | ||
4 | + | ||
5 | +#ifndef GENERATED_PLUGIN_REGISTRANT_ | ||
6 | +#define GENERATED_PLUGIN_REGISTRANT_ | ||
7 | + | ||
8 | +#include <flutter_linux/flutter_linux.h> | ||
9 | + | ||
10 | +// Registers Flutter plugins. | ||
11 | +void fl_register_plugins(FlPluginRegistry* registry); | ||
12 | + | ||
13 | +#endif // GENERATED_PLUGIN_REGISTRANT_ |
1 | +# | ||
2 | +# Generated file, do not edit. | ||
3 | +# | ||
4 | + | ||
5 | +list(APPEND FLUTTER_PLUGIN_LIST | ||
6 | +) | ||
7 | + | ||
8 | +set(PLUGIN_BUNDLED_LIBRARIES) | ||
9 | + | ||
10 | +foreach(plugin ${FLUTTER_PLUGIN_LIST}) | ||
11 | + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) | ||
12 | + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) | ||
13 | + list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>) | ||
14 | + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) | ||
15 | +endforeach(plugin) |
example/linux/main.cc
0 → 100644
example/linux/my_application.cc
0 → 100644
1 | +#include "my_application.h" | ||
2 | + | ||
3 | +#include <flutter_linux/flutter_linux.h> | ||
4 | +#ifdef GDK_WINDOWING_X11 | ||
5 | +#include <gdk/gdkx.h> | ||
6 | +#endif | ||
7 | + | ||
8 | +#include "flutter/generated_plugin_registrant.h" | ||
9 | + | ||
10 | +struct _MyApplication { | ||
11 | + GtkApplication parent_instance; | ||
12 | + char** dart_entrypoint_arguments; | ||
13 | +}; | ||
14 | + | ||
15 | +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) | ||
16 | + | ||
17 | +// Implements GApplication::activate. | ||
18 | +static void my_application_activate(GApplication* application) { | ||
19 | + MyApplication* self = MY_APPLICATION(application); | ||
20 | + GtkWindow* window = | ||
21 | + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); | ||
22 | + | ||
23 | + // Use a header bar when running in GNOME as this is the common style used | ||
24 | + // by applications and is the setup most users will be using (e.g. Ubuntu | ||
25 | + // desktop). | ||
26 | + // If running on X and not using GNOME then just use a traditional title bar | ||
27 | + // in case the window manager does more exotic layout, e.g. tiling. | ||
28 | + // If running on Wayland assume the header bar will work (may need changing | ||
29 | + // if future cases occur). | ||
30 | + gboolean use_header_bar = TRUE; | ||
31 | +#ifdef GDK_WINDOWING_X11 | ||
32 | + GdkScreen *screen = gtk_window_get_screen(window); | ||
33 | + if (GDK_IS_X11_SCREEN(screen)) { | ||
34 | + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); | ||
35 | + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { | ||
36 | + use_header_bar = FALSE; | ||
37 | + } | ||
38 | + } | ||
39 | +#endif | ||
40 | + if (use_header_bar) { | ||
41 | + GtkHeaderBar *header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); | ||
42 | + gtk_widget_show(GTK_WIDGET(header_bar)); | ||
43 | + gtk_header_bar_set_title(header_bar, "example"); | ||
44 | + gtk_header_bar_set_show_close_button(header_bar, TRUE); | ||
45 | + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); | ||
46 | + } | ||
47 | + else { | ||
48 | + gtk_window_set_title(window, "example"); | ||
49 | + } | ||
50 | + | ||
51 | + gtk_window_set_default_size(window, 1280, 720); | ||
52 | + gtk_widget_show(GTK_WIDGET(window)); | ||
53 | + | ||
54 | + g_autoptr(FlDartProject) project = fl_dart_project_new(); | ||
55 | + fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); | ||
56 | + | ||
57 | + FlView* view = fl_view_new(project); | ||
58 | + gtk_widget_show(GTK_WIDGET(view)); | ||
59 | + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); | ||
60 | + | ||
61 | + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); | ||
62 | + | ||
63 | + gtk_widget_grab_focus(GTK_WIDGET(view)); | ||
64 | +} | ||
65 | + | ||
66 | +// Implements GApplication::local_command_line. | ||
67 | +static gboolean my_application_local_command_line(GApplication* application, gchar ***arguments, int *exit_status) { | ||
68 | + MyApplication* self = MY_APPLICATION(application); | ||
69 | + // Strip out the first argument as it is the binary name. | ||
70 | + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); | ||
71 | + | ||
72 | + g_autoptr(GError) error = nullptr; | ||
73 | + if (!g_application_register(application, nullptr, &error)) { | ||
74 | + g_warning("Failed to register: %s", error->message); | ||
75 | + *exit_status = 1; | ||
76 | + return TRUE; | ||
77 | + } | ||
78 | + | ||
79 | + g_application_activate(application); | ||
80 | + *exit_status = 0; | ||
81 | + | ||
82 | + return TRUE; | ||
83 | +} | ||
84 | + | ||
85 | +// Implements GObject::dispose. | ||
86 | +static void my_application_dispose(GObject *object) { | ||
87 | + MyApplication* self = MY_APPLICATION(object); | ||
88 | + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); | ||
89 | + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); | ||
90 | +} | ||
91 | + | ||
92 | +static void my_application_class_init(MyApplicationClass* klass) { | ||
93 | + G_APPLICATION_CLASS(klass)->activate = my_application_activate; | ||
94 | + G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; | ||
95 | + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; | ||
96 | +} | ||
97 | + | ||
98 | +static void my_application_init(MyApplication* self) {} | ||
99 | + | ||
100 | +MyApplication* my_application_new() { | ||
101 | + return MY_APPLICATION(g_object_new(my_application_get_type(), | ||
102 | + "application-id", APPLICATION_ID, | ||
103 | + nullptr)); | ||
104 | +} |
example/linux/my_application.h
0 → 100644
1 | +#ifndef FLUTTER_MY_APPLICATION_H_ | ||
2 | +#define FLUTTER_MY_APPLICATION_H_ | ||
3 | + | ||
4 | +#include <gtk/gtk.h> | ||
5 | + | ||
6 | +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, | ||
7 | + GtkApplication) | ||
8 | + | ||
9 | +/** | ||
10 | + * my_application_new: | ||
11 | + * | ||
12 | + * Creates a new Flutter-based application. | ||
13 | + * | ||
14 | + * Returns: a new #MyApplication. | ||
15 | + */ | ||
16 | +MyApplication* my_application_new(); | ||
17 | + | ||
18 | +#endif // FLUTTER_MY_APPLICATION_H_ |
@@ -28,7 +28,6 @@ dependencies: | @@ -28,7 +28,6 @@ dependencies: | ||
28 | # Use with the CupertinoIcons class for iOS style icons. | 28 | # Use with the CupertinoIcons class for iOS style icons. |
29 | get: | 29 | get: |
30 | path: ../ | 30 | path: ../ |
31 | - dio: ^3.0.9 | ||
32 | get_test: ^3.13.3 | 31 | get_test: ^3.13.3 |
33 | 32 | ||
34 | dependency_overrides: | 33 | dependency_overrides: |
example/web/favicon.png
0 → 100644

917 Bytes
example/web/icons/Icon-192.png
0 → 100644

5.17 KB
example/web/icons/Icon-512.png
0 → 100644

8.06 KB
example/web/index.html
0 → 100644
1 | +<!DOCTYPE html> | ||
2 | +<html> | ||
3 | +<head> | ||
4 | + <!-- | ||
5 | + If you are serving your web app in a path other than the root, change the | ||
6 | + href value below to reflect the base path you are serving from. | ||
7 | + | ||
8 | + The path provided below has to start and end with a slash "/" in order for | ||
9 | + it to work correctly. | ||
10 | + | ||
11 | + Fore more details: | ||
12 | + * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base | ||
13 | + --> | ||
14 | + <base href="/"> | ||
15 | + | ||
16 | + <meta charset="UTF-8"> | ||
17 | + <meta content="IE=Edge" http-equiv="X-UA-Compatible"> | ||
18 | + <meta name="description" content="A new Flutter project."> | ||
19 | + | ||
20 | + <!-- iOS meta tags & icons --> | ||
21 | + <meta name="apple-mobile-web-app-capable" content="yes"> | ||
22 | + <meta name="apple-mobile-web-app-status-bar-style" content="black"> | ||
23 | + <meta name="apple-mobile-web-app-title" content="example"> | ||
24 | + <link rel="apple-touch-icon" href="icons/Icon-192.png"> | ||
25 | + | ||
26 | + <!-- Favicon --> | ||
27 | + <link rel="icon" type="image/png" href="favicon.png"/> | ||
28 | + | ||
29 | + <title>example</title> | ||
30 | + <link rel="manifest" href="manifest.json"> | ||
31 | +</head> | ||
32 | +<body> | ||
33 | + <!-- This script installs service_worker.js to provide PWA functionality to | ||
34 | + application. For more information, see: | ||
35 | + https://developers.google.com/web/fundamentals/primers/service-workers --> | ||
36 | + <script> | ||
37 | + if ('serviceWorker' in navigator) { | ||
38 | + window.addEventListener('flutter-first-frame', function () { | ||
39 | + navigator.serviceWorker.register('flutter_service_worker.js'); | ||
40 | + }); | ||
41 | + } | ||
42 | + </script> | ||
43 | + <script src="main.dart.js" type="application/javascript"></script> | ||
44 | +</body> | ||
45 | +</html> |
example/web/manifest.json
0 → 100644
1 | +{ | ||
2 | + "name": "example", | ||
3 | + "short_name": "example", | ||
4 | + "start_url": ".", | ||
5 | + "display": "standalone", | ||
6 | + "background_color": "#0175C2", | ||
7 | + "theme_color": "#0175C2", | ||
8 | + "description": "A new Flutter project.", | ||
9 | + "orientation": "portrait-primary", | ||
10 | + "prefer_related_applications": false, | ||
11 | + "icons": [ | ||
12 | + { | ||
13 | + "src": "icons/Icon-192.png", | ||
14 | + "sizes": "192x192", | ||
15 | + "type": "image/png" | ||
16 | + }, | ||
17 | + { | ||
18 | + "src": "icons/Icon-512.png", | ||
19 | + "sizes": "512x512", | ||
20 | + "type": "image/png" | ||
21 | + } | ||
22 | + ] | ||
23 | +} |
@@ -3,6 +3,7 @@ | @@ -3,6 +3,7 @@ | ||
3 | /// injection, and route management in a quick and practical way. | 3 | /// injection, and route management in a quick and practical way. |
4 | library get; | 4 | library get; |
5 | 5 | ||
6 | +export 'get_connect/connect.dart'; | ||
6 | export 'get_core/get_core.dart'; | 7 | export 'get_core/get_core.dart'; |
7 | export 'get_instance/get_instance.dart'; | 8 | export 'get_instance/get_instance.dart'; |
8 | export 'get_navigation/get_navigation.dart'; | 9 | export 'get_navigation/get_navigation.dart'; |
lib/get_connect/connect.dart
0 → 100644
1 | +import '../get_instance/src/lifecycle.dart'; | ||
2 | +import 'http/src/certificates/certificates.dart'; | ||
3 | +import 'http/src/http.dart'; | ||
4 | +import 'http/src/response/response.dart'; | ||
5 | +import 'sockets/sockets.dart'; | ||
6 | + | ||
7 | +export 'http/src/certificates/certificates.dart'; | ||
8 | +export 'http/src/http.dart'; | ||
9 | +export 'http/src/multipart/form_data.dart'; | ||
10 | +export 'http/src/multipart/multipart_file.dart'; | ||
11 | +export 'http/src/response/response.dart'; | ||
12 | +export 'sockets/sockets.dart'; | ||
13 | + | ||
14 | +abstract class GetConnectInterface with GetLifeCycleBase { | ||
15 | + List<GetSocket> sockets; | ||
16 | + GetHttpClient get httpClient; | ||
17 | + | ||
18 | + Future<Response<T>> get<T>( | ||
19 | + String url, { | ||
20 | + Map<String, String> headers, | ||
21 | + String contentType, | ||
22 | + Map<String, dynamic> query, | ||
23 | + Decoder<T> decoder, | ||
24 | + }); | ||
25 | + Future<Response<T>> post<T>( | ||
26 | + String url, | ||
27 | + dynamic body, { | ||
28 | + String contentType, | ||
29 | + Map<String, String> headers, | ||
30 | + Map<String, dynamic> query, | ||
31 | + }); | ||
32 | + | ||
33 | + Future<Response<T>> put<T>( | ||
34 | + String url, | ||
35 | + Map<String, dynamic> body, { | ||
36 | + String contentType, | ||
37 | + Map<String, String> headers, | ||
38 | + Map<String, dynamic> query, | ||
39 | + }); | ||
40 | + | ||
41 | + Future<Response<T>> delete<T>( | ||
42 | + String url, { | ||
43 | + Map<String, String> headers, | ||
44 | + String contentType, | ||
45 | + Map<String, dynamic> query, | ||
46 | + }); | ||
47 | + | ||
48 | + GetSocket socket(String url, {Duration ping = const Duration(seconds: 5)}); | ||
49 | +} | ||
50 | + | ||
51 | +class GetConnect extends GetConnectInterface { | ||
52 | + GetConnect({ | ||
53 | + this.userAgent = 'getx-client', | ||
54 | + this.timeout = const Duration(seconds: 5), | ||
55 | + this.followRedirects = true, | ||
56 | + this.maxRedirects = 5, | ||
57 | + this.maxAuthRetries = 1, | ||
58 | + this.allowAutoSignedCert = false, | ||
59 | + }) { | ||
60 | + $configureLifeCycle(); | ||
61 | + } | ||
62 | + | ||
63 | + bool allowAutoSignedCert; | ||
64 | + String userAgent; | ||
65 | + String baseUrl; | ||
66 | + String defaultContentType = 'application/json; charset=utf-8'; | ||
67 | + bool followRedirects; | ||
68 | + int maxRedirects; | ||
69 | + int maxAuthRetries; | ||
70 | + Decoder defaultDecoder; | ||
71 | + Duration timeout; | ||
72 | + List<TrustedCertificate> trustedCertificates; | ||
73 | + GetHttpClient _httpClient; | ||
74 | + List<GetSocket> _sockets; | ||
75 | + | ||
76 | + @override | ||
77 | + List<GetSocket> get sockets => _sockets ??= <GetSocket>[]; | ||
78 | + | ||
79 | + @override | ||
80 | + GetHttpClient get httpClient => _httpClient ??= GetHttpClient( | ||
81 | + userAgent: userAgent, | ||
82 | + timeout: timeout, | ||
83 | + followRedirects: followRedirects, | ||
84 | + maxRedirects: maxRedirects, | ||
85 | + maxAuthRetries: maxAuthRetries, | ||
86 | + allowAutoSignedCert: allowAutoSignedCert, | ||
87 | + baseUrl: baseUrl, | ||
88 | + trustedCertificates: trustedCertificates, | ||
89 | + ); | ||
90 | + | ||
91 | + @override | ||
92 | + Future<Response<T>> get<T>( | ||
93 | + String url, { | ||
94 | + Map<String, String> headers, | ||
95 | + String contentType, | ||
96 | + Map<String, dynamic> query, | ||
97 | + Decoder<T> decoder, | ||
98 | + }) { | ||
99 | + _checkIfDisposed(); | ||
100 | + return httpClient.get( | ||
101 | + url, | ||
102 | + headers: headers, | ||
103 | + contentType: contentType, | ||
104 | + query: query, | ||
105 | + decoder: decoder, | ||
106 | + ); | ||
107 | + } | ||
108 | + | ||
109 | + @override | ||
110 | + Future<Response<T>> post<T>( | ||
111 | + String url, | ||
112 | + dynamic body, { | ||
113 | + String contentType, | ||
114 | + Map<String, String> headers, | ||
115 | + Map<String, dynamic> query, | ||
116 | + Decoder<T> decoder, | ||
117 | + }) { | ||
118 | + _checkIfDisposed(); | ||
119 | + return httpClient.post<T>( | ||
120 | + url, | ||
121 | + body, | ||
122 | + headers: headers, | ||
123 | + contentType: contentType, | ||
124 | + query: query, | ||
125 | + decoder: decoder, | ||
126 | + ); | ||
127 | + } | ||
128 | + | ||
129 | + @override | ||
130 | + Future<Response<T>> put<T>( | ||
131 | + String url, | ||
132 | + Map<String, dynamic> body, { | ||
133 | + String contentType, | ||
134 | + Map<String, String> headers, | ||
135 | + Map<String, dynamic> query, | ||
136 | + Decoder<T> decoder, | ||
137 | + }) { | ||
138 | + _checkIfDisposed(); | ||
139 | + return httpClient.put( | ||
140 | + url, | ||
141 | + body, | ||
142 | + headers: headers, | ||
143 | + contentType: contentType, | ||
144 | + query: query, | ||
145 | + decoder: decoder, | ||
146 | + ); | ||
147 | + } | ||
148 | + | ||
149 | + @override | ||
150 | + Future<Response<T>> delete<T>( | ||
151 | + String url, { | ||
152 | + Map<String, String> headers, | ||
153 | + String contentType, | ||
154 | + Map<String, dynamic> query, | ||
155 | + Decoder<T> decoder, | ||
156 | + }) { | ||
157 | + _checkIfDisposed(); | ||
158 | + return httpClient.delete( | ||
159 | + url, | ||
160 | + headers: headers, | ||
161 | + contentType: contentType, | ||
162 | + query: query, | ||
163 | + decoder: decoder, | ||
164 | + ); | ||
165 | + } | ||
166 | + | ||
167 | + @override | ||
168 | + GetSocket socket(String url, {Duration ping = const Duration(seconds: 5)}) { | ||
169 | + _checkIfDisposed(isHttp: false); | ||
170 | + final _url = baseUrl == null ? url : baseUrl + url; | ||
171 | + final _socket = GetSocket(_url, ping: ping); | ||
172 | + sockets.add(_socket); | ||
173 | + return _socket; | ||
174 | + } | ||
175 | + | ||
176 | + bool _isDisposed = false; | ||
177 | + | ||
178 | + bool get isDisposed => _isDisposed; | ||
179 | + | ||
180 | + void _checkIfDisposed({bool isHttp = true}) { | ||
181 | + if (_isDisposed) { | ||
182 | + throw 'Can not emit events to disposed clients'; | ||
183 | + } | ||
184 | + } | ||
185 | + | ||
186 | + void dispose() { | ||
187 | + if (_sockets != null) { | ||
188 | + for (var socket in sockets) { | ||
189 | + socket.close(); | ||
190 | + } | ||
191 | + _sockets?.clear(); | ||
192 | + sockets = null; | ||
193 | + } | ||
194 | + if (_httpClient != null) { | ||
195 | + httpClient.close(); | ||
196 | + _httpClient = null; | ||
197 | + } | ||
198 | + _isDisposed = true; | ||
199 | + } | ||
200 | +} |
1 | +class GetHttpException implements Exception { | ||
2 | + final String message; | ||
3 | + | ||
4 | + final Uri uri; | ||
5 | + | ||
6 | + GetHttpException(this.message, [this.uri]); | ||
7 | + | ||
8 | + @override | ||
9 | + String toString() => message; | ||
10 | +} | ||
11 | + | ||
12 | +class UnauthorizedException implements Exception { | ||
13 | + @override | ||
14 | + String toString() { | ||
15 | + return 'Operation Unauthorized'; | ||
16 | + } | ||
17 | +} | ||
18 | + | ||
19 | +class UnexpectedFormat implements Exception { | ||
20 | + final String message; | ||
21 | + UnexpectedFormat(this.message); | ||
22 | + @override | ||
23 | + String toString() { | ||
24 | + return 'Unexpected format: $message'; | ||
25 | + } | ||
26 | +} |
lib/get_connect/http/src/http.dart
0 → 100644
1 | +import 'dart:async'; | ||
2 | +import 'dart:convert'; | ||
3 | +import 'package:meta/meta.dart'; | ||
4 | + | ||
5 | +import '../src/certificates/certificates.dart'; | ||
6 | +import '../src/exceptions/exceptions.dart'; | ||
7 | +import '../src/http_impl/http_request_stub.dart' | ||
8 | + if (dart.library.html) 'http_impl/http_request_html.dart' | ||
9 | + if (dart.library.io) 'http_impl/http_request_io.dart'; | ||
10 | +import '../src/http_impl/request_base.dart'; | ||
11 | +import '../src/interceptors/get_interceptors.dart'; | ||
12 | +import '../src/multipart/form_data.dart'; | ||
13 | +import '../src/request/request.dart'; | ||
14 | +import '../src/response/response.dart'; | ||
15 | +import '../src/status/http_status.dart'; | ||
16 | + | ||
17 | +typedef Decoder<T> = T Function(dynamic data); | ||
18 | + | ||
19 | +class GetHttpClient { | ||
20 | + String userAgent; | ||
21 | + String baseUrl; | ||
22 | + | ||
23 | + String defaultContentType = 'application/json; charset=utf-8'; | ||
24 | + | ||
25 | + bool followRedirects; | ||
26 | + int maxRedirects; | ||
27 | + int maxAuthRetries; | ||
28 | + | ||
29 | + Decoder defaultDecoder; | ||
30 | + | ||
31 | + Duration timeout; | ||
32 | + | ||
33 | + final HttpRequestBase _httpClient; | ||
34 | + | ||
35 | + final GetModifier _interceptor; | ||
36 | + | ||
37 | + GetHttpClient({ | ||
38 | + this.userAgent = 'getx-client', | ||
39 | + this.timeout = const Duration(seconds: 8), | ||
40 | + this.followRedirects = true, | ||
41 | + this.maxRedirects = 5, | ||
42 | + this.maxAuthRetries = 1, | ||
43 | + bool allowAutoSignedCert = false, | ||
44 | + this.baseUrl, | ||
45 | + List<TrustedCertificate> trustedCertificates, | ||
46 | + }) : _httpClient = HttpRequestImpl( | ||
47 | + allowAutoSignedCert: allowAutoSignedCert, | ||
48 | + trustedCertificates: trustedCertificates, | ||
49 | + ), | ||
50 | + _interceptor = GetModifier(); | ||
51 | + | ||
52 | + Uri _createUri(String url, Map<String, dynamic> query) { | ||
53 | + if (baseUrl != null) { | ||
54 | + url = baseUrl + url; | ||
55 | + } | ||
56 | + final uri = Uri.parse(url); | ||
57 | + if (query != null) { | ||
58 | + uri.replace(queryParameters: query); | ||
59 | + } | ||
60 | + return uri; | ||
61 | + } | ||
62 | + | ||
63 | + Future<Request<T>> _requestWithBody<T>( | ||
64 | + String url, | ||
65 | + String contentType, | ||
66 | + dynamic body, | ||
67 | + String method, | ||
68 | + Map<String, dynamic> query, | ||
69 | + Decoder<T> decoder, | ||
70 | + ) async { | ||
71 | + assert(method != null); | ||
72 | + assert(body != null); | ||
73 | + | ||
74 | + List<int> bodyBytes; | ||
75 | + | ||
76 | + if (body is FormData) { | ||
77 | + bodyBytes = await body.readAsBytes(); | ||
78 | + } else { | ||
79 | + try { | ||
80 | + var jsonString = json.encode(body); | ||
81 | + | ||
82 | + //TODO check this implementation | ||
83 | + if (contentType != null) { | ||
84 | + if (contentType.toLowerCase() == | ||
85 | + 'application/x-www-form-urlencoded') { | ||
86 | + var paramName = 'param'; | ||
87 | + jsonString = '$paramName=${Uri.encodeQueryComponent(jsonString)}'; | ||
88 | + } | ||
89 | + } | ||
90 | + | ||
91 | + bodyBytes = utf8.encode(jsonString); | ||
92 | + } on Exception catch (err) { | ||
93 | + throw UnexpectedFormat(err.toString()); | ||
94 | + } | ||
95 | + } | ||
96 | + | ||
97 | + final bodyStream = BodyBytes.fromBytes(bodyBytes); | ||
98 | + | ||
99 | + final headers = <String, String>{}; | ||
100 | + | ||
101 | + _setHeadersWithBody(contentType, headers, bodyBytes); | ||
102 | + | ||
103 | + final uri = _createUri(url, query); | ||
104 | + | ||
105 | + return Request( | ||
106 | + method: method, | ||
107 | + url: uri, | ||
108 | + headers: headers, | ||
109 | + bodyBytes: bodyStream, | ||
110 | + followRedirects: followRedirects, | ||
111 | + maxRedirects: maxRedirects, | ||
112 | + ); | ||
113 | + } | ||
114 | + | ||
115 | + void _setHeadersWithBody( | ||
116 | + String contentType, | ||
117 | + // String jsonString, | ||
118 | + Map<String, String> headers, | ||
119 | + List<int> bodyBytes, | ||
120 | + // List<MultipartFile> files, | ||
121 | + ) { | ||
122 | + // if (files != null) { | ||
123 | + // headers['content-type'] = 'multipart/form-data'; | ||
124 | + // headers['x-requested-with'] = 'XMLHttpRequest'; | ||
125 | + // } else { | ||
126 | + // headers['content-type'] = contentType ?? defaultContentType; | ||
127 | + // } | ||
128 | + | ||
129 | + headers['content-type'] = | ||
130 | + contentType ?? defaultContentType; // verify if this is better location | ||
131 | + | ||
132 | + headers['user-agent'] = userAgent; | ||
133 | + headers['content-length'] = bodyBytes.length.toString(); | ||
134 | + } | ||
135 | + | ||
136 | + void _setSimpleHeaders( | ||
137 | + Map<String, String> headers, | ||
138 | + String contentType, | ||
139 | + ) { | ||
140 | + headers['content-type'] = contentType ?? defaultContentType; | ||
141 | + headers['user-agent'] = userAgent; | ||
142 | + } | ||
143 | + | ||
144 | + Future<Response<T>> _performRequest<T>( | ||
145 | + HandlerExecute<T> handler, { | ||
146 | + bool authenticate = false, | ||
147 | + int requestNumber = 1, | ||
148 | + Map<String, String> headers, | ||
149 | + }) async { | ||
150 | + try { | ||
151 | + var request = await handler(); | ||
152 | + | ||
153 | + headers?.forEach((key, value) { | ||
154 | + request.headers[key] = value; | ||
155 | + }); | ||
156 | + | ||
157 | + if (authenticate) await _interceptor.authenticator(request); | ||
158 | + await _interceptor.modifyRequest(request); | ||
159 | + | ||
160 | + var response = await _httpClient.send<T>(request); | ||
161 | + | ||
162 | + await _interceptor.modifyResponse(request, response); | ||
163 | + | ||
164 | + if (HttpStatus.unauthorized == response.statusCode && | ||
165 | + _interceptor.authenticator != null && | ||
166 | + requestNumber <= maxAuthRetries) { | ||
167 | + return _performRequest( | ||
168 | + handler, | ||
169 | + authenticate: true, | ||
170 | + requestNumber: requestNumber + 1, | ||
171 | + headers: request.headers, | ||
172 | + ); | ||
173 | + } else if (HttpStatus.unauthorized == response.statusCode) { | ||
174 | + throw UnauthorizedException(); | ||
175 | + } | ||
176 | + | ||
177 | + return response; | ||
178 | + } on Exception catch (err) { | ||
179 | + throw GetHttpException(err.toString()); | ||
180 | + } | ||
181 | + } | ||
182 | + | ||
183 | + Future<Request<T>> _get<T>( | ||
184 | + String url, | ||
185 | + String contentType, | ||
186 | + Map<String, dynamic> query, | ||
187 | + Decoder<T> decoder, | ||
188 | + ) { | ||
189 | + final headers = <String, String>{}; | ||
190 | + _setSimpleHeaders(headers, contentType); | ||
191 | + final uri = _createUri(url, query); | ||
192 | + | ||
193 | + return Future.value(Request<T>( | ||
194 | + method: 'get', | ||
195 | + url: uri, | ||
196 | + headers: headers, | ||
197 | + decoder: decoder ?? (defaultDecoder as Decoder<T>), | ||
198 | + )); | ||
199 | + } | ||
200 | + | ||
201 | + Future<Request<T>> _post<T>( | ||
202 | + String url, { | ||
203 | + String contentType, | ||
204 | + @required dynamic body, | ||
205 | + Map<String, dynamic> query, | ||
206 | + Decoder<T> decoder, | ||
207 | + // List<MultipartFile> files, | ||
208 | + }) { | ||
209 | + assert(body != null); | ||
210 | + return _requestWithBody<T>( | ||
211 | + url, | ||
212 | + contentType, | ||
213 | + body, | ||
214 | + 'post', | ||
215 | + query, | ||
216 | + decoder, | ||
217 | + ); | ||
218 | + } | ||
219 | + | ||
220 | + Future<Request<T>> _put<T>( | ||
221 | + String url, { | ||
222 | + String contentType, | ||
223 | + @required dynamic body, | ||
224 | + @required Map<String, dynamic> query, | ||
225 | + Decoder<T> decoder, | ||
226 | + // List<MultipartFile> files, | ||
227 | + }) { | ||
228 | + assert(body != null); | ||
229 | + return _requestWithBody(url, contentType, body, 'put', query, decoder); | ||
230 | + } | ||
231 | + | ||
232 | + Request<T> _delete<T>( | ||
233 | + String url, | ||
234 | + String contentType, | ||
235 | + Map<String, dynamic> query, | ||
236 | + Decoder<T> decoder, | ||
237 | + ) { | ||
238 | + final headers = <String, String>{}; | ||
239 | + _setSimpleHeaders(headers, contentType); | ||
240 | + final uri = _createUri(url, query); | ||
241 | + | ||
242 | + return Request<T>( | ||
243 | + method: 'delete', url: uri, headers: headers, decoder: decoder); | ||
244 | + } | ||
245 | + | ||
246 | + Future<Response<T>> post<T>( | ||
247 | + String url, | ||
248 | + dynamic body, { | ||
249 | + String contentType, | ||
250 | + Map<String, String> headers, | ||
251 | + Map<String, dynamic> query, | ||
252 | + Decoder<T> decoder, | ||
253 | + // List<MultipartFile> files, | ||
254 | + }) async { | ||
255 | + try { | ||
256 | + var response = await _performRequest<T>( | ||
257 | + () => _post<T>( | ||
258 | + url, | ||
259 | + contentType: contentType, | ||
260 | + body: body, | ||
261 | + query: query, | ||
262 | + decoder: decoder, | ||
263 | + // files: files, | ||
264 | + ), | ||
265 | + headers: headers, | ||
266 | + ); | ||
267 | + return response; | ||
268 | + } on Exception catch (e) { | ||
269 | + return Future.value(Response<T>( | ||
270 | + request: null, | ||
271 | + statusCode: null, | ||
272 | + body: null, | ||
273 | + statusText: 'Can not connect to server. Reason: $e', | ||
274 | + )); | ||
275 | + } | ||
276 | + } | ||
277 | + | ||
278 | + Future<Response<T>> put<T>( | ||
279 | + String url, | ||
280 | + Map<String, dynamic> body, { | ||
281 | + String contentType, | ||
282 | + Map<String, String> headers, | ||
283 | + Map<String, dynamic> query, | ||
284 | + Decoder<T> decoder, | ||
285 | + }) async { | ||
286 | + try { | ||
287 | + var response = await _performRequest( | ||
288 | + () => _put( | ||
289 | + url, | ||
290 | + contentType: contentType, | ||
291 | + query: query, | ||
292 | + body: body, | ||
293 | + decoder: decoder, | ||
294 | + ), | ||
295 | + headers: headers, | ||
296 | + ); | ||
297 | + return response; | ||
298 | + } on Exception catch (e) { | ||
299 | + return Future.value(Response<T>( | ||
300 | + request: null, | ||
301 | + statusCode: null, | ||
302 | + body: null, | ||
303 | + statusText: 'Can not connect to server. Reason: $e', | ||
304 | + )); | ||
305 | + } | ||
306 | + } | ||
307 | + | ||
308 | + Future<Response<T>> get<T>( | ||
309 | + String url, { | ||
310 | + Map<String, String> headers, | ||
311 | + String contentType, | ||
312 | + Map<String, dynamic> query, | ||
313 | + Decoder<T> decoder, | ||
314 | + }) async { | ||
315 | + try { | ||
316 | + var response = await _performRequest<T>( | ||
317 | + () => _get<T>(url, contentType, query, decoder), | ||
318 | + headers: headers, | ||
319 | + ); | ||
320 | + return response; | ||
321 | + } on Exception catch (e) { | ||
322 | + return Future.value(Response<T>( | ||
323 | + request: null, | ||
324 | + statusCode: null, | ||
325 | + body: null, | ||
326 | + statusText: 'Can not connect to server. Reason: $e', | ||
327 | + )); | ||
328 | + } | ||
329 | + } | ||
330 | + | ||
331 | + Future<Response<T>> delete<T>( | ||
332 | + String url, { | ||
333 | + Map<String, String> headers, | ||
334 | + String contentType, | ||
335 | + Map<String, dynamic> query, | ||
336 | + Decoder<T> decoder, | ||
337 | + }) async { | ||
338 | + try { | ||
339 | + var response = await _performRequest( | ||
340 | + () async => _delete<T>(url, contentType, query, decoder), | ||
341 | + headers: headers, | ||
342 | + ); | ||
343 | + return response; | ||
344 | + } on Exception catch (e) { | ||
345 | + return Future.value(Response<T>( | ||
346 | + request: null, | ||
347 | + statusCode: null, | ||
348 | + body: null, | ||
349 | + statusText: 'Can not connect to server. Reason: $e', | ||
350 | + )); | ||
351 | + } | ||
352 | + } | ||
353 | + | ||
354 | + void close() { | ||
355 | + _httpClient.close(); | ||
356 | + } | ||
357 | +} |
1 | +import 'dart:async'; | ||
2 | +import 'dart:convert'; | ||
3 | +import 'dart:html' as html; | ||
4 | +import 'dart:typed_data'; | ||
5 | + | ||
6 | +import '../certificates/certificates.dart'; | ||
7 | +import '../exceptions/exceptions.dart'; | ||
8 | +import '../request/request.dart'; | ||
9 | +import '../response/response.dart'; | ||
10 | +import 'request_base.dart'; | ||
11 | + | ||
12 | +/// A `dart:html` implementation of `HttpRequestBase`. | ||
13 | +class HttpRequestImpl implements HttpRequestBase { | ||
14 | + HttpRequestImpl({ | ||
15 | + bool allowAutoSignedCert = true, | ||
16 | + List<TrustedCertificate> trustedCertificates, | ||
17 | + }); | ||
18 | + | ||
19 | + /// The currently active XHRs. | ||
20 | + final _xhrs = <html.HttpRequest>{}; | ||
21 | + | ||
22 | + ///This option requires that you submit credentials for requests | ||
23 | + ///on different sites. The default is false | ||
24 | + bool withCredentials = false; | ||
25 | + | ||
26 | + /// Sends an HTTP request and asynchronously returns the response. | ||
27 | + @override | ||
28 | + Future<Response<T>> send<T>(Request<T> request) async { | ||
29 | + var bytes = await request.bodyBytes.toBytes(); | ||
30 | + html.HttpRequest xhr; | ||
31 | + | ||
32 | + // if (request.files != null) { | ||
33 | + // var data = html.FormData(); | ||
34 | + // if (request.files != null) { | ||
35 | + // for (MultipartFile element in request.files) { | ||
36 | + // var stream = element.finalize(); | ||
37 | + // data.appendBlob(element., html.File(element.finalize(), | ||
38 | + // element.filename), | ||
39 | + // element.filename); | ||
40 | + // } | ||
41 | + // } | ||
42 | + | ||
43 | + // xhr = await html.HttpRequest.request('${request.url}', | ||
44 | + // method: request.method, sendData: data); | ||
45 | + // } else { | ||
46 | + // xhr = html.HttpRequest() | ||
47 | + // ..open(request.method, '${request.url}', async: true); | ||
48 | + // } | ||
49 | + | ||
50 | + xhr = html.HttpRequest() | ||
51 | + ..open(request.method, '${request.url}', async: true); // check this | ||
52 | + | ||
53 | + _xhrs.add(xhr); | ||
54 | + | ||
55 | + xhr | ||
56 | + ..responseType = 'blob' | ||
57 | + ..withCredentials = withCredentials; | ||
58 | + request.headers.forEach(xhr.setRequestHeader); | ||
59 | + | ||
60 | + var completer = Completer<Response<T>>(); | ||
61 | + xhr.onLoad.first.then((_) { | ||
62 | + var blob = xhr.response as html.Blob ?? html.Blob([]); | ||
63 | + var reader = html.FileReader(); | ||
64 | + | ||
65 | + reader.onLoad.first.then((_) async { | ||
66 | + var bodyBytes = BodyBytes.fromBytes(reader.result as Uint8List); | ||
67 | + | ||
68 | + final stringBody = | ||
69 | + await bodyBytesToString(bodyBytes, xhr.responseHeaders); | ||
70 | + | ||
71 | + T body; | ||
72 | + try { | ||
73 | + if (request.decoder == null) { | ||
74 | + body = jsonDecode(stringBody) as T; | ||
75 | + } else { | ||
76 | + body = request.decoder(jsonDecode(stringBody)); | ||
77 | + } | ||
78 | + // body = request.decoder(stringBody); | ||
79 | + } on Exception catch (_) { | ||
80 | + body = stringBody as T; | ||
81 | + } | ||
82 | + | ||
83 | + // final body = jsonDecode(stringBody); | ||
84 | + | ||
85 | + final response = Response<T>( | ||
86 | + bodyBytes: bodyBytes, | ||
87 | + statusCode: xhr.status, | ||
88 | + request: request, | ||
89 | + headers: xhr.responseHeaders, | ||
90 | + statusText: xhr.statusText, | ||
91 | + body: body, | ||
92 | + ); | ||
93 | + completer.complete(response); | ||
94 | + }); | ||
95 | + | ||
96 | + reader.onError.first.then((error) { | ||
97 | + completer.completeError( | ||
98 | + GetHttpException(error.toString(), request.url), | ||
99 | + StackTrace.current, | ||
100 | + ); | ||
101 | + }); | ||
102 | + | ||
103 | + reader.readAsArrayBuffer(blob); | ||
104 | + }); | ||
105 | + | ||
106 | + xhr.onError.first.then((_) { | ||
107 | + completer.completeError( | ||
108 | + GetHttpException('XMLHttpRequest error.', request.url), | ||
109 | + StackTrace.current); | ||
110 | + }); | ||
111 | + | ||
112 | + xhr.send(bytes); | ||
113 | + | ||
114 | + try { | ||
115 | + return await completer.future; | ||
116 | + } finally { | ||
117 | + _xhrs.remove(xhr); | ||
118 | + } | ||
119 | + } | ||
120 | + | ||
121 | + /// Closes the client and abort all active requests. | ||
122 | + @override | ||
123 | + void close() { | ||
124 | + for (var xhr in _xhrs) { | ||
125 | + xhr.abort(); | ||
126 | + } | ||
127 | + } | ||
128 | +} |
1 | +import 'dart:convert'; | ||
2 | +import 'dart:io' as io; | ||
3 | + | ||
4 | +import '../certificates/certificates.dart'; | ||
5 | +import '../exceptions/exceptions.dart'; | ||
6 | +import '../request/request.dart'; | ||
7 | +import '../response/response.dart'; | ||
8 | +import 'request_base.dart'; | ||
9 | + | ||
10 | +/// A `dart:io` implementation of `HttpRequestBase`. | ||
11 | +class HttpRequestImpl extends HttpRequestBase { | ||
12 | + io.HttpClient _httpClient; | ||
13 | + io.SecurityContext _securityContext; | ||
14 | + | ||
15 | + HttpRequestImpl({ | ||
16 | + bool allowAutoSignedCert = true, | ||
17 | + List<TrustedCertificate> trustedCertificates, | ||
18 | + }) { | ||
19 | + _httpClient = io.HttpClient(); | ||
20 | + if (trustedCertificates != null) { | ||
21 | + _securityContext = io.SecurityContext(); | ||
22 | + for (final trustedCertificate in trustedCertificates) { | ||
23 | + _securityContext | ||
24 | + .setTrustedCertificatesBytes(List.from(trustedCertificate.bytes)); | ||
25 | + } | ||
26 | + } | ||
27 | + | ||
28 | + _httpClient = io.HttpClient(context: _securityContext); | ||
29 | + _httpClient.badCertificateCallback = (_, __, ___) => allowAutoSignedCert; | ||
30 | + } | ||
31 | + | ||
32 | + @override | ||
33 | + Future<Response<T>> send<T>(Request<T> request) async { | ||
34 | + var requestBody = await request.bodyBytes.toBytes(); | ||
35 | + var stream = BodyBytes.fromBytes(requestBody ?? const []); | ||
36 | + | ||
37 | + try { | ||
38 | + var ioRequest = (await _httpClient.openUrl(request.method, request.url)) | ||
39 | + ..followRedirects = request.followRedirects | ||
40 | + ..persistentConnection = request.persistentConnection | ||
41 | + ..maxRedirects = request.maxRedirects | ||
42 | + ..contentLength = requestBody.length ?? -1; | ||
43 | + request.headers.forEach(ioRequest.headers.set); | ||
44 | + | ||
45 | + var response = await stream.pipe(ioRequest) as io.HttpClientResponse; | ||
46 | + | ||
47 | + var headers = <String, String>{}; | ||
48 | + response.headers.forEach((key, values) { | ||
49 | + headers[key] = values.join(','); | ||
50 | + }); | ||
51 | + | ||
52 | + final bodyBytes = BodyBytes(response); | ||
53 | + | ||
54 | + final stringBody = await bodyBytesToString(bodyBytes, headers); | ||
55 | + | ||
56 | + T body; | ||
57 | + try { | ||
58 | + if (request.decoder == null) { | ||
59 | + body = jsonDecode(stringBody) as T; | ||
60 | + } else { | ||
61 | + body = request.decoder(jsonDecode(stringBody)); | ||
62 | + } | ||
63 | + } on Exception catch (_) { | ||
64 | + body = stringBody as T; | ||
65 | + } | ||
66 | + | ||
67 | + return Response( | ||
68 | + headers: headers, | ||
69 | + request: request, | ||
70 | + statusCode: response.statusCode, | ||
71 | + statusText: response.reasonPhrase, | ||
72 | + bodyBytes: bodyBytes, | ||
73 | + body: body, | ||
74 | + ); | ||
75 | + } on io.HttpException catch (error) { | ||
76 | + throw GetHttpException(error.message, error.uri); | ||
77 | + } | ||
78 | + } | ||
79 | + | ||
80 | + /// Closes the HttpClient. | ||
81 | + @override | ||
82 | + void close() { | ||
83 | + if (_httpClient != null) { | ||
84 | + _httpClient.close(force: true); | ||
85 | + _httpClient = null; | ||
86 | + } | ||
87 | + } | ||
88 | +} | ||
89 | + | ||
90 | +extension FileExt on io.FileSystemEntity { | ||
91 | + String get fileName { | ||
92 | + return this?.path?.split(io.Platform.pathSeparator)?.last; | ||
93 | + } | ||
94 | +} |
1 | +import '../certificates/certificates.dart'; | ||
2 | +import '../request/request.dart'; | ||
3 | +import '../response/response.dart'; | ||
4 | +import 'request_base.dart'; | ||
5 | + | ||
6 | +class HttpRequestImpl extends HttpRequestBase { | ||
7 | + HttpRequestImpl({ | ||
8 | + bool allowAutoSignedCert = true, | ||
9 | + List<TrustedCertificate> trustedCertificates, | ||
10 | + }); | ||
11 | + @override | ||
12 | + void close() {} | ||
13 | + | ||
14 | + @override | ||
15 | + Future<Response<T>> send<T>(Request<T> request) { | ||
16 | + throw UnimplementedError(); | ||
17 | + } | ||
18 | +} |
1 | +import '../request/request.dart'; | ||
2 | +import '../response/response.dart'; | ||
3 | + | ||
4 | +/// Abstract interface of [HttpRequestImpl]. | ||
5 | +abstract class HttpRequestBase { | ||
6 | + /// Sends an HTTP [Request]. | ||
7 | + Future<Response<T>> send<T>(Request<T> request); | ||
8 | + | ||
9 | + /// Closes the [Request] and cleans up any resources associated with it. | ||
10 | + void close(); | ||
11 | +} |
1 | +import '../request/request.dart'; | ||
2 | +import '../response/response.dart'; | ||
3 | + | ||
4 | +typedef RequestModifier = Future<Request> Function(Request request); | ||
5 | + | ||
6 | +typedef ResponseModifier = Future<Null> Function( | ||
7 | + Request request, Response response); | ||
8 | + | ||
9 | +typedef HandlerExecute<T> = Future<Request<T>> Function(); | ||
10 | + | ||
11 | +class GetModifier { | ||
12 | + final _requestModifiers = <RequestModifier>[]; | ||
13 | + final _responseModifiers = <ResponseModifier>[]; | ||
14 | + RequestModifier authenticator; | ||
15 | + | ||
16 | + void addRequestModifier(RequestModifier interceptor) { | ||
17 | + _requestModifiers.add(interceptor); | ||
18 | + } | ||
19 | + | ||
20 | + void removeRequestModifier(RequestModifier interceptor) { | ||
21 | + _requestModifiers.remove(interceptor); | ||
22 | + } | ||
23 | + | ||
24 | + void addResponseModifier(ResponseModifier interceptor) { | ||
25 | + _responseModifiers.add(interceptor); | ||
26 | + } | ||
27 | + | ||
28 | + void removeResponseModifier(ResponseModifier interceptor) { | ||
29 | + _requestModifiers.remove(interceptor); | ||
30 | + } | ||
31 | + | ||
32 | + Future<void> modifyRequest(Request request) async { | ||
33 | + if (_requestModifiers.isNotEmpty) { | ||
34 | + for (var interceptor in _requestModifiers) { | ||
35 | + await interceptor(request); | ||
36 | + } | ||
37 | + } | ||
38 | + } | ||
39 | + | ||
40 | + Future<void> modifyResponse(Request request, Response response) async { | ||
41 | + if (_responseModifiers.isNotEmpty) { | ||
42 | + for (var interceptor in _responseModifiers) { | ||
43 | + await interceptor(request, response); | ||
44 | + } | ||
45 | + } | ||
46 | + } | ||
47 | +} |
1 | +import 'dart:async'; | ||
2 | +import 'dart:convert'; | ||
3 | +import 'dart:math'; | ||
4 | +import '../../../../get_rx/src/rx_stream/rx_stream.dart'; | ||
5 | + | ||
6 | +import '../utils/utils.dart'; | ||
7 | + | ||
8 | +import 'multipart_file.dart'; | ||
9 | + | ||
10 | +class FormData { | ||
11 | + static const _BOUNDARY_LENGTH = GET_BOUNDARY.length + 10; | ||
12 | + | ||
13 | + final String boundary; | ||
14 | + | ||
15 | + /// The boundary of FormData, it consists of a constant prefix and a random | ||
16 | + /// postfix to assure the the boundary unpredictable and unique, each FormData | ||
17 | + /// instance will be different. | ||
18 | + | ||
19 | + final _newlineRegExp = RegExp(r'\r\n|\r|\n'); | ||
20 | + | ||
21 | + /// The form fields to send for this request. | ||
22 | + final fields = <MapEntry<String, String>>[]; | ||
23 | + | ||
24 | + /// The [files]. | ||
25 | + final files = <MapEntry<String, MultipartFile>>[]; | ||
26 | + | ||
27 | + /// Whether [finalize] has been called. | ||
28 | + bool get isFinalized => _isFinalized; | ||
29 | + bool _isFinalized = false; | ||
30 | + | ||
31 | + FormData(Map<String, dynamic> map) | ||
32 | + : boundary = GET_BOUNDARY + | ||
33 | + Random().nextInt(4294967296).toString().padLeft(10, '0') { | ||
34 | + encodeMap( | ||
35 | + map, | ||
36 | + (key, value) { | ||
37 | + if (value == null) return null; | ||
38 | + if (value is MultipartFile) { | ||
39 | + files.add(MapEntry(key, value)); | ||
40 | + } else { | ||
41 | + fields.add(MapEntry(key, value.toString())); | ||
42 | + } | ||
43 | + return null; | ||
44 | + }, | ||
45 | + encode: false, | ||
46 | + ); | ||
47 | + } | ||
48 | + | ||
49 | + /// Returns the header string for a field. The return value is guaranteed to | ||
50 | + /// contain only ASCII characters. | ||
51 | + String _headerForField(String name, String value) { | ||
52 | + var header = | ||
53 | + 'content-disposition: form-data; name="${_browserEncode(name)}"'; | ||
54 | + if (!isPlainAscii(value)) { | ||
55 | + header = '$header\r\n' | ||
56 | + 'content-type: text/plain; charset=utf-8\r\n' | ||
57 | + 'content-transfer-encoding: binary'; | ||
58 | + } | ||
59 | + return '$header\r\n\r\n'; | ||
60 | + } | ||
61 | + | ||
62 | + /// Returns the header string for a file. The return value is guaranteed to | ||
63 | + /// contain only ASCII characters. | ||
64 | + String _headerForFile(MapEntry<String, MultipartFile> entry) { | ||
65 | + var file = entry.value; | ||
66 | + var header = | ||
67 | + 'content-disposition: form-data; name="${_browserEncode(entry.key)}"'; | ||
68 | + if (file.filename != null) { | ||
69 | + header = '$header; filename="${_browserEncode(file.filename)}"'; | ||
70 | + } | ||
71 | + header = '$header\r\n' | ||
72 | + 'content-type: ${file.contentType}'; | ||
73 | + return '$header\r\n\r\n'; | ||
74 | + } | ||
75 | + | ||
76 | + /// Encode [value] in the same way browsers do. | ||
77 | + String _browserEncode(String value) { | ||
78 | + // http://tools.ietf.org/html/rfc2388 mandates some complex encodings for | ||
79 | + // field names and file names, but in practice user agents seem not to | ||
80 | + // follow this at all. Instead, they URL-encode `\r`, `\n`, and `\r\n` as | ||
81 | + // `\r\n`; URL-encode `"`; and do nothing else (even for `%` or non-ASCII | ||
82 | + // characters). We follow their behavior. | ||
83 | + return value.replaceAll(_newlineRegExp, '%0D%0A').replaceAll('"', '%22'); | ||
84 | + } | ||
85 | + | ||
86 | + /// The total length of the request body, in bytes. This is calculated from | ||
87 | + /// [fields] and [files] and cannot be set manually. | ||
88 | + int get length { | ||
89 | + var length = 0; | ||
90 | + | ||
91 | + for (final item in fields) { | ||
92 | + length += '--'.length + | ||
93 | + _BOUNDARY_LENGTH + | ||
94 | + '\r\n'.length + | ||
95 | + utf8.encode(_headerForField(item.key, item.value)).length + | ||
96 | + utf8.encode(item.value).length + | ||
97 | + '\r\n'.length; | ||
98 | + } | ||
99 | + | ||
100 | + for (var file in files) { | ||
101 | + length += '--'.length + | ||
102 | + _BOUNDARY_LENGTH + | ||
103 | + '\r\n'.length + | ||
104 | + utf8.encode(_headerForFile(file)).length + | ||
105 | + file.value.length + | ||
106 | + '\r\n'.length; | ||
107 | + } | ||
108 | + | ||
109 | + return length + '--'.length + _BOUNDARY_LENGTH + '--\r\n'.length; | ||
110 | + } | ||
111 | + | ||
112 | + Stream<List<int>> finalize() { | ||
113 | + if (isFinalized) { | ||
114 | + throw StateError("Can't finalize a finalized MultipartFile."); | ||
115 | + } | ||
116 | + _isFinalized = true; | ||
117 | + final getStream = GetStream<List<int>>(); | ||
118 | + | ||
119 | + for (final item in fields) { | ||
120 | + stringToBytes('--$boundary\r\n', getStream); | ||
121 | + stringToBytes(_headerForField(item.key, item.value), getStream); | ||
122 | + stringToBytes(item.value, getStream); | ||
123 | + writeLine(getStream); | ||
124 | + } | ||
125 | + | ||
126 | + Future.forEach<MapEntry<String, MultipartFile>>(files, (file) { | ||
127 | + stringToBytes('--$boundary\r\n', getStream); | ||
128 | + stringToBytes(_headerForFile(file), getStream); | ||
129 | + | ||
130 | + return streamToFuture(file.value.stream, getStream) | ||
131 | + .then((_) => writeLine(getStream)); | ||
132 | + }).then((_) { | ||
133 | + stringToBytes('--$boundary--\r\n', getStream); | ||
134 | + getStream.close(); | ||
135 | + }); | ||
136 | + return getStream.stream; | ||
137 | + } | ||
138 | + | ||
139 | + ///Transform the entire FormData contents as a list of bytes asynchronously. | ||
140 | + Future<List<int>> readAsBytes() { | ||
141 | + return Future(() => finalize().reduce((a, b) => [...a, ...b])); | ||
142 | + } | ||
143 | +} |
1 | +class MultipartFile { | ||
2 | + final String contentType; | ||
3 | + | ||
4 | + final Stream<List<int>> _stream; | ||
5 | + | ||
6 | + final int length; | ||
7 | + | ||
8 | + final String filename; | ||
9 | + | ||
10 | + MultipartFile( | ||
11 | + List<int> bytes, { | ||
12 | + this.filename, | ||
13 | + this.contentType = 'application/octet-stream', | ||
14 | + }) : length = bytes.length, | ||
15 | + _stream = Stream.fromIterable([bytes]); | ||
16 | + | ||
17 | + Stream<List<int>> get stream => _stream; | ||
18 | +} |
1 | +import 'dart:async'; | ||
2 | +import 'dart:convert'; | ||
3 | +import 'dart:typed_data'; | ||
4 | + | ||
5 | +import 'package:meta/meta.dart'; | ||
6 | + | ||
7 | +import '../http.dart'; | ||
8 | +import '../multipart/form_data.dart'; | ||
9 | + | ||
10 | +class Request<T> { | ||
11 | + /// Headers attach to this [Request] | ||
12 | + final Map<String, String> headers; | ||
13 | + | ||
14 | + /// The [Uri] from request | ||
15 | + final Uri url; | ||
16 | + | ||
17 | + final Decoder<T> decoder; | ||
18 | + | ||
19 | + /// The Http Method from this [Request] | ||
20 | + /// ex: `GET`,`POST`,`PUT`,`DELETE` | ||
21 | + final String method; | ||
22 | + | ||
23 | + /// The BodyBytes of body from this [Request] | ||
24 | + final BodyBytes bodyBytes; | ||
25 | + | ||
26 | + /// When true, the client will follow redirects to resolves this [Request] | ||
27 | + final bool followRedirects; | ||
28 | + | ||
29 | + /// The maximum number of redirects if [followRedirects] is true. | ||
30 | + final int maxRedirects; | ||
31 | + | ||
32 | + final bool persistentConnection; | ||
33 | + | ||
34 | + final FormData files; | ||
35 | + | ||
36 | + const Request._({ | ||
37 | + @required this.method, | ||
38 | + @required this.bodyBytes, | ||
39 | + @required this.url, | ||
40 | + @required this.headers, | ||
41 | + @required this.followRedirects, | ||
42 | + @required this.maxRedirects, | ||
43 | + @required this.files, | ||
44 | + @required this.persistentConnection, | ||
45 | + @required this.decoder, | ||
46 | + }); | ||
47 | + | ||
48 | + factory Request({ | ||
49 | + @required Uri url, | ||
50 | + @required String method, | ||
51 | + @required Map<String, String> headers, | ||
52 | + BodyBytes bodyBytes, | ||
53 | + bool followRedirects = true, | ||
54 | + int maxRedirects = 4, | ||
55 | + FormData files, | ||
56 | + bool persistentConnection = true, | ||
57 | + final Decoder<T> decoder, | ||
58 | + }) { | ||
59 | + assert(url != null); | ||
60 | + assert(method != null); | ||
61 | + assert(followRedirects != null); | ||
62 | + if (followRedirects) { | ||
63 | + assert(maxRedirects != null); | ||
64 | + assert(maxRedirects > 0); | ||
65 | + } | ||
66 | + return Request._( | ||
67 | + url: url, | ||
68 | + method: method, | ||
69 | + bodyBytes: bodyBytes ??= BodyBytes.fromBytes(const []), | ||
70 | + headers: Map.from(headers ??= <String, String>{}), | ||
71 | + followRedirects: followRedirects, | ||
72 | + maxRedirects: maxRedirects, | ||
73 | + files: files, | ||
74 | + persistentConnection: persistentConnection, | ||
75 | + decoder: decoder, | ||
76 | + ); | ||
77 | + } | ||
78 | + | ||
79 | + /// Constructs a [Request] containing the same properties as [request]. | ||
80 | + factory Request.copyWith(Request request) => Request( | ||
81 | + method: request.method, | ||
82 | + url: request.url, | ||
83 | + headers: request.headers, | ||
84 | + bodyBytes: request.bodyBytes, | ||
85 | + followRedirects: request.followRedirects, | ||
86 | + maxRedirects: request.maxRedirects, | ||
87 | + files: request.files, | ||
88 | + persistentConnection: request.persistentConnection, | ||
89 | + ); | ||
90 | +} | ||
91 | + | ||
92 | +class BodyBytes extends StreamView<List<int>> { | ||
93 | + BodyBytes(Stream<List<int>> stream) : super(stream); | ||
94 | + | ||
95 | + factory BodyBytes.fromBytes(List<int> bytes) => | ||
96 | + BodyBytes(Stream.fromIterable([bytes])); | ||
97 | + | ||
98 | + Future<Uint8List> toBytes() { | ||
99 | + var completer = Completer<Uint8List>(); | ||
100 | + var sink = ByteConversionSink.withCallback( | ||
101 | + (bytes) => completer.complete( | ||
102 | + Uint8List.fromList(bytes), | ||
103 | + ), | ||
104 | + ); | ||
105 | + listen(sink.add, | ||
106 | + onError: completer.completeError, | ||
107 | + onDone: sink.close, | ||
108 | + cancelOnError: true); | ||
109 | + return completer.future; | ||
110 | + } | ||
111 | + | ||
112 | + Future<String> bytesToString([Encoding encoding = utf8]) => | ||
113 | + encoding.decodeStream(this); | ||
114 | + | ||
115 | + Stream<String> toStringStream([Encoding encoding = utf8]) => | ||
116 | + encoding.decoder.bind(this); | ||
117 | +} |
1 | +import 'dart:collection'; | ||
2 | +import 'dart:convert'; | ||
3 | + | ||
4 | +import 'package:flutter/foundation.dart'; | ||
5 | +import 'package:meta/meta.dart'; | ||
6 | + | ||
7 | +import '../request/request.dart'; | ||
8 | +import '../status/http_status.dart'; | ||
9 | + | ||
10 | +class Response<T> { | ||
11 | + const Response({ | ||
12 | + @required this.request, | ||
13 | + @required this.statusCode, | ||
14 | + // ignore: always_require_non_null_named_parameters | ||
15 | + this.bodyBytes, | ||
16 | + this.statusText = '', | ||
17 | + this.headers = const {}, | ||
18 | + @required this.body, | ||
19 | + }); | ||
20 | + | ||
21 | + /// The Http [Request] linked with this [Response]. | ||
22 | + final Request request; | ||
23 | + | ||
24 | + /// The response headers. | ||
25 | + final Map<String, String> headers; | ||
26 | + | ||
27 | + /// The status code returned by the server. | ||
28 | + final int statusCode; | ||
29 | + | ||
30 | + /// Human-readable context for [statusCode]. | ||
31 | + final String statusText; | ||
32 | + | ||
33 | + /// [HttpStatus] from [Response]. `status.connectionError` is true | ||
34 | + /// when statusCode is null. `status.isUnauthorized` is true when | ||
35 | + /// statusCode is equal `401`. `status.isNotFound` is true when | ||
36 | + /// statusCode is equal `404`. `status.isServerError` is true when | ||
37 | + /// statusCode is between `500` and `599`. | ||
38 | + HttpStatus get status => HttpStatus(statusCode); | ||
39 | + | ||
40 | + /// `hasError` is true when statusCode is not between 200 and 299. | ||
41 | + bool get hasError => status.hasError; | ||
42 | + | ||
43 | + /// `isOk` is true when statusCode is between 200 and 299. | ||
44 | + bool get isOk => !hasError; | ||
45 | + | ||
46 | + /// `unauthorized` is true when statusCode is equal `401`. | ||
47 | + bool get unauthorized => status.isUnauthorized; | ||
48 | + | ||
49 | + /// The response body as a Stream of Bytes. | ||
50 | + final BodyBytes bodyBytes; | ||
51 | + | ||
52 | + /// The decoded body of this [Response]. You can access the | ||
53 | + /// body parameters as Map | ||
54 | + /// Ex: body['title']; | ||
55 | + final T body; | ||
56 | +} | ||
57 | + | ||
58 | +Future<String> bodyBytesToString( | ||
59 | + BodyBytes bodyBytes, Map<String, String> headers) { | ||
60 | + return bodyBytes.bytesToString(_encodingForHeaders(headers)); | ||
61 | +} | ||
62 | + | ||
63 | +/// Returns the encoding to use for a response with the given headers. | ||
64 | +/// | ||
65 | +/// Defaults to [latin1] if the headers don't specify a charset or if that | ||
66 | +/// charset is unknown. | ||
67 | +Encoding _encodingForHeaders(Map<String, String> headers) => | ||
68 | + _encodingForCharset(_contentTypeForHeaders(headers).parameters['charset']); | ||
69 | + | ||
70 | +/// Returns the [Encoding] that corresponds to [charset]. | ||
71 | +/// | ||
72 | +/// Returns [fallback] if [charset] is null or if no [Encoding] was found that | ||
73 | +/// corresponds to [charset]. | ||
74 | +Encoding _encodingForCharset(String charset, [Encoding fallback = latin1]) { | ||
75 | + if (charset == null) return fallback; | ||
76 | + return Encoding.getByName(charset) ?? fallback; | ||
77 | +} | ||
78 | + | ||
79 | +/// Returns the [MediaType] object for the given headers's content-type. | ||
80 | +/// | ||
81 | +/// Defaults to `application/octet-stream`. | ||
82 | +HeaderValue _contentTypeForHeaders(Map<String, String> headers) { | ||
83 | + var contentType = headers['content-type']; | ||
84 | + if (contentType != null) return HeaderValue.parse(contentType); | ||
85 | + return HeaderValue('application/octet-stream'); | ||
86 | +} | ||
87 | + | ||
88 | +class HeaderValue { | ||
89 | + String _value; | ||
90 | + Map<String, String> _parameters; | ||
91 | + Map<String, String> _unmodifiableParameters; | ||
92 | + | ||
93 | + HeaderValue([this._value = '', Map<String, String> parameters]) { | ||
94 | + if (parameters != null) { | ||
95 | + _parameters = HashMap<String, String>.from(parameters); | ||
96 | + } | ||
97 | + } | ||
98 | + | ||
99 | + static HeaderValue parse(String value, | ||
100 | + {String parameterSeparator = ';', | ||
101 | + String valueSeparator, | ||
102 | + bool preserveBackslash = false}) { | ||
103 | + var result = HeaderValue(); | ||
104 | + result._parse(value, parameterSeparator, valueSeparator, preserveBackslash); | ||
105 | + return result; | ||
106 | + } | ||
107 | + | ||
108 | + String get value => _value; | ||
109 | + | ||
110 | + void _ensureParameters() { | ||
111 | + _parameters ??= HashMap<String, String>(); | ||
112 | + } | ||
113 | + | ||
114 | + Map<String, String> get parameters { | ||
115 | + _ensureParameters(); | ||
116 | + _unmodifiableParameters ??= UnmodifiableMapView(_parameters); | ||
117 | + return _unmodifiableParameters; | ||
118 | + } | ||
119 | + | ||
120 | + @override | ||
121 | + String toString() { | ||
122 | + var sb = StringBuffer(); | ||
123 | + sb.write(_value); | ||
124 | + if (parameters != null && parameters.isNotEmpty) { | ||
125 | + _parameters.forEach((name, value) { | ||
126 | + sb..write('; ')..write(name)..write('=')..write(value); | ||
127 | + }); | ||
128 | + } | ||
129 | + return sb.toString(); | ||
130 | + } | ||
131 | + | ||
132 | + void _parse(String value, String parameterSeparator, String valueSeparator, | ||
133 | + bool preserveBackslash) { | ||
134 | + var index = 0; | ||
135 | + | ||
136 | + bool done() => index == value.length; | ||
137 | + | ||
138 | + void skipWS() { | ||
139 | + while (!done()) { | ||
140 | + if (value[index] != ' ' && value[index] != '\t') return; | ||
141 | + index++; | ||
142 | + } | ||
143 | + } | ||
144 | + | ||
145 | + String parseValue() { | ||
146 | + var start = index; | ||
147 | + while (!done()) { | ||
148 | + if (value[index] == ' ' || | ||
149 | + value[index] == '\t' || | ||
150 | + value[index] == valueSeparator || | ||
151 | + value[index] == parameterSeparator) { | ||
152 | + break; | ||
153 | + } | ||
154 | + index++; | ||
155 | + } | ||
156 | + return value.substring(start, index); | ||
157 | + } | ||
158 | + | ||
159 | + void expect(String expected) { | ||
160 | + if (done() || value[index] != expected) { | ||
161 | + throw StateError('Failed to parse header value'); | ||
162 | + } | ||
163 | + index++; | ||
164 | + } | ||
165 | + | ||
166 | + void maybeExpect(String expected) { | ||
167 | + if (value[index] == expected) index++; | ||
168 | + } | ||
169 | + | ||
170 | + void parseParameters() { | ||
171 | + var parameters = HashMap<String, String>(); | ||
172 | + _parameters = UnmodifiableMapView(parameters); | ||
173 | + | ||
174 | + String parseParameterName() { | ||
175 | + var start = index; | ||
176 | + while (!done()) { | ||
177 | + if (value[index] == ' ' || | ||
178 | + value[index] == '\t' || | ||
179 | + value[index] == '=' || | ||
180 | + value[index] == parameterSeparator || | ||
181 | + value[index] == valueSeparator) { | ||
182 | + break; | ||
183 | + } | ||
184 | + index++; | ||
185 | + } | ||
186 | + return value.substring(start, index).toLowerCase(); | ||
187 | + } | ||
188 | + | ||
189 | + String parseParameterValue() { | ||
190 | + if (!done() && value[index] == '\"') { | ||
191 | + var sb = StringBuffer(); | ||
192 | + index++; | ||
193 | + while (!done()) { | ||
194 | + if (value[index] == '\\') { | ||
195 | + if (index + 1 == value.length) { | ||
196 | + throw StateError('Failed to parse header value'); | ||
197 | + } | ||
198 | + if (preserveBackslash && value[index + 1] != '\"') { | ||
199 | + sb.write(value[index]); | ||
200 | + } | ||
201 | + index++; | ||
202 | + } else if (value[index] == '\"') { | ||
203 | + index++; | ||
204 | + break; | ||
205 | + } | ||
206 | + sb.write(value[index]); | ||
207 | + index++; | ||
208 | + } | ||
209 | + return sb.toString(); | ||
210 | + } else { | ||
211 | + var val = parseValue(); | ||
212 | + return val == '' ? null : val; | ||
213 | + } | ||
214 | + } | ||
215 | + | ||
216 | + while (!done()) { | ||
217 | + skipWS(); | ||
218 | + if (done()) return; | ||
219 | + var name = parseParameterName(); | ||
220 | + skipWS(); | ||
221 | + if (done()) { | ||
222 | + parameters[name] = null; | ||
223 | + return; | ||
224 | + } | ||
225 | + maybeExpect('='); | ||
226 | + skipWS(); | ||
227 | + if (done()) { | ||
228 | + parameters[name] = null; | ||
229 | + return; | ||
230 | + } | ||
231 | + var value = parseParameterValue(); | ||
232 | + if (name == 'charset' && value != null) { | ||
233 | + value = value.toLowerCase(); | ||
234 | + } | ||
235 | + parameters[name] = value; | ||
236 | + skipWS(); | ||
237 | + if (done()) return; | ||
238 | + if (value[index] == valueSeparator) return; | ||
239 | + expect(parameterSeparator); | ||
240 | + } | ||
241 | + } | ||
242 | + | ||
243 | + skipWS(); | ||
244 | + _value = parseValue(); | ||
245 | + skipWS(); | ||
246 | + if (done()) return; | ||
247 | + maybeExpect(parameterSeparator); | ||
248 | + parseParameters(); | ||
249 | + } | ||
250 | +} |
1 | +class HttpStatus { | ||
2 | + HttpStatus(this.code); | ||
3 | + final int code; | ||
4 | + | ||
5 | + static const int continue_ = 100; | ||
6 | + static const int switchingProtocols = 101; | ||
7 | + static const int processing = 102; | ||
8 | + static const int ok = 200; | ||
9 | + static const int created = 201; | ||
10 | + static const int accepted = 202; | ||
11 | + static const int nonAuthoritativeInformation = 203; | ||
12 | + static const int noContent = 204; | ||
13 | + static const int resetContent = 205; | ||
14 | + static const int partialContent = 206; | ||
15 | + static const int multiStatus = 207; | ||
16 | + static const int alreadyReported = 208; | ||
17 | + static const int imUsed = 226; | ||
18 | + static const int multipleChoices = 300; | ||
19 | + static const int movedPermanently = 301; | ||
20 | + static const int found = 302; | ||
21 | + static const int movedTemporarily = 302; // Common alias for found. | ||
22 | + static const int seeOther = 303; | ||
23 | + static const int notModified = 304; | ||
24 | + static const int useProxy = 305; | ||
25 | + static const int temporaryRedirect = 307; | ||
26 | + static const int permanentRedirect = 308; | ||
27 | + static const int badRequest = 400; | ||
28 | + static const int unauthorized = 401; | ||
29 | + static const int paymentRequired = 402; | ||
30 | + static const int forbidden = 403; | ||
31 | + static const int notFound = 404; | ||
32 | + static const int methodNotAllowed = 405; | ||
33 | + static const int notAcceptable = 406; | ||
34 | + static const int proxyAuthenticationRequired = 407; | ||
35 | + static const int requestTimeout = 408; | ||
36 | + static const int conflict = 409; | ||
37 | + static const int gone = 410; | ||
38 | + static const int lengthRequired = 411; | ||
39 | + static const int preconditionFailed = 412; | ||
40 | + static const int requestEntityTooLarge = 413; | ||
41 | + static const int requestUriTooLong = 414; | ||
42 | + static const int unsupportedMediaType = 415; | ||
43 | + static const int requestedRangeNotSatisfiable = 416; | ||
44 | + static const int expectationFailed = 417; | ||
45 | + static const int misdirectedRequest = 421; | ||
46 | + static const int unprocessableEntity = 422; | ||
47 | + static const int locked = 423; | ||
48 | + static const int failedDependency = 424; | ||
49 | + static const int upgradeRequired = 426; | ||
50 | + static const int preconditionRequired = 428; | ||
51 | + static const int tooManyRequests = 429; | ||
52 | + static const int requestHeaderFieldsTooLarge = 431; | ||
53 | + static const int connectionClosedWithoutResponse = 444; | ||
54 | + static const int unavailableForLegalReasons = 451; | ||
55 | + static const int clientClosedRequest = 499; | ||
56 | + static const int internalServerError = 500; | ||
57 | + static const int notImplemented = 501; | ||
58 | + static const int badGateway = 502; | ||
59 | + static const int serviceUnavailable = 503; | ||
60 | + static const int gatewayTimeout = 504; | ||
61 | + static const int httpVersionNotSupported = 505; | ||
62 | + static const int variantAlsoNegotiates = 506; | ||
63 | + static const int insufficientStorage = 507; | ||
64 | + static const int loopDetected = 508; | ||
65 | + static const int notExtended = 510; | ||
66 | + static const int networkAuthenticationRequired = 511; | ||
67 | + static const int networkConnectTimeoutError = 599; | ||
68 | + | ||
69 | + bool get connectionError => code == null; | ||
70 | + | ||
71 | + bool get isUnauthorized => code == unauthorized; | ||
72 | + | ||
73 | + bool get isForbidden => code == forbidden; | ||
74 | + | ||
75 | + bool get isNotFound => code == notFound; | ||
76 | + | ||
77 | + bool get isServerError => | ||
78 | + between(internalServerError, networkConnectTimeoutError); | ||
79 | + | ||
80 | + bool between(int begin, int end) { | ||
81 | + return !connectionError && code >= begin && code <= end; | ||
82 | + } | ||
83 | + | ||
84 | + bool get isOk => between(200, 299); | ||
85 | + | ||
86 | + bool get hasError => !isOk; | ||
87 | +} |
lib/get_connect/http/src/utils/utils.dart
0 → 100644
1 | +import 'dart:async'; | ||
2 | +import 'dart:convert'; | ||
3 | + | ||
4 | +import '../../../../get_rx/src/rx_stream/rx_stream.dart'; | ||
5 | + | ||
6 | +import '../request/request.dart'; | ||
7 | + | ||
8 | +bool isTokenChar(int byte) { | ||
9 | + return byte > 31 && byte < 128 && !SEPARATOR_MAP[byte]; | ||
10 | +} | ||
11 | + | ||
12 | +bool isValueChar(int byte) { | ||
13 | + return (byte > 31 && byte < 128) || | ||
14 | + (byte == CharCode.SP) || | ||
15 | + (byte == CharCode.HT); | ||
16 | +} | ||
17 | + | ||
18 | +class CharCode { | ||
19 | + static const int HT = 9; | ||
20 | + static const int LF = 10; | ||
21 | + static const int CR = 13; | ||
22 | + static const int SP = 32; | ||
23 | + static const int COMMA = 44; | ||
24 | + static const int SLASH = 47; | ||
25 | + static const int ZERO = 48; | ||
26 | + static const int ONE = 49; | ||
27 | + static const int COLON = 58; | ||
28 | + static const int SEMI_COLON = 59; | ||
29 | +} | ||
30 | + | ||
31 | +const bool F = false; | ||
32 | + | ||
33 | +const String GET_BOUNDARY = '--get-boundary-'; | ||
34 | + | ||
35 | +const bool T = true; | ||
36 | +const SEPARATOR_MAP = [ | ||
37 | + F, F, F, F, F, F, F, F, F, T, F, F, F, F, F, F, F, F, F, F, F, F, F, F, // | ||
38 | + F, F, F, F, F, F, F, F, T, F, T, F, F, F, F, F, T, T, F, F, T, F, F, T, // | ||
39 | + F, F, F, F, F, F, F, F, F, F, T, T, T, T, T, T, T, F, F, F, F, F, F, F, // | ||
40 | + F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, T, T, T, F, F, // | ||
41 | + F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, // | ||
42 | + F, F, F, T, F, T, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, // | ||
43 | + F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, // | ||
44 | + F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, // | ||
45 | + F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, // | ||
46 | + F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, // | ||
47 | + F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F | ||
48 | +]; | ||
49 | + | ||
50 | +String validateField(String field) { | ||
51 | + for (var i = 0; i < field.length; i++) { | ||
52 | + if (!isTokenChar(field.codeUnitAt(i))) { | ||
53 | + throw FormatException( | ||
54 | + 'Invalid HTTP header field name: ${json.encode(field)}', field, i); | ||
55 | + } | ||
56 | + } | ||
57 | + return field.toLowerCase(); | ||
58 | +} | ||
59 | + | ||
60 | +BodyBytes toBodyBytes(Stream<List<int>> stream) { | ||
61 | + if (stream is BodyBytes) return stream; | ||
62 | + return BodyBytes(stream); | ||
63 | +} | ||
64 | + | ||
65 | +final _asciiOnly = RegExp(r'^[\x00-\x7F]+$'); | ||
66 | + | ||
67 | +/// Returns whether [string] is composed entirely of ASCII-compatible | ||
68 | +/// characters. | ||
69 | +bool isPlainAscii(String string) => _asciiOnly.hasMatch(string); | ||
70 | + | ||
71 | +String encodeMap(dynamic data, _Handler handler, {bool encode = true}) { | ||
72 | + return urlEncode(data, '', encode, handler).toString(); | ||
73 | +} | ||
74 | + | ||
75 | +StringBuffer urlEncode( | ||
76 | + dynamic sub, | ||
77 | + String path, | ||
78 | + bool encode, | ||
79 | + _Handler handler, | ||
80 | +) { | ||
81 | + var urlData = StringBuffer(''); | ||
82 | + var first = true; | ||
83 | + var leftBracket = '['; | ||
84 | + var rightBracket = ']'; | ||
85 | + | ||
86 | + if (encode) { | ||
87 | + leftBracket = '%5B'; | ||
88 | + rightBracket = '%5D'; | ||
89 | + } | ||
90 | + | ||
91 | + var encodeComponent = encode ? Uri.encodeQueryComponent : (e) => e; | ||
92 | + if (sub is List) { | ||
93 | + for (var i = 0; i < sub.length; i++) { | ||
94 | + urlEncode( | ||
95 | + sub[i], | ||
96 | + // ignore: lines_longer_than_80_chars | ||
97 | + '$path$leftBracket${(sub[i] is Map || sub[i] is List) ? i : ''}$rightBracket', | ||
98 | + encode, | ||
99 | + handler); | ||
100 | + } | ||
101 | + } else if (sub is Map) { | ||
102 | + sub.forEach((key, value) { | ||
103 | + if (path == '') { | ||
104 | + urlEncode( | ||
105 | + value, | ||
106 | + '${encodeComponent(key as String)}', | ||
107 | + encode, | ||
108 | + handler, | ||
109 | + ); | ||
110 | + } else { | ||
111 | + urlEncode( | ||
112 | + value, | ||
113 | + '$path$leftBracket${encodeComponent(key as String)}$rightBracket', | ||
114 | + encode, | ||
115 | + handler, | ||
116 | + ); | ||
117 | + } | ||
118 | + }); | ||
119 | + } else { | ||
120 | + var str = handler(path, sub); | ||
121 | + var isNotEmpty = str != null && (str as String).trim().isNotEmpty; | ||
122 | + if (!first && isNotEmpty) { | ||
123 | + urlData.write('&'); | ||
124 | + } | ||
125 | + first = false; | ||
126 | + if (isNotEmpty) { | ||
127 | + urlData.write(str); | ||
128 | + } | ||
129 | + } | ||
130 | + | ||
131 | + return urlData; | ||
132 | +} | ||
133 | + | ||
134 | +Future streamToFuture(Stream stream, GetStream sink) { | ||
135 | + var completer = Completer(); | ||
136 | + stream.listen(sink.add, | ||
137 | + onError: sink.addError, onDone: () => completer.complete()); | ||
138 | + return completer.future; | ||
139 | +} | ||
140 | + | ||
141 | +void stringToBytes(String string, GetStream stream) { | ||
142 | + stream.add(utf8.encode(string)); | ||
143 | +} | ||
144 | + | ||
145 | +void writeLine(GetStream stream) => stream.add([13, 10]); | ||
146 | + | ||
147 | +typedef _Handler = Function(String key, Object value); |
lib/get_connect/sockets/sockets.dart
0 → 100644
1 | +import 'dart:convert'; | ||
2 | + | ||
3 | +class Close { | ||
4 | + final String message; | ||
5 | + final int reason; | ||
6 | + | ||
7 | + Close(this.message, this.reason); | ||
8 | + | ||
9 | + @override | ||
10 | + String toString() { | ||
11 | + return 'Closed by server [$reason => $message]!'; | ||
12 | + } | ||
13 | +} | ||
14 | + | ||
15 | +typedef OpenSocket = void Function(); | ||
16 | + | ||
17 | +typedef CloseSocket = void Function(Close); | ||
18 | + | ||
19 | +typedef MessageSocket = void Function(dynamic val); | ||
20 | + | ||
21 | +class SocketNotifier { | ||
22 | + var _onMessages = <MessageSocket>[]; | ||
23 | + var _onEvents = <String, MessageSocket>{}; | ||
24 | + var _onCloses = <CloseSocket>[]; | ||
25 | + var _onErrors = <CloseSocket>[]; | ||
26 | + | ||
27 | + OpenSocket open; | ||
28 | + | ||
29 | + void addMessages(MessageSocket socket) { | ||
30 | + _onMessages.add((socket)); | ||
31 | + } | ||
32 | + | ||
33 | + void addEvents(String event, MessageSocket socket) { | ||
34 | + _onEvents[event] = socket; | ||
35 | + } | ||
36 | + | ||
37 | + void addCloses(CloseSocket socket) { | ||
38 | + _onCloses.add(socket); | ||
39 | + } | ||
40 | + | ||
41 | + void addErrors(CloseSocket socket) { | ||
42 | + _onErrors.add((socket)); | ||
43 | + } | ||
44 | + | ||
45 | + void notifyData(dynamic data) { | ||
46 | + for (var item in _onMessages) { | ||
47 | + item(data); | ||
48 | + } | ||
49 | + if (data is String) { | ||
50 | + _tryOn(data); | ||
51 | + } | ||
52 | + } | ||
53 | + | ||
54 | + void notifyClose(Close err) { | ||
55 | + for (var item in _onCloses) { | ||
56 | + item(err); | ||
57 | + } | ||
58 | + } | ||
59 | + | ||
60 | + void notifyError(Close err) { | ||
61 | + // rooms.removeWhere((key, value) => value.contains(_ws)); | ||
62 | + for (var item in _onErrors) { | ||
63 | + item(err); | ||
64 | + } | ||
65 | + } | ||
66 | + | ||
67 | + void _tryOn(String message) { | ||
68 | + try { | ||
69 | + var msg = jsonDecode(message); | ||
70 | + final event = msg['type']; | ||
71 | + final data = msg['data']; | ||
72 | + if (_onEvents.containsKey(event)) { | ||
73 | + _onEvents[event](data); | ||
74 | + } | ||
75 | + } on Exception catch (_) { | ||
76 | + return; | ||
77 | + } | ||
78 | + } | ||
79 | + | ||
80 | + void dispose() { | ||
81 | + _onMessages = null; | ||
82 | + _onEvents = null; | ||
83 | + _onCloses = null; | ||
84 | + _onErrors = null; | ||
85 | + } | ||
86 | +} |
1 | +import 'dart:async'; | ||
2 | +import 'dart:convert'; | ||
3 | +// ignore: avoid_web_libraries_in_flutter | ||
4 | +import 'dart:html'; | ||
5 | + | ||
6 | +import '../../../get_core/get_core.dart'; | ||
7 | + | ||
8 | +import 'socket_notifier.dart'; | ||
9 | + | ||
10 | +enum ConnectionStatus { | ||
11 | + connecting, | ||
12 | + connected, | ||
13 | + closed, | ||
14 | +} | ||
15 | + | ||
16 | +class BaseWebSocket { | ||
17 | + String url; | ||
18 | + WebSocket socket; | ||
19 | + SocketNotifier socketNotifier = SocketNotifier(); | ||
20 | + Duration ping; | ||
21 | + bool isDisposed = false; | ||
22 | + | ||
23 | + BaseWebSocket(this.url, {this.ping = const Duration(seconds: 5)}) { | ||
24 | + url = url.startsWith('https') | ||
25 | + ? url.replaceAll('https:', 'wss:') | ||
26 | + : url.replaceAll('http:', 'ws:'); | ||
27 | + } | ||
28 | + ConnectionStatus connectionStatus; | ||
29 | + Timer _t; | ||
30 | + | ||
31 | + void connect() { | ||
32 | + try { | ||
33 | + connectionStatus = ConnectionStatus.connecting; | ||
34 | + socket = WebSocket(url); | ||
35 | + socket.onOpen.listen((e) { | ||
36 | + socketNotifier?.open(); | ||
37 | + _t = Timer?.periodic(ping, (t) { | ||
38 | + socket.send(''); | ||
39 | + }); | ||
40 | + connectionStatus = ConnectionStatus.connected; | ||
41 | + }); | ||
42 | + | ||
43 | + socket.onMessage.listen((event) { | ||
44 | + socketNotifier.notifyData(event.data); | ||
45 | + }); | ||
46 | + | ||
47 | + socket.onClose.listen((e) { | ||
48 | + _t?.cancel(); | ||
49 | + | ||
50 | + connectionStatus = ConnectionStatus.closed; | ||
51 | + socketNotifier.notifyClose(Close(e.reason, e.code)); | ||
52 | + }); | ||
53 | + socket.onError.listen((event) { | ||
54 | + _t?.cancel(); | ||
55 | + socketNotifier.notifyError(Close(event.toString(), 0)); | ||
56 | + connectionStatus = ConnectionStatus.closed; | ||
57 | + }); | ||
58 | + } on Exception catch (e) { | ||
59 | + _t?.cancel(); | ||
60 | + socketNotifier.notifyError(Close(e.toString(), 500)); | ||
61 | + connectionStatus = ConnectionStatus.closed; | ||
62 | + // close(500, e.toString()); | ||
63 | + } | ||
64 | + } | ||
65 | + | ||
66 | + // ignore: use_setters_to_change_properties | ||
67 | + void onOpen(OpenSocket fn) { | ||
68 | + socketNotifier.open = fn; | ||
69 | + } | ||
70 | + | ||
71 | + void onClose(CloseSocket fn) { | ||
72 | + socketNotifier.addCloses(fn); | ||
73 | + } | ||
74 | + | ||
75 | + void onError(CloseSocket fn) { | ||
76 | + socketNotifier.addErrors(fn); | ||
77 | + } | ||
78 | + | ||
79 | + void onMessage(MessageSocket fn) { | ||
80 | + socketNotifier.addMessages(fn); | ||
81 | + } | ||
82 | + | ||
83 | + void on(String event, MessageSocket message) { | ||
84 | + socketNotifier.addEvents(event, message); | ||
85 | + } | ||
86 | + | ||
87 | + void close([int status, String reason]) { | ||
88 | + if (socket != null) socket.close(status, reason); | ||
89 | + } | ||
90 | + | ||
91 | + void send(dynamic data) { | ||
92 | + if (connectionStatus == ConnectionStatus.closed) { | ||
93 | + connect(); | ||
94 | + } | ||
95 | + if (socket != null && socket.readyState == WebSocket.OPEN) { | ||
96 | + socket.send(data); | ||
97 | + } else { | ||
98 | + Get.log('WebSocket not connected, message $data not sent'); | ||
99 | + } | ||
100 | + } | ||
101 | + | ||
102 | + void emit(String event, dynamic data) { | ||
103 | + send(jsonEncode({'type': event, 'data': data})); | ||
104 | + } | ||
105 | + | ||
106 | + void dispose() { | ||
107 | + socketNotifier.dispose(); | ||
108 | + socketNotifier = null; | ||
109 | + isDisposed = true; | ||
110 | + } | ||
111 | +} |
lib/get_connect/sockets/src/sockets_io.dart
0 → 100644
1 | +import 'dart:async'; | ||
2 | +import 'dart:convert'; | ||
3 | +import 'dart:io'; | ||
4 | +import 'dart:math'; | ||
5 | + | ||
6 | +import '../../../get_core/get_core.dart'; | ||
7 | + | ||
8 | +import 'socket_notifier.dart'; | ||
9 | + | ||
10 | +enum ConnectionStatus { | ||
11 | + connecting, | ||
12 | + connected, | ||
13 | + closed, | ||
14 | +} | ||
15 | + | ||
16 | +class BaseWebSocket { | ||
17 | + String url; | ||
18 | + WebSocket socket; | ||
19 | + SocketNotifier socketNotifier = SocketNotifier(); | ||
20 | + bool isDisposed = false; | ||
21 | + BaseWebSocket(this.url, {this.ping = const Duration(seconds: 5)}); | ||
22 | + Duration ping; | ||
23 | + bool allowSelfSigned = true; | ||
24 | + | ||
25 | + ConnectionStatus connectionStatus; | ||
26 | + | ||
27 | + Future connect() async { | ||
28 | + if (isDisposed) { | ||
29 | + socketNotifier = SocketNotifier(); | ||
30 | + } | ||
31 | + try { | ||
32 | + connectionStatus = ConnectionStatus.connecting; | ||
33 | + socket = allowSelfSigned | ||
34 | + ? await _connectForSelfSignedCert(url) | ||
35 | + : await WebSocket.connect(url); | ||
36 | + | ||
37 | + socket.pingInterval = ping; | ||
38 | + socketNotifier?.open(); | ||
39 | + connectionStatus = ConnectionStatus.connected; | ||
40 | + | ||
41 | + socket.listen((data) { | ||
42 | + socketNotifier.notifyData(data); | ||
43 | + }, onError: (err) { | ||
44 | + socketNotifier.notifyError(Close(err.toString(), 1005)); | ||
45 | + }, onDone: () { | ||
46 | + connectionStatus = ConnectionStatus.closed; | ||
47 | + socketNotifier | ||
48 | + .notifyClose(Close('Connection Closed', socket.closeCode)); | ||
49 | + }, cancelOnError: true); | ||
50 | + return; | ||
51 | + } on SocketException catch (e) { | ||
52 | + connectionStatus = ConnectionStatus.closed; | ||
53 | + socketNotifier.notifyError(Close(e.osError.message, e.osError.errorCode)); | ||
54 | + return; | ||
55 | + } | ||
56 | + } | ||
57 | + | ||
58 | + // ignore: use_setters_to_change_properties | ||
59 | + void onOpen(OpenSocket fn) { | ||
60 | + socketNotifier.open = fn; | ||
61 | + } | ||
62 | + | ||
63 | + void onClose(CloseSocket fn) { | ||
64 | + socketNotifier.addCloses(fn); | ||
65 | + } | ||
66 | + | ||
67 | + void onError(CloseSocket fn) { | ||
68 | + socketNotifier.addErrors(fn); | ||
69 | + } | ||
70 | + | ||
71 | + void onMessage(MessageSocket fn) { | ||
72 | + socketNotifier.addMessages(fn); | ||
73 | + } | ||
74 | + | ||
75 | + void on(String event, MessageSocket message) { | ||
76 | + socketNotifier.addEvents(event, message); | ||
77 | + } | ||
78 | + | ||
79 | + void close([int status, String reason]) { | ||
80 | + if (socket != null) { | ||
81 | + socket.close(status, reason); | ||
82 | + } | ||
83 | + } | ||
84 | + | ||
85 | + void send(dynamic data) async { | ||
86 | + if (connectionStatus == ConnectionStatus.closed) { | ||
87 | + await connect(); | ||
88 | + } | ||
89 | + | ||
90 | + if (socket != null) { | ||
91 | + socket.add(data); | ||
92 | + } | ||
93 | + } | ||
94 | + | ||
95 | + void dispose() { | ||
96 | + socketNotifier.dispose(); | ||
97 | + socketNotifier = null; | ||
98 | + isDisposed = true; | ||
99 | + } | ||
100 | + | ||
101 | + void emit(String event, dynamic data) { | ||
102 | + send(jsonEncode({'type': event, 'data': data})); | ||
103 | + } | ||
104 | + | ||
105 | + Future<WebSocket> _connectForSelfSignedCert(String url) async { | ||
106 | + try { | ||
107 | + var r = Random(); | ||
108 | + var key = base64.encode(List<int>.generate(8, (_) => r.nextInt(255))); | ||
109 | + var client = HttpClient(context: SecurityContext()); | ||
110 | + client.badCertificateCallback = (cert, host, port) { | ||
111 | + Get.log( | ||
112 | + 'BaseWebSocket: Allow self-signed certificate => $host:$port. '); | ||
113 | + return true; | ||
114 | + }; | ||
115 | + | ||
116 | + var request = await client.getUrl(Uri.parse(url)); | ||
117 | + request.headers.add('Connection', 'Upgrade'); | ||
118 | + request.headers.add('Upgrade', 'websocket'); | ||
119 | + request.headers.add('Sec-WebSocket-Version', '13'); | ||
120 | + request.headers.add('Sec-WebSocket-Key', key.toLowerCase()); | ||
121 | + | ||
122 | + var response = await request.close(); | ||
123 | + // ignore: close_sinks | ||
124 | + var socket = await response.detachSocket(); | ||
125 | + var webSocket = WebSocket.fromUpgradedSocket( | ||
126 | + socket, | ||
127 | + serverSide: false, | ||
128 | + ); | ||
129 | + | ||
130 | + return webSocket; | ||
131 | + } on Exception catch (_) { | ||
132 | + rethrow; | ||
133 | + } | ||
134 | + } | ||
135 | +} |
1 | +class BaseWebSocket { | ||
2 | + String url; | ||
3 | + Duration ping; | ||
4 | + BaseWebSocket(this.url, {this.ping = const Duration(seconds: 5)}) { | ||
5 | + throw 'To use sockets you need dart:io or dart:html'; | ||
6 | + } | ||
7 | + | ||
8 | + void close([int status, String reason]) { | ||
9 | + throw 'To use sockets you need dart:io or dart:html'; | ||
10 | + } | ||
11 | +} |
@@ -221,7 +221,7 @@ class GetInstance { | @@ -221,7 +221,7 @@ class GetInstance { | ||
221 | S _startController<S>({String tag}) { | 221 | S _startController<S>({String tag}) { |
222 | final key = _getKey(S, tag); | 222 | final key = _getKey(S, tag); |
223 | final i = _singl[key].getDependency() as S; | 223 | final i = _singl[key].getDependency() as S; |
224 | - if (i is GetLifeCycle) { | 224 | + if (i is GetLifeCycleBase) { |
225 | if (i.onStart != null) { | 225 | if (i.onStart != null) { |
226 | i.onStart(); | 226 | i.onStart(); |
227 | Get.log('"$key" has been initialized'); | 227 | Get.log('"$key" has been initialized'); |
@@ -115,7 +115,8 @@ class GetObserver extends NavigatorObserver { | @@ -115,7 +115,8 @@ class GetObserver extends NavigatorObserver { | ||
115 | value.removed = ''; | 115 | value.removed = ''; |
116 | value.previous = _extractRouteName(previousRoute) ?? ''; | 116 | value.previous = _extractRouteName(previousRoute) ?? ''; |
117 | value.isSnackbar = newRoute.isSnackbar ? true : value.isSnackbar ?? false; | 117 | value.isSnackbar = newRoute.isSnackbar ? true : value.isSnackbar ?? false; |
118 | - value.isBottomSheet = newRoute.isBottomSheet ? true : value.isBottomSheet ?? false; | 118 | + value.isBottomSheet = |
119 | + newRoute.isBottomSheet ? true : value.isBottomSheet ?? false; | ||
119 | value.isDialog = newRoute.isDialog ? true : value.isDialog ?? false; | 120 | value.isDialog = newRoute.isDialog ? true : value.isDialog ?? false; |
120 | }); | 121 | }); |
121 | 122 | ||
@@ -154,7 +155,8 @@ class GetObserver extends NavigatorObserver { | @@ -154,7 +155,8 @@ class GetObserver extends NavigatorObserver { | ||
154 | value.removed = ''; | 155 | value.removed = ''; |
155 | value.previous = newRoute.name ?? ''; | 156 | value.previous = newRoute.name ?? ''; |
156 | value.isSnackbar = currentRoute.isSnackbar ? false : value.isSnackbar; | 157 | value.isSnackbar = currentRoute.isSnackbar ? false : value.isSnackbar; |
157 | - value.isBottomSheet = currentRoute.isBottomSheet ? false : value.isBottomSheet; | 158 | + value.isBottomSheet = |
159 | + currentRoute.isBottomSheet ? false : value.isBottomSheet; | ||
158 | value.isDialog = currentRoute.isDialog ? false : value.isDialog; | 160 | value.isDialog = currentRoute.isDialog ? false : value.isDialog; |
159 | }); | 161 | }); |
160 | 162 | ||
@@ -184,7 +186,8 @@ class GetObserver extends NavigatorObserver { | @@ -184,7 +186,8 @@ class GetObserver extends NavigatorObserver { | ||
184 | value.removed = ''; | 186 | value.removed = ''; |
185 | value.previous = '$oldName'; | 187 | value.previous = '$oldName'; |
186 | value.isSnackbar = currentRoute.isSnackbar ? false : value.isSnackbar; | 188 | value.isSnackbar = currentRoute.isSnackbar ? false : value.isSnackbar; |
187 | - value.isBottomSheet = currentRoute.isBottomSheet ? false : value.isBottomSheet; | 189 | + value.isBottomSheet = |
190 | + currentRoute.isBottomSheet ? false : value.isBottomSheet; | ||
188 | value.isDialog = currentRoute.isDialog ? false : value.isDialog; | 191 | value.isDialog = currentRoute.isDialog ? false : value.isDialog; |
189 | }); | 192 | }); |
190 | 193 | ||
@@ -205,7 +208,8 @@ class GetObserver extends NavigatorObserver { | @@ -205,7 +208,8 @@ class GetObserver extends NavigatorObserver { | ||
205 | value.removed = routeName ?? ''; | 208 | value.removed = routeName ?? ''; |
206 | value.previous = routeName ?? ''; | 209 | value.previous = routeName ?? ''; |
207 | value.isSnackbar = currentRoute.isSnackbar ? false : value.isSnackbar; | 210 | value.isSnackbar = currentRoute.isSnackbar ? false : value.isSnackbar; |
208 | - value.isBottomSheet = currentRoute.isBottomSheet ? false : value.isBottomSheet; | 211 | + value.isBottomSheet = |
212 | + currentRoute.isBottomSheet ? false : value.isBottomSheet; | ||
209 | value.isDialog = currentRoute.isDialog ? false : value.isDialog; | 213 | value.isDialog = currentRoute.isDialog ? false : value.isDialog; |
210 | }); | 214 | }); |
211 | 215 |
@@ -3,7 +3,6 @@ part of rx_types; | @@ -3,7 +3,6 @@ part of rx_types; | ||
3 | /// global object that registers against `GetX` and `Obx`, and allows the | 3 | /// global object that registers against `GetX` and `Obx`, and allows the |
4 | /// reactivity | 4 | /// reactivity |
5 | /// of those `Widgets` and Rx values. | 5 | /// of those `Widgets` and Rx values. |
6 | -RxInterface getObs; | ||
7 | 6 | ||
8 | mixin RxObjectMixin<T> on NotifyManager<T> { | 7 | mixin RxObjectMixin<T> on NotifyManager<T> { |
9 | T _value; | 8 | T _value; |
@@ -104,8 +103,8 @@ mixin RxObjectMixin<T> on NotifyManager<T> { | @@ -104,8 +103,8 @@ mixin RxObjectMixin<T> on NotifyManager<T> { | ||
104 | 103 | ||
105 | /// Returns the current [value] | 104 | /// Returns the current [value] |
106 | T get value { | 105 | T get value { |
107 | - if (getObs != null) { | ||
108 | - getObs.addListener(subject); | 106 | + if (RxInterface.proxy != null) { |
107 | + RxInterface.proxy.addListener(subject); | ||
109 | } | 108 | } |
110 | return _value; | 109 | return _value; |
111 | } | 110 | } |
@@ -15,6 +15,8 @@ abstract class RxInterface<T> { | @@ -15,6 +15,8 @@ abstract class RxInterface<T> { | ||
15 | /// Close the Rx Variable | 15 | /// Close the Rx Variable |
16 | void close(); | 16 | void close(); |
17 | 17 | ||
18 | + static RxInterface proxy; | ||
19 | + | ||
18 | /// Calls [callback] with current value, when the value changes. | 20 | /// Calls [callback] with current value, when the value changes. |
19 | StreamSubscription<T> listen(void Function(T event) onData, | 21 | StreamSubscription<T> listen(void Function(T event) onData, |
20 | {Function onError, void Function() onDone, bool cancelOnError}); | 22 | {Function onError, void Function() onDone, bool cancelOnError}); |
@@ -87,8 +87,8 @@ class RxList<E> extends ListMixin<E> | @@ -87,8 +87,8 @@ class RxList<E> extends ListMixin<E> | ||
87 | @override | 87 | @override |
88 | @protected | 88 | @protected |
89 | List<E> get value { | 89 | List<E> get value { |
90 | - if (getObs != null) { | ||
91 | - getObs.addListener(subject); | 90 | + if (RxInterface.proxy != null) { |
91 | + RxInterface.proxy.addListener(subject); | ||
92 | } | 92 | } |
93 | return _value; | 93 | return _value; |
94 | } | 94 | } |
@@ -39,8 +39,8 @@ class RxMap<K, V> extends MapMixin<K, V> | @@ -39,8 +39,8 @@ class RxMap<K, V> extends MapMixin<K, V> | ||
39 | @override | 39 | @override |
40 | @protected | 40 | @protected |
41 | Map<K, V> get value { | 41 | Map<K, V> get value { |
42 | - if (getObs != null) { | ||
43 | - getObs.addListener(subject); | 42 | + if (RxInterface.proxy != null) { |
43 | + RxInterface.proxy.addListener(subject); | ||
44 | } | 44 | } |
45 | return _value; | 45 | return _value; |
46 | } | 46 | } |
@@ -61,8 +61,8 @@ class RxSet<E> extends SetMixin<E> | @@ -61,8 +61,8 @@ class RxSet<E> extends SetMixin<E> | ||
61 | @override | 61 | @override |
62 | @protected | 62 | @protected |
63 | Set<E> get value { | 63 | Set<E> get value { |
64 | - if (getObs != null) { | ||
65 | - getObs.addListener(subject); | 64 | + if (RxInterface.proxy != null) { |
65 | + RxInterface.proxy.addListener(subject); | ||
66 | } | 66 | } |
67 | return _value; | 67 | return _value; |
68 | } | 68 | } |
@@ -113,8 +113,8 @@ class GetXState<T extends DisposableInterface> extends State<GetX<T>> { | @@ -113,8 +113,8 @@ class GetXState<T extends DisposableInterface> extends State<GetX<T>> { | ||
113 | } | 113 | } |
114 | 114 | ||
115 | Widget get notifyChildren { | 115 | Widget get notifyChildren { |
116 | - final observer = getObs; | ||
117 | - getObs = _observer; | 116 | + final observer = RxInterface.proxy; |
117 | + RxInterface.proxy = _observer; | ||
118 | final result = widget.builder(controller); | 118 | final result = widget.builder(controller); |
119 | if (!_observer.canUpdate) { | 119 | if (!_observer.canUpdate) { |
120 | throw """ | 120 | throw """ |
@@ -126,7 +126,7 @@ class GetXState<T extends DisposableInterface> extends State<GetX<T>> { | @@ -126,7 +126,7 @@ class GetXState<T extends DisposableInterface> extends State<GetX<T>> { | ||
126 | If you need to update a parent widget and a child widget, wrap each one in an Obx/GetX. | 126 | If you need to update a parent widget and a child widget, wrap each one in an Obx/GetX. |
127 | """; | 127 | """; |
128 | } | 128 | } |
129 | - getObs = observer; | 129 | + RxInterface.proxy = observer; |
130 | return result; | 130 | return result; |
131 | } | 131 | } |
132 | 132 |
@@ -41,8 +41,8 @@ class _ObxState extends State<ObxWidget> { | @@ -41,8 +41,8 @@ class _ObxState extends State<ObxWidget> { | ||
41 | } | 41 | } |
42 | 42 | ||
43 | Widget get notifyChilds { | 43 | Widget get notifyChilds { |
44 | - final observer = getObs; | ||
45 | - getObs = _observer; | 44 | + final observer = RxInterface.proxy; |
45 | + RxInterface.proxy = _observer; | ||
46 | final result = widget.build(); | 46 | final result = widget.build(); |
47 | if (!_observer.canUpdate) { | 47 | if (!_observer.canUpdate) { |
48 | throw """ | 48 | throw """ |
@@ -54,7 +54,7 @@ class _ObxState extends State<ObxWidget> { | @@ -54,7 +54,7 @@ class _ObxState extends State<ObxWidget> { | ||
54 | If you need to update a parent widget and a child widget, wrap each one in an Obx/GetX. | 54 | If you need to update a parent widget and a child widget, wrap each one in an Obx/GetX. |
55 | """; | 55 | """; |
56 | } | 56 | } |
57 | - getObs = observer; | 57 | + RxInterface.proxy = observer; |
58 | return result; | 58 | return result; |
59 | } | 59 | } |
60 | 60 |
@@ -59,6 +59,12 @@ extension ContextExtensionss on BuildContext { | @@ -59,6 +59,12 @@ extension ContextExtensionss on BuildContext { | ||
59 | /// similar to [MediaQuery.of(context).padding] | 59 | /// similar to [MediaQuery.of(context).padding] |
60 | ThemeData get theme => Theme.of(this); | 60 | ThemeData get theme => Theme.of(this); |
61 | 61 | ||
62 | + /// Check if dark mode theme is enable | ||
63 | + bool get isDarkMode => (theme.brightness == Brightness.dark); | ||
64 | + | ||
65 | + /// give access to Theme.of(context).iconTheme.color | ||
66 | + Color get iconColor => theme.iconTheme.color; | ||
67 | + | ||
62 | /// similar to [MediaQuery.of(context).padding] | 68 | /// similar to [MediaQuery.of(context).padding] |
63 | TextTheme get textTheme => Theme.of(this).textTheme; | 69 | TextTheme get textTheme => Theme.of(this).textTheme; |
64 | 70 |
-
Please register or login to post a comment