From 2ffdfe3b8d23cb22d65fb760c5bbb135ff135cb5 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 13 Oct 2026 01:59:36 +0000 Subject: [PATCH 01/22] Migrate to codelessly_api 5 and upstream JSON codegen The SDK no longer resolved on current Dart: codelessly_json_serializable needs the removed _macros package. Switch to json_annotation and json_serializable, depend on codelessly_api ^5.0.0 (overridden to the API's development branch until it is published), and upgrade the remaining dependencies to their latest majors. - EquatableMixin -> Equatable (equatable 3). - Import DateTimeConverter from codelessly_api. - Drop a `final` parameter modifier that Dart 3.13 rejects. - Map the enum values added in codelessly_api 5 to their Flutter counterparts, including FilledButton and RoundedSuperellipseBorder. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01A67XoQYsLVvoMtiKcXXUxY --- CHANGELOG.md | 12 + analysis_options.yaml | 4 + build.yaml | 2 +- example/analysis_options.yaml | 9 + .../flutter/generated_plugin_registrant.cc | 8 - example/linux/flutter/generated_plugins.cmake | 3 +- .../Flutter/GeneratedPluginRegistrant.swift | 24 - example/pubspec.yaml | 13 +- .../flutter/generated_plugin_registrant.cc | 12 - .../windows/flutter/generated_plugins.cmake | 5 +- lib/src/codelessly_config.dart | 2 +- lib/src/model/asset_model.dart | 5 +- lib/src/model/asset_model.g.dart | 53 +- lib/src/model/auth_data.dart | 3 +- lib/src/model/auth_data.g.dart | 60 +-- lib/src/model/custom_component.dart | 6 +- lib/src/model/custom_component.g.dart | 165 +++--- lib/src/model/font_family.dart | 2 +- lib/src/model/font_family.g.dart | 73 ++- lib/src/model/model_http_request.dart | 2 +- lib/src/model/model_http_request.g.dart | 227 ++++---- lib/src/model/privacy_base.dart | 4 +- lib/src/model/privacy_base.g.dart | 16 +- lib/src/model/sdk_publish_model.dart | 4 +- lib/src/model/sdk_publish_model.g.dart | 484 ++++++++---------- lib/src/transformers/node_transformer.dart | 2 +- .../passive_button_transformer.dart | 52 ++ .../transformers/transformer_functions.dart | 50 ++ lib/src/transformers/utils/dash_path.dart | 2 +- .../utils/indexed_item_provider.dart | 2 +- lib/src/ui/codelessly_context.dart | 2 +- lib/src/utils/extensions.dart | 17 + lib/src/utils/utils.dart | 2 +- pubspec.yaml | 33 +- 34 files changed, 674 insertions(+), 686 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dde3003f..0d4f7c02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +## Unreleased + +- Require Dart 3.13 and Flutter 3.47. +- Migrate to codelessly_api 5.0.0, json_annotation and json_serializable, + replacing the forked codegen that no longer builds on current Dart. +- Upgrade dependencies, including Firebase, google_fonts, equatable 3 and + json_path. +- Render the new API values: FilledButton and FilledButton.tonal buttons, + RoundedSuperellipseBorder shapes, TextAlign start/end, the + `endContained` FAB location, `webSearch`/`twitter` keyboards, and the + `onUnfocus`/`onUserInteractionIfError` autovalidate modes. + ## 6.2.0 - Create dynamic settings architecture. diff --git a/analysis_options.yaml b/analysis_options.yaml index fc71dc38..2d41ad3f 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -13,6 +13,7 @@ analyzer: exclude: - lib/**/*.pb*.dart - lib/**/*.g.dart + - build/** errors: # treat missing required parameters as a warning (not a hint) missing_required_param: warning @@ -20,6 +21,9 @@ analyzer: missing_return: warning # allow having TODOs in the code todo: ignore + # Models are mutable by design. Equatable 3 marks itself @immutable, + # which would flag every one of them. + must_be_immutable: ignore linter: rules: diff --git a/build.yaml b/build.yaml index 21c94274..4d315687 100644 --- a/build.yaml +++ b/build.yaml @@ -4,7 +4,7 @@ targets: exclude: - example/** builders: - codelessly_json_serializable: + json_serializable: options: # The other properties are the same as default (https://pub.dev/packages/json_serializable#build-configuration). # explicit_to_json must be true for nested properties to work (like a Paint inside a Frame, toJson must be called in both) diff --git a/example/analysis_options.yaml b/example/analysis_options.yaml index ae08714c..f78ea2dc 100644 --- a/example/analysis_options.yaml +++ b/example/analysis_options.yaml @@ -7,6 +7,15 @@ # The following line activates a set of recommended lints for Flutter apps, # packages, and plugins designed to encourage good coding practices. +analyzer: + exclude: + - build/** + - android/** + - ios/** + - web/** + - windows/** + - macos/** + - linux/** include: package:flutter_lints/flutter.yaml linter: diff --git a/example/linux/flutter/generated_plugin_registrant.cc b/example/linux/flutter/generated_plugin_registrant.cc index fcafa493..f6f23bfe 100644 --- a/example/linux/flutter/generated_plugin_registrant.cc +++ b/example/linux/flutter/generated_plugin_registrant.cc @@ -6,17 +6,9 @@ #include "generated_plugin_registrant.h" -#include -#include #include void fl_register_plugins(FlPluginRegistry* registry) { - g_autoptr(FlPluginRegistrar) irondash_engine_context_registrar = - fl_plugin_registry_get_registrar_for_plugin(registry, "IrondashEngineContextPlugin"); - irondash_engine_context_plugin_register_with_registrar(irondash_engine_context_registrar); - g_autoptr(FlPluginRegistrar) super_native_extensions_registrar = - fl_plugin_registry_get_registrar_for_plugin(registry, "SuperNativeExtensionsPlugin"); - super_native_extensions_plugin_register_with_registrar(super_native_extensions_registrar); g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); diff --git a/example/linux/flutter/generated_plugins.cmake b/example/linux/flutter/generated_plugins.cmake index f6cb52d0..df8d2f72 100644 --- a/example/linux/flutter/generated_plugins.cmake +++ b/example/linux/flutter/generated_plugins.cmake @@ -3,12 +3,11 @@ # list(APPEND FLUTTER_PLUGIN_LIST - irondash_engine_context - super_native_extensions url_launcher_linux ) list(APPEND FLUTTER_FFI_PLUGIN_LIST + jni ) set(PLUGIN_BUNDLED_LIBRARIES) diff --git a/example/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift index c4dc4aa7..6bfb9205 100644 --- a/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,41 +6,17 @@ import FlutterMacOS import Foundation import cloud_firestore -import cloud_functions -import connectivity_plus -import device_info_plus -import file_picker -import firebase_analytics import firebase_auth import firebase_core -import firebase_crashlytics -import firebase_database -import firebase_storage -import google_sign_in_ios -import irondash_engine_context -import path_provider_foundation import shared_preferences_foundation -import super_native_extensions import url_launcher_macos import webview_flutter_wkwebview func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FLTFirebaseFirestorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseFirestorePlugin")) - FLTFirebaseFunctionsPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseFunctionsPlugin")) - ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin")) - DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) - FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) - FLTFirebaseAnalyticsPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAnalyticsPlugin")) FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin")) FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) - FLTFirebaseCrashlyticsPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCrashlyticsPlugin")) - FLTFirebaseDatabasePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseDatabasePlugin")) - FLTFirebaseStoragePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseStoragePlugin")) - FLTGoogleSignInPlugin.register(with: registry.registrar(forPlugin: "FLTGoogleSignInPlugin")) - IrondashEngineContextPlugin.register(with: registry.registrar(forPlugin: "IrondashEngineContextPlugin")) - PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) - SuperNativeExtensionsPlugin.register(with: registry.registrar(forPlugin: "SuperNativeExtensionsPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) WebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "WebViewFlutterPlugin")) } diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 2d4e8793..500f7def 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -6,8 +6,8 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 environment: - sdk: ">=3.6.0 <4.0.0" - flutter: ">=3.24.0" + sdk: ^3.13.0 + flutter: ">=3.47.0" dependencies: flutter: @@ -19,7 +19,14 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^5.0.0 + flutter_lints: ^6.0.0 flutter: uses-material-design: true + +# codelessly_api 5.0.0 is not on pub.dev yet. Remove once it is published. +dependency_overrides: + codelessly_api: + git: + url: https://github.com/Codelessly/CodelesslyAPI.git + ref: claude/hopeful-brown-c8o8x7 diff --git a/example/windows/flutter/generated_plugin_registrant.cc b/example/windows/flutter/generated_plugin_registrant.cc index c437ccec..a3c4d169 100644 --- a/example/windows/flutter/generated_plugin_registrant.cc +++ b/example/windows/flutter/generated_plugin_registrant.cc @@ -7,29 +7,17 @@ #include "generated_plugin_registrant.h" #include -#include #include #include -#include -#include -#include #include void RegisterPlugins(flutter::PluginRegistry* registry) { CloudFirestorePluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("CloudFirestorePluginCApi")); - ConnectivityPlusWindowsPluginRegisterWithRegistrar( - registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin")); FirebaseAuthPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("FirebaseAuthPluginCApi")); FirebaseCorePluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("FirebaseCorePluginCApi")); - FirebaseStoragePluginCApiRegisterWithRegistrar( - registry->GetRegistrarForPlugin("FirebaseStoragePluginCApi")); - IrondashEngineContextPluginCApiRegisterWithRegistrar( - registry->GetRegistrarForPlugin("IrondashEngineContextPluginCApi")); - SuperNativeExtensionsPluginCApiRegisterWithRegistrar( - registry->GetRegistrarForPlugin("SuperNativeExtensionsPluginCApi")); UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/example/windows/flutter/generated_plugins.cmake b/example/windows/flutter/generated_plugins.cmake index 932bb087..9cebacbc 100644 --- a/example/windows/flutter/generated_plugins.cmake +++ b/example/windows/flutter/generated_plugins.cmake @@ -4,16 +4,13 @@ list(APPEND FLUTTER_PLUGIN_LIST cloud_firestore - connectivity_plus firebase_auth firebase_core - firebase_storage - irondash_engine_context - super_native_extensions url_launcher_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST + jni ) set(PLUGIN_BUNDLED_LIBRARIES) diff --git a/lib/src/codelessly_config.dart b/lib/src/codelessly_config.dart index 534d763f..b70d221f 100644 --- a/lib/src/codelessly_config.dart +++ b/lib/src/codelessly_config.dart @@ -6,7 +6,7 @@ import 'firebase_options_prod.dart'; import 'model/publish_source.dart'; /// Holds initialization configuration options for the SDK. -class CodelesslyConfig with EquatableMixin { +class CodelesslyConfig with Equatable { /// The SDK auth token used to authenticate the SDK with the Codelessly /// servers. final String authToken; diff --git a/lib/src/model/asset_model.dart b/lib/src/model/asset_model.dart index 199a5d90..5a3cb14f 100644 --- a/lib/src/model/asset_model.dart +++ b/lib/src/model/asset_model.dart @@ -1,12 +1,13 @@ -import 'package:codelessly_json_annotation/codelessly_json_annotation.dart'; +import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; +import 'package:codelessly_api/codelessly_api.dart'; part 'asset_model.g.dart'; /// Defines an asset that is utilized throughout node data as an id and /// looked up for information about the asset based on that id. @JsonSerializable() -class AssetModel with EquatableMixin { +class AssetModel with Equatable { /// The id of the asset. final String id; diff --git a/lib/src/model/asset_model.g.dart b/lib/src/model/asset_model.g.dart index f2023df7..7b95731f 100644 --- a/lib/src/model/asset_model.g.dart +++ b/lib/src/model/asset_model.g.dart @@ -7,37 +7,24 @@ part of 'asset_model.dart'; // ************************************************************************** AssetModel _$AssetModelFromJson(Map json) => AssetModel( - id: json['id'] as String, - name: json['name'] as String, - url: json['url'] as String, - blurHash: json['blurHash'] as String? ?? '', - sourceWidth: (json['sourceWidth'] as num).toDouble(), - sourceHeight: (json['sourceHeight'] as num).toDouble(), - createdAt: const DateTimeConverter() - .fromJson((json['createdAt'] as num?)?.toInt()), - ); + id: json['id'] as String, + name: json['name'] as String, + url: json['url'] as String, + blurHash: json['blurHash'] as String? ?? '', + sourceWidth: (json['sourceWidth'] as num).toDouble(), + sourceHeight: (json['sourceHeight'] as num).toDouble(), + createdAt: const DateTimeConverter().fromJson( + (json['createdAt'] as num?)?.toInt(), + ), +); -Map _$AssetModelToJson(AssetModel instance) { - final val = { - 'id': instance.id, - 'name': instance.name, - 'url': instance.url, - 'sourceWidth': instance.sourceWidth, - 'sourceHeight': instance.sourceHeight, - }; - - void writeNotNull( - String key, dynamic value, dynamic jsonValue, dynamic defaultValue) { - final bool serialize = - shouldSerialize(key, value, jsonValue, defaultValue, true); - - if (serialize) { - val[key] = jsonValue; - } - } - - writeNotNull('createdAt', instance.createdAt, - const DateTimeConverter().toJson(instance.createdAt), null); - writeNotNull('blurHash', instance.blurHash, instance.blurHash, ''); - return val; -} +Map _$AssetModelToJson(AssetModel instance) => + { + 'id': instance.id, + 'name': instance.name, + 'url': instance.url, + 'sourceWidth': instance.sourceWidth, + 'sourceHeight': instance.sourceHeight, + 'createdAt': ?const DateTimeConverter().toJson(instance.createdAt), + 'blurHash': instance.blurHash, + }; diff --git a/lib/src/model/auth_data.dart b/lib/src/model/auth_data.dart index 87e40bc4..d2399c80 100644 --- a/lib/src/model/auth_data.dart +++ b/lib/src/model/auth_data.dart @@ -1,4 +1,5 @@ -import 'package:codelessly_json_annotation/codelessly_json_annotation.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:codelessly_api/codelessly_api.dart'; import '../../codelessly_sdk.dart'; diff --git a/lib/src/model/auth_data.g.dart b/lib/src/model/auth_data.g.dart index 5ad023cd..be53b09d 100644 --- a/lib/src/model/auth_data.g.dart +++ b/lib/src/model/auth_data.g.dart @@ -7,41 +7,27 @@ part of 'auth_data.dart'; // ************************************************************************** AuthData _$AuthDataFromJson(Map json) => AuthData( - authToken: json['authToken'] as String, - projectId: json['projectId'] as String, - timestamp: const DateTimeConverter() - .fromJson((json['timestamp'] as num?)?.toInt()), - isTemplate: json['isTemplate'] as bool? ?? false, - owner: json['owner'] as String, - teams: (json['teams'] as List?)?.map((e) => e as String).toSet(), - users: (json['users'] as List?)?.map((e) => e as String).toSet(), - roles: (json['roles'] as Map).map( - (k, e) => MapEntry(k as String, Role.fromJson(e as String)), - ), - ); + authToken: json['authToken'] as String, + projectId: json['projectId'] as String, + timestamp: const DateTimeConverter().fromJson( + (json['timestamp'] as num?)?.toInt(), + ), + isTemplate: json['isTemplate'] as bool? ?? false, + owner: json['owner'] as String, + teams: (json['teams'] as List?)?.map((e) => e as String).toSet(), + users: (json['users'] as List?)?.map((e) => e as String).toSet(), + roles: (json['roles'] as Map).map( + (k, e) => MapEntry(k as String, Role.fromJson(e as String)), + ), +); -Map _$AuthDataToJson(AuthData instance) { - final val = { - 'owner': instance.owner, - 'users': instance.users.toList(), - 'roles': instance.roles.map((k, e) => MapEntry(k, e.toJson())), - 'teams': instance.teams.toList(), - 'authToken': instance.authToken, - 'projectId': instance.projectId, - }; - - void writeNotNull( - String key, dynamic value, dynamic jsonValue, dynamic defaultValue) { - final bool serialize = - shouldSerialize(key, value, jsonValue, defaultValue, true); - - if (serialize) { - val[key] = jsonValue; - } - } - - writeNotNull('isTemplate', instance.isTemplate, instance.isTemplate, false); - writeNotNull('timestamp', instance.timestamp, - const DateTimeConverter().toJson(instance.timestamp), null); - return val; -} +Map _$AuthDataToJson(AuthData instance) => { + 'owner': instance.owner, + 'users': instance.users.toList(), + 'roles': instance.roles.map((k, e) => MapEntry(k, e.toJson())), + 'teams': instance.teams.toList(), + 'authToken': instance.authToken, + 'projectId': instance.projectId, + 'isTemplate': instance.isTemplate, + 'timestamp': ?const DateTimeConverter().toJson(instance.timestamp), +}; diff --git a/lib/src/model/custom_component.dart b/lib/src/model/custom_component.dart index 88263ef8..13168f94 100644 --- a/lib/src/model/custom_component.dart +++ b/lib/src/model/custom_component.dart @@ -3,14 +3,14 @@ // license that can be found in the LICENSE.md file. import 'package:codelessly_api/codelessly_api.dart'; -import 'package:codelessly_json_annotation/codelessly_json_annotation.dart'; +import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; part 'custom_component.g.dart'; /// A model to hold custom component data. @JsonSerializable() -class CustomComponent with EquatableMixin, SerializableMixin { +class CustomComponent with Equatable, SerializableMixin { /// Unique identifier for the component. final String id; @@ -111,7 +111,7 @@ class CustomComponent with EquatableMixin, SerializableMixin { /// Holds component's node data and containing rect size data. @JsonSerializable() -class ComponentData with EquatableMixin, SerializableMixin { +class ComponentData with Equatable, SerializableMixin { /// Width of the containing rect for [nodes]. final double width; diff --git a/lib/src/model/custom_component.g.dart b/lib/src/model/custom_component.g.dart index d2acbba7..3c122a1d 100644 --- a/lib/src/model/custom_component.g.dart +++ b/lib/src/model/custom_component.g.dart @@ -7,102 +7,77 @@ part of 'custom_component.dart'; // ************************************************************************** CustomComponent _$CustomComponentFromJson(Map json) => CustomComponent( - id: json['id'] as String, - name: json['name'] as String? ?? '', - data: ComponentData.fromJson( - Map.from(json['data'] as Map)), - createdAt: const DateTimeConverter() - .fromJson((json['createdAt'] as num?)?.toInt()), - previewUrl: json['previewUrl'] as String?, - blurhash: json['blurhash'] as String? ?? '', - projectId: json['projectId'] as String, - pageId: json['pageId'] as String, - version: (json['version'] as num?)?.toInt(), - ); + id: json['id'] as String, + name: json['name'] as String? ?? '', + data: ComponentData.fromJson(Map.from(json['data'] as Map)), + createdAt: const DateTimeConverter().fromJson( + (json['createdAt'] as num?)?.toInt(), + ), + previewUrl: json['previewUrl'] as String?, + blurhash: json['blurhash'] as String? ?? '', + projectId: json['projectId'] as String, + pageId: json['pageId'] as String, + version: (json['version'] as num?)?.toInt(), +); -Map _$CustomComponentToJson(CustomComponent instance) { - final val = { - 'id': instance.id, - }; - - void writeNotNull( - String key, dynamic value, dynamic jsonValue, dynamic defaultValue) { - final bool serialize = - shouldSerialize(key, value, jsonValue, defaultValue, true); - - if (serialize) { - val[key] = jsonValue; - } - } - - writeNotNull('name', instance.name, instance.name, ''); - val['data'] = instance.data.toJson(); - writeNotNull('createdAt', instance.createdAt, - const DateTimeConverter().toJson(instance.createdAt), null); - writeNotNull('previewUrl', instance.previewUrl, instance.previewUrl, null); - writeNotNull('blurhash', instance.blurhash, instance.blurhash, ''); - val['projectId'] = instance.projectId; - val['pageId'] = instance.pageId; - val['version'] = instance.version; - return val; -} +Map _$CustomComponentToJson(CustomComponent instance) => + { + 'id': instance.id, + 'name': instance.name, + 'data': instance.data.toJson(), + 'createdAt': ?const DateTimeConverter().toJson(instance.createdAt), + 'previewUrl': ?instance.previewUrl, + 'blurhash': instance.blurhash, + 'projectId': instance.projectId, + 'pageId': instance.pageId, + 'version': instance.version, + }; ComponentData _$ComponentDataFromJson(Map json) => ComponentData( - width: (json['width'] as num).toDouble(), - height: (json['height'] as num).toDouble(), - nodes: Map.from(json['nodes'] as Map), - variables: (json['variables'] as Map?)?.map( - (k, e) => MapEntry( - k as String, - (e as List) - .map((e) => VariableData.fromJson( - Map.from(e as Map))) - .toSet()), - ) ?? - {}, - conditions: (json['conditions'] as Map?)?.map( - (k, e) => MapEntry( - k as String, - (e as List) - .map((e) => BaseCondition.fromJson( - Map.from(e as Map))) - .toSet()), - ) ?? - {}, - parentId: json['parentId'] as String, - originalParentId: json['originalParentId'] as String, - ); - -Map _$ComponentDataToJson(ComponentData instance) { - final val = { - 'width': instance.width, - 'height': instance.height, - 'nodes': instance.nodes, - }; - - void writeNotNull( - String key, dynamic value, dynamic jsonValue, dynamic defaultValue) { - final bool serialize = - shouldSerialize(key, value, jsonValue, defaultValue, true); - - if (serialize) { - val[key] = jsonValue; - } - } + width: (json['width'] as num).toDouble(), + height: (json['height'] as num).toDouble(), + nodes: Map.from(json['nodes'] as Map), + variables: + (json['variables'] as Map?)?.map( + (k, e) => MapEntry( + k as String, + (e as List) + .map( + (e) => + VariableData.fromJson(Map.from(e as Map)), + ) + .toSet(), + ), + ) ?? + const {}, + conditions: + (json['conditions'] as Map?)?.map( + (k, e) => MapEntry( + k as String, + (e as List) + .map( + (e) => + BaseCondition.fromJson(Map.from(e as Map)), + ) + .toSet(), + ), + ) ?? + const {}, + parentId: json['parentId'] as String, + originalParentId: json['originalParentId'] as String, +); - writeNotNull( - 'variables', - instance.variables, - instance.variables - .map((k, e) => MapEntry(k, e.map((e) => e.toJson()).toList())), - const {}); - writeNotNull( - 'conditions', - instance.conditions, - instance.conditions - .map((k, e) => MapEntry(k, e.map((e) => e.toJson()).toList())), - const {}); - val['parentId'] = instance.parentId; - val['originalParentId'] = instance.originalParentId; - return val; -} +Map _$ComponentDataToJson(ComponentData instance) => + { + 'width': instance.width, + 'height': instance.height, + 'nodes': instance.nodes, + 'variables': instance.variables.map( + (k, e) => MapEntry(k, e.map((e) => e.toJson()).toList()), + ), + 'conditions': instance.conditions.map( + (k, e) => MapEntry(k, e.map((e) => e.toJson()).toList()), + ), + 'parentId': instance.parentId, + 'originalParentId': instance.originalParentId, + }; diff --git a/lib/src/model/font_family.dart b/lib/src/model/font_family.dart index 7b9e2499..5815e8fb 100644 --- a/lib/src/model/font_family.dart +++ b/lib/src/model/font_family.dart @@ -3,7 +3,7 @@ // license that can be found in the LICENSE.md file. import 'package:codelessly_api/codelessly_api.dart'; -import 'package:codelessly_json_annotation/codelessly_json_annotation.dart'; +import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import '../../codelessly_sdk.dart'; diff --git a/lib/src/model/font_family.g.dart b/lib/src/model/font_family.g.dart index 07ac8516..867099cb 100644 --- a/lib/src/model/font_family.g.dart +++ b/lib/src/model/font_family.g.dart @@ -7,20 +7,21 @@ part of 'font_family.dart'; // ************************************************************************** FontFamilyModel _$FontFamilyModelFromJson(Map json) => FontFamilyModel( - id: json['id'] as String?, - name: json['name'] as String, - fontVariants: (json['fontVariants'] as List) - .map((e) => - FontVariantModel.fromJson(Map.from(e as Map))) - .toList(), - owner: json['owner'] as String, - teams: (json['teams'] as List?)?.map((e) => e as String).toSet(), - users: (json['users'] as List?)?.map((e) => e as String).toSet(), - roles: (json['roles'] as Map).map( - (k, e) => MapEntry(k as String, Role.fromJson(e as String)), - ), - public: json['public'] as bool?, - ); + id: json['id'] as String?, + name: json['name'] as String, + fontVariants: (json['fontVariants'] as List) + .map( + (e) => FontVariantModel.fromJson(Map.from(e as Map)), + ) + .toList(), + owner: json['owner'] as String, + teams: (json['teams'] as List?)?.map((e) => e as String).toSet(), + users: (json['users'] as List?)?.map((e) => e as String).toSet(), + roles: (json['roles'] as Map).map( + (k, e) => MapEntry(k as String, Role.fromJson(e as String)), + ), + public: json['public'] as bool?, +); Map _$FontFamilyModelToJson(FontFamilyModel instance) => { @@ -35,35 +36,23 @@ Map _$FontFamilyModelToJson(FontFamilyModel instance) => }; FontVariantModel _$FontVariantModelFromJson(Map json) => FontVariantModel( - name: json['name'] as String? ?? 'regular', - weight: $enumDecodeNullable(_$FontWeightNumericEnumMap, json['weight']) ?? - FontWeightNumeric.w400, - style: json['style'] as String? ?? 'Normal', - fontURL: json['fontURL'] as String? ?? '', - previewURL: json['previewURL'] as String? ?? '', - ); + name: json['name'] as String? ?? 'regular', + weight: + $enumDecodeNullable(_$FontWeightNumericEnumMap, json['weight']) ?? + FontWeightNumeric.w400, + style: json['style'] as String? ?? 'Normal', + fontURL: json['fontURL'] as String? ?? '', + previewURL: json['previewURL'] as String? ?? '', +); -Map _$FontVariantModelToJson(FontVariantModel instance) { - final val = {}; - - void writeNotNull( - String key, dynamic value, dynamic jsonValue, dynamic defaultValue) { - final bool serialize = - shouldSerialize(key, value, jsonValue, defaultValue, true); - - if (serialize) { - val[key] = jsonValue; - } - } - - writeNotNull('name', instance.name, instance.name, 'regular'); - writeNotNull('weight', instance.weight, - _$FontWeightNumericEnumMap[instance.weight]!, FontWeightNumeric.w400); - writeNotNull('style', instance.style, instance.style, 'Normal'); - writeNotNull('fontURL', instance.fontURL, instance.fontURL, ''); - writeNotNull('previewURL', instance.previewURL, instance.previewURL, ''); - return val; -} +Map _$FontVariantModelToJson(FontVariantModel instance) => + { + 'name': instance.name, + 'weight': _$FontWeightNumericEnumMap[instance.weight]!, + 'style': instance.style, + 'fontURL': instance.fontURL, + 'previewURL': instance.previewURL, + }; const _$FontWeightNumericEnumMap = { FontWeightNumeric.w100: 'w100', diff --git a/lib/src/model/model_http_request.dart b/lib/src/model/model_http_request.dart index add4ecfe..97a9d47f 100644 --- a/lib/src/model/model_http_request.dart +++ b/lib/src/model/model_http_request.dart @@ -1,5 +1,5 @@ import 'package:codelessly_api/codelessly_api.dart'; -import 'package:codelessly_json_annotation/codelessly_json_annotation.dart'; +import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import '../../codelessly_sdk.dart'; diff --git a/lib/src/model/model_http_request.g.dart b/lib/src/model/model_http_request.g.dart index 281d6c2d..45313e60 100644 --- a/lib/src/model/model_http_request.g.dart +++ b/lib/src/model/model_http_request.g.dart @@ -7,112 +7,96 @@ part of 'model_http_request.dart'; // ************************************************************************** HttpApiData _$HttpApiDataFromJson(Map json) => HttpApiData( - directory: json['directory'] as String, - method: $enumDecodeNullable(_$HttpMethodEnumMap, json['method']) ?? - HttpMethod.get, - url: json['url'] as String? ?? '', - headers: (json['headers'] as List?) - ?.map((e) => HttpKeyValuePair.fromJson( - Map.from(e as Map))) - .toList() ?? - [], - queryParams: (json['queryParams'] as List?) - ?.map((e) => HttpKeyValuePair.fromJson( - Map.from(e as Map))) - .toList() ?? - [], - formFields: (json['formFields'] as List?) - ?.map((e) => HttpKeyValuePair.fromJson( - Map.from(e as Map))) - .toList() ?? - [], - body: json['body'] as String?, - bodyType: - $enumDecodeNullable(_$RequestBodyTypeEnumMap, json['bodyType']) ?? - RequestBodyType.text, - name: json['name'] as String, - id: json['id'] as String? ?? '', - project: json['project'] as String? ?? '', - variables: (json['variables'] as List?) - ?.map((e) => - VariableData.fromJson(Map.from(e as Map))) - .toList() ?? - [], - isDeleted: json['deleted'] as bool? ?? false, - lastUpdated: const DateTimeConverter() - .fromJson((json['lastUpdated'] as num?)?.toInt()), - requestBodyContentType: $enumDecodeNullable( - _$RequestBodyTextTypeEnumMap, json['requestBodyContentType']) ?? - RequestBodyTextType.json, - created: const DateTimeConverter() - .fromJson((json['created'] as num?)?.toInt()), - owner: json['owner'] as String, - teams: (json['teams'] as List?)?.map((e) => e as String).toSet(), - users: (json['users'] as List?)?.map((e) => e as String).toSet(), - roles: (json['roles'] as Map).map( - (k, e) => MapEntry(k as String, Role.fromJson(e as String)), - ), - public: json['public'] as bool?, - ); + directory: json['directory'] as String, + method: + $enumDecodeNullable(_$HttpMethodEnumMap, json['method']) ?? + HttpMethod.get, + url: json['url'] as String? ?? '', + headers: + (json['headers'] as List?) + ?.map( + (e) => + HttpKeyValuePair.fromJson(Map.from(e as Map)), + ) + .toList() ?? + const [], + queryParams: + (json['queryParams'] as List?) + ?.map( + (e) => + HttpKeyValuePair.fromJson(Map.from(e as Map)), + ) + .toList() ?? + const [], + formFields: + (json['formFields'] as List?) + ?.map( + (e) => + HttpKeyValuePair.fromJson(Map.from(e as Map)), + ) + .toList() ?? + const [], + body: json['body'] as String?, + bodyType: + $enumDecodeNullable(_$RequestBodyTypeEnumMap, json['bodyType']) ?? + RequestBodyType.text, + name: json['name'] as String, + id: json['id'] as String? ?? '', + project: json['project'] as String? ?? '', + variables: + (json['variables'] as List?) + ?.map( + (e) => VariableData.fromJson(Map.from(e as Map)), + ) + .toList() ?? + const [], + isDeleted: json['deleted'] as bool? ?? false, + lastUpdated: const DateTimeConverter().fromJson( + (json['lastUpdated'] as num?)?.toInt(), + ), + requestBodyContentType: + $enumDecodeNullable( + _$RequestBodyTextTypeEnumMap, + json['requestBodyContentType'], + ) ?? + RequestBodyTextType.json, + created: const DateTimeConverter().fromJson( + (json['created'] as num?)?.toInt(), + ), + owner: json['owner'] as String, + teams: (json['teams'] as List?)?.map((e) => e as String).toSet(), + users: (json['users'] as List?)?.map((e) => e as String).toSet(), + roles: (json['roles'] as Map).map( + (k, e) => MapEntry(k as String, Role.fromJson(e as String)), + ), + public: json['public'] as bool?, +); -Map _$HttpApiDataToJson(HttpApiData instance) { - final val = { - 'owner': instance.owner, - 'users': instance.users.toList(), - 'roles': instance.roles.map((k, e) => MapEntry(k, e.toJson())), - 'teams': instance.teams.toList(), - 'public': instance.public, - }; - - void writeNotNull( - String key, dynamic value, dynamic jsonValue, dynamic defaultValue) { - final bool serialize = - shouldSerialize(key, value, jsonValue, defaultValue, true); - - if (serialize) { - val[key] = jsonValue; - } - } - - writeNotNull('id', instance.id, instance.id, ''); - val['name'] = instance.name; - writeNotNull('project', instance.project, instance.project, ''); - writeNotNull('method', instance.method, _$HttpMethodEnumMap[instance.method]!, - HttpMethod.get); - writeNotNull('url', instance.url, instance.url, ''); - writeNotNull( - 'headers', - instance.headers, - instance.headers.map((e) => e.toJson()).toList(), - const []); - writeNotNull( - 'queryParams', - instance.queryParams, - instance.queryParams.map((e) => e.toJson()).toList(), - const []); - writeNotNull( - 'formFields', - instance.formFields, - instance.formFields.map((e) => e.toJson()).toList(), - const []); - writeNotNull('body', instance.body, instance.body, null); - writeNotNull('bodyType', instance.bodyType, - _$RequestBodyTypeEnumMap[instance.bodyType]!, RequestBodyType.text); - writeNotNull('deleted', instance.isDeleted, instance.isDeleted, false); - writeNotNull('lastUpdated', instance.lastUpdated, - const DateTimeConverter().toJson(instance.lastUpdated), null); - writeNotNull( - 'requestBodyContentType', - instance.requestBodyContentType, - _$RequestBodyTextTypeEnumMap[instance.requestBodyContentType]!, - RequestBodyTextType.json); - writeNotNull('variables', instance.variables, - instance.variables.map((e) => e.toJson()).toList(), const []); - writeNotNull('created', instance.created, - const DateTimeConverter().toJson(instance.created), null); - val['directory'] = instance.directory; - return val; -} +Map _$HttpApiDataToJson(HttpApiData instance) => + { + 'owner': instance.owner, + 'users': instance.users.toList(), + 'roles': instance.roles.map((k, e) => MapEntry(k, e.toJson())), + 'teams': instance.teams.toList(), + 'public': instance.public, + 'id': instance.id, + 'name': instance.name, + 'project': instance.project, + 'method': _$HttpMethodEnumMap[instance.method]!, + 'url': instance.url, + 'headers': instance.headers.map((e) => e.toJson()).toList(), + 'queryParams': instance.queryParams.map((e) => e.toJson()).toList(), + 'formFields': instance.formFields.map((e) => e.toJson()).toList(), + 'body': ?instance.body, + 'bodyType': _$RequestBodyTypeEnumMap[instance.bodyType]!, + 'deleted': instance.isDeleted, + 'lastUpdated': ?const DateTimeConverter().toJson(instance.lastUpdated), + 'requestBodyContentType': + _$RequestBodyTextTypeEnumMap[instance.requestBodyContentType]!, + 'variables': instance.variables.map((e) => e.toJson()).toList(), + 'created': ?const DateTimeConverter().toJson(instance.created), + 'directory': instance.directory, + }; const _$HttpMethodEnumMap = { HttpMethod.get: 'get', @@ -134,27 +118,14 @@ const _$RequestBodyTextTypeEnumMap = { }; HttpKeyValuePair _$HttpKeyValuePairFromJson(Map json) => HttpKeyValuePair( - key: json['key'] as String, - value: json['value'] as String, - isUsed: json['isUsed'] as bool? ?? true, - ); - -Map _$HttpKeyValuePairToJson(HttpKeyValuePair instance) { - final val = { - 'key': instance.key, - 'value': instance.value, - }; - - void writeNotNull( - String key, dynamic value, dynamic jsonValue, dynamic defaultValue) { - final bool serialize = - shouldSerialize(key, value, jsonValue, defaultValue, true); - - if (serialize) { - val[key] = jsonValue; - } - } + key: json['key'] as String, + value: json['value'] as String, + isUsed: json['isUsed'] as bool? ?? true, +); - writeNotNull('isUsed', instance.isUsed, instance.isUsed, true); - return val; -} +Map _$HttpKeyValuePairToJson(HttpKeyValuePair instance) => + { + 'key': instance.key, + 'value': instance.value, + 'isUsed': instance.isUsed, + }; diff --git a/lib/src/model/privacy_base.dart b/lib/src/model/privacy_base.dart index be897184..8abf8f80 100644 --- a/lib/src/model/privacy_base.dart +++ b/lib/src/model/privacy_base.dart @@ -1,5 +1,5 @@ import 'package:codelessly_api/codelessly_api.dart'; -import 'package:codelessly_json_annotation/codelessly_json_annotation.dart'; +import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; part 'privacy_base.g.dart'; @@ -70,7 +70,7 @@ enum Role { /// Represents the privacy controls of a given model that this mixin /// is applied to. -abstract class PrivacyBase with SerializableMixin, EquatableMixin { +abstract class PrivacyBase with SerializableMixin, Equatable { /// The owner of the document. The owner is the user who is responsible for /// the document and has the highest level of access to it. In terms of /// permissions, it's equivalent to the [Role.admin] role, however this owner diff --git a/lib/src/model/privacy_base.g.dart b/lib/src/model/privacy_base.g.dart index e8b67980..ce32f96b 100644 --- a/lib/src/model/privacy_base.g.dart +++ b/lib/src/model/privacy_base.g.dart @@ -7,14 +7,14 @@ part of 'privacy_base.dart'; // ************************************************************************** _PrivacyBaseImpl _$PrivacyBaseImplFromJson(Map json) => _PrivacyBaseImpl( - owner: json['owner'] as String, - teams: (json['teams'] as List?)?.map((e) => e as String).toSet(), - users: (json['users'] as List?)?.map((e) => e as String).toSet(), - roles: (json['roles'] as Map).map( - (k, e) => MapEntry(k as String, Role.fromJson(e as String)), - ), - public: json['public'] as bool?, - ); + owner: json['owner'] as String, + teams: (json['teams'] as List?)?.map((e) => e as String).toSet(), + users: (json['users'] as List?)?.map((e) => e as String).toSet(), + roles: (json['roles'] as Map).map( + (k, e) => MapEntry(k as String, Role.fromJson(e as String)), + ), + public: json['public'] as bool?, +); Map _$PrivacyBaseImplToJson(_PrivacyBaseImpl instance) => { diff --git a/lib/src/model/sdk_publish_model.dart b/lib/src/model/sdk_publish_model.dart index 6076e351..438f7610 100644 --- a/lib/src/model/sdk_publish_model.dart +++ b/lib/src/model/sdk_publish_model.dart @@ -1,7 +1,7 @@ import 'dart:convert'; import 'package:codelessly_api/codelessly_api.dart'; -import 'package:codelessly_json_annotation/codelessly_json_annotation.dart'; +import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'model_http_request.dart'; @@ -643,7 +643,7 @@ enum UpdateType { } @JsonSerializable() -class SDKPublishUpdates with EquatableMixin { +class SDKPublishUpdates with Equatable { /// The last time the fonts collection received an update. @DateTimeMapConverter() final Map fonts; diff --git a/lib/src/model/sdk_publish_model.g.dart b/lib/src/model/sdk_publish_model.g.dart index 4a7b6156..2e10e382 100644 --- a/lib/src/model/sdk_publish_model.g.dart +++ b/lib/src/model/sdk_publish_model.g.dart @@ -7,260 +7,223 @@ part of 'sdk_publish_model.dart'; // ************************************************************************** SDKPublishModel _$SDKPublishModelFromJson(Map json) => SDKPublishModel( - projectId: json['projectId'] as String, - updates: json['updates'] == null - ? null - : SDKPublishUpdates.fromJson( - Map.from(json['updates'] as Map)), - pages: - (json['pages'] as List?)?.map((e) => e as String).toList(), - layoutIDMap: (json['layoutIDMap'] as Map?)?.map( - (k, e) => MapEntry(k as String, e as String), - ), - fonts: SDKPublishModel.deserializeFonts( - SDKPublishModel.readLookupMap(json, 'fonts') as Map), - layouts: SDKPublishModel.deserializeLayouts( - SDKPublishModel.readLookupMap(json, 'layouts') as Map), - variables: SDKPublishModel.deserializeVariables( - SDKPublishModel.readLookupMap(json, 'variables') as Map), - conditions: SDKPublishModel.deserializeConditions( - SDKPublishModel.readLookupMap(json, 'conditions') as Map), - apis: (json['apis'] as Map?)?.map( - (k, e) => MapEntry(k as String, - HttpApiData.fromJson(Map.from(e as Map))), - ), - entryLayoutId: json['entryLayoutId'] as String?, - entryPageId: json['entryPageId'] as String?, - entryCanvasId: json['entryCanvasId'] as String?, - lastUpdated: const DateTimeConverter() - .fromJson((json['lastUpdated'] as num?)?.toInt()), - disabledLayouts: (json['disabledLayouts'] as List?) - ?.map((e) => e as String) - .toList(), - owner: json['owner'] as String, - teams: (json['teams'] as List?)?.map((e) => e as String).toSet(), - users: (json['users'] as List?)?.map((e) => e as String).toSet(), - roles: (json['roles'] as Map).map( - (k, e) => MapEntry(k as String, Role.fromJson(e as String)), - ), - public: json['public'] as bool?, - ); + projectId: json['projectId'] as String, + updates: json['updates'] == null + ? null + : SDKPublishUpdates.fromJson( + Map.from(json['updates'] as Map), + ), + pages: (json['pages'] as List?)?.map((e) => e as String).toList(), + layoutIDMap: (json['layoutIDMap'] as Map?)?.map( + (k, e) => MapEntry(k as String, e as String), + ), + fonts: SDKPublishModel.deserializeFonts( + SDKPublishModel.readLookupMap(json, 'fonts') as Map, + ), + layouts: SDKPublishModel.deserializeLayouts( + SDKPublishModel.readLookupMap(json, 'layouts') as Map, + ), + variables: SDKPublishModel.deserializeVariables( + SDKPublishModel.readLookupMap(json, 'variables') as Map, + ), + conditions: SDKPublishModel.deserializeConditions( + SDKPublishModel.readLookupMap(json, 'conditions') as Map, + ), + apis: (json['apis'] as Map?)?.map( + (k, e) => MapEntry( + k as String, + HttpApiData.fromJson(Map.from(e as Map)), + ), + ), + entryLayoutId: json['entryLayoutId'] as String?, + entryPageId: json['entryPageId'] as String?, + entryCanvasId: json['entryCanvasId'] as String?, + lastUpdated: const DateTimeConverter().fromJson( + (json['lastUpdated'] as num?)?.toInt(), + ), + disabledLayouts: (json['disabledLayouts'] as List?) + ?.map((e) => e as String) + .toList(), + owner: json['owner'] as String, + teams: (json['teams'] as List?)?.map((e) => e as String).toSet(), + users: (json['users'] as List?)?.map((e) => e as String).toSet(), + roles: (json['roles'] as Map).map( + (k, e) => MapEntry(k as String, Role.fromJson(e as String)), + ), + public: json['public'] as bool?, +); -Map _$SDKPublishModelToJson(SDKPublishModel instance) { - final val = { - 'owner': instance.owner, - 'users': instance.users.toList(), - 'roles': instance.roles.map((k, e) => MapEntry(k, e.toJson())), - 'teams': instance.teams.toList(), - 'public': instance.public, - 'projectId': instance.projectId, - 'fonts': SDKPublishModel.serializeLookupMapFonts(instance.fonts), - 'layouts': SDKPublishModel.serializeLookupMapLayouts(instance.layouts), - 'pages': instance.pages, - 'updates': instance.updates.toJson(), - 'apis': instance.apis.map((k, e) => MapEntry(k, e.toJson())), - 'variables': - SDKPublishModel.serializeLookupMapVariables(instance.variables), - 'conditions': - SDKPublishModel.serializeLookupMapConditions(instance.conditions), - }; - - void writeNotNull( - String key, dynamic value, dynamic jsonValue, dynamic defaultValue) { - final bool serialize = - shouldSerialize(key, value, jsonValue, defaultValue, true); - - if (serialize) { - val[key] = jsonValue; - } - } - - writeNotNull( - 'entryLayoutId', instance.entryLayoutId, instance.entryLayoutId, null); - writeNotNull('entryPageId', instance.entryPageId, instance.entryPageId, null); - writeNotNull( - 'entryCanvasId', instance.entryCanvasId, instance.entryCanvasId, null); - writeNotNull('lastUpdated', instance.lastUpdated, - const DateTimeConverter().toJson(instance.lastUpdated), null); - val['layoutIDMap'] = instance.layoutIDMap; - val['disabledLayouts'] = instance.disabledLayouts; - return val; -} +Map _$SDKPublishModelToJson( + SDKPublishModel instance, +) => { + 'owner': instance.owner, + 'users': instance.users.toList(), + 'roles': instance.roles.map((k, e) => MapEntry(k, e.toJson())), + 'teams': instance.teams.toList(), + 'public': instance.public, + 'projectId': instance.projectId, + 'fonts': SDKPublishModel.serializeLookupMapFonts(instance.fonts), + 'layouts': SDKPublishModel.serializeLookupMapLayouts(instance.layouts), + 'pages': instance.pages, + 'updates': instance.updates.toJson(), + 'apis': instance.apis.map((k, e) => MapEntry(k, e.toJson())), + 'variables': SDKPublishModel.serializeLookupMapVariables(instance.variables), + 'conditions': SDKPublishModel.serializeLookupMapConditions( + instance.conditions, + ), + 'entryLayoutId': ?instance.entryLayoutId, + 'entryPageId': ?instance.entryPageId, + 'entryCanvasId': ?instance.entryCanvasId, + 'lastUpdated': ?const DateTimeConverter().toJson(instance.lastUpdated), + 'layoutIDMap': instance.layoutIDMap, + 'disabledLayouts': instance.disabledLayouts, +}; SDKPublishLayout _$SDKPublishLayoutFromJson(Map json) => SDKPublishLayout( - id: json['id'] as String, - pageId: json['pageId'] as String, - projectId: json['projectId'] as String, - canvases: const CanvasesMapConverter() - .fromJson(json['canvases'] as Map), - lastUpdated: const DateTimeConverter() - .fromJson((json['lastUpdated'] as num?)?.toInt()), - breakpoints: (json['breakpoints'] as List?) - ?.map((e) => Breakpoint.fromJson(e as Map)) - .toList(), - hidden: json['hidden'] as bool?, - owner: json['owner'] as String, - teams: (json['teams'] as List?)?.map((e) => e as String).toSet(), - users: (json['users'] as List?)?.map((e) => e as String).toSet(), - roles: (json['roles'] as Map).map( - (k, e) => MapEntry(k as String, Role.fromJson(e as String)), - ), - public: json['public'] as bool?, - ); - -Map _$SDKPublishLayoutToJson(SDKPublishLayout instance) { - final val = { - 'owner': instance.owner, - 'users': instance.users.toList(), - 'roles': instance.roles.map((k, e) => MapEntry(k, e.toJson())), - 'teams': instance.teams.toList(), - 'public': instance.public, - 'id': instance.id, - 'pageId': instance.pageId, - 'projectId': instance.projectId, - 'canvases': const CanvasesMapConverter().toJson(instance.canvases), - 'breakpoints': instance.breakpoints.map((e) => e.toJson()).toList(), - }; - - void writeNotNull( - String key, dynamic value, dynamic jsonValue, dynamic defaultValue) { - final bool serialize = - shouldSerialize(key, value, jsonValue, defaultValue, true); + id: json['id'] as String, + pageId: json['pageId'] as String, + projectId: json['projectId'] as String, + canvases: const CanvasesMapConverter().fromJson( + json['canvases'] as Map, + ), + lastUpdated: const DateTimeConverter().fromJson( + (json['lastUpdated'] as num?)?.toInt(), + ), + breakpoints: (json['breakpoints'] as List?) + ?.map((e) => Breakpoint.fromJson(e as Map)) + .toList(), + hidden: json['hidden'] as bool?, + owner: json['owner'] as String, + teams: (json['teams'] as List?)?.map((e) => e as String).toSet(), + users: (json['users'] as List?)?.map((e) => e as String).toSet(), + roles: (json['roles'] as Map).map( + (k, e) => MapEntry(k as String, Role.fromJson(e as String)), + ), + public: json['public'] as bool?, +); - if (serialize) { - val[key] = jsonValue; - } - } - - writeNotNull('lastUpdated', instance.lastUpdated, - const DateTimeConverter().toJson(instance.lastUpdated), null); - val['hidden'] = instance.hidden; - return val; -} +Map _$SDKPublishLayoutToJson(SDKPublishLayout instance) => + { + 'owner': instance.owner, + 'users': instance.users.toList(), + 'roles': instance.roles.map((k, e) => MapEntry(k, e.toJson())), + 'teams': instance.teams.toList(), + 'public': instance.public, + 'id': instance.id, + 'pageId': instance.pageId, + 'projectId': instance.projectId, + 'canvases': const CanvasesMapConverter().toJson(instance.canvases), + 'breakpoints': instance.breakpoints.map((e) => e.toJson()).toList(), + 'lastUpdated': ?const DateTimeConverter().toJson(instance.lastUpdated), + 'hidden': instance.hidden, + }; SDKPublishFont _$SDKPublishFontFromJson(Map json) => SDKPublishFont( - id: json['id'] as String?, - url: json['url'] as String, - family: json['family'] as String, - weight: json['weight'] as String, - style: json['style'] as String?, - owner: json['owner'] as String, - teams: (json['teams'] as List?)?.map((e) => e as String).toSet(), - users: (json['users'] as List?)?.map((e) => e as String).toSet(), - roles: (json['roles'] as Map).map( - (k, e) => MapEntry(k as String, Role.fromJson(e as String)), - ), - public: json['public'] as bool?, - ); - -Map _$SDKPublishFontToJson(SDKPublishFont instance) { - final val = { - 'owner': instance.owner, - 'users': instance.users.toList(), - 'roles': instance.roles.map((k, e) => MapEntry(k, e.toJson())), - 'teams': instance.teams.toList(), - 'public': instance.public, - 'id': instance.id, - 'url': instance.url, - 'family': instance.family, - 'weight': instance.weight, - }; - - void writeNotNull( - String key, dynamic value, dynamic jsonValue, dynamic defaultValue) { - final bool serialize = - shouldSerialize(key, value, jsonValue, defaultValue, true); + id: json['id'] as String?, + url: json['url'] as String, + family: json['family'] as String, + weight: json['weight'] as String, + style: json['style'] as String?, + owner: json['owner'] as String, + teams: (json['teams'] as List?)?.map((e) => e as String).toSet(), + users: (json['users'] as List?)?.map((e) => e as String).toSet(), + roles: (json['roles'] as Map).map( + (k, e) => MapEntry(k as String, Role.fromJson(e as String)), + ), + public: json['public'] as bool?, +); - if (serialize) { - val[key] = jsonValue; - } - } - - writeNotNull('style', instance.style, instance.style, null); - return val; -} +Map _$SDKPublishFontToJson(SDKPublishFont instance) => + { + 'owner': instance.owner, + 'users': instance.users.toList(), + 'roles': instance.roles.map((k, e) => MapEntry(k, e.toJson())), + 'teams': instance.teams.toList(), + 'public': instance.public, + 'id': instance.id, + 'url': instance.url, + 'family': instance.family, + 'weight': instance.weight, + 'style': ?instance.style, + }; SDKPublishUpdates _$SDKPublishUpdatesFromJson(Map json) => SDKPublishUpdates( - fonts: json['fonts'] == null - ? const {} - : const DateTimeMapConverter() - .fromJson(json['fonts'] as Map), - layouts: json['layouts'] == null - ? const {} - : const DateTimeMapConverter() - .fromJson(json['layouts'] as Map), - apis: json['apis'] == null - ? const {} - : const DateTimeMapConverter() - .fromJson(json['apis'] as Map), - variables: json['variables'] == null - ? const {} - : const DateTimeMapConverter() - .fromJson(json['variables'] as Map), - conditions: json['conditions'] == null - ? const {} - : const DateTimeMapConverter() - .fromJson(json['conditions'] as Map), - layoutFonts: (json['layoutFonts'] as Map?)?.map( - (k, e) => MapEntry(k as String, - (e as List).map((e) => e as String).toSet()), - ) ?? - {}, - layoutApis: (json['layoutApis'] as Map?)?.map( - (k, e) => MapEntry(k as String, - (e as List).map((e) => e as String).toSet()), - ) ?? - {}, - ); - -Map _$SDKPublishUpdatesToJson(SDKPublishUpdates instance) { - final val = {}; - - void writeNotNull( - String key, dynamic value, dynamic jsonValue, dynamic defaultValue) { - final bool serialize = - shouldSerialize(key, value, jsonValue, defaultValue, true); - - if (serialize) { - val[key] = jsonValue; - } - } + fonts: json['fonts'] == null + ? const {} + : const DateTimeMapConverter().fromJson( + json['fonts'] as Map, + ), + layouts: json['layouts'] == null + ? const {} + : const DateTimeMapConverter().fromJson( + json['layouts'] as Map, + ), + apis: json['apis'] == null + ? const {} + : const DateTimeMapConverter().fromJson( + json['apis'] as Map, + ), + variables: json['variables'] == null + ? const {} + : const DateTimeMapConverter().fromJson( + json['variables'] as Map, + ), + conditions: json['conditions'] == null + ? const {} + : const DateTimeMapConverter().fromJson( + json['conditions'] as Map, + ), + layoutFonts: + (json['layoutFonts'] as Map?)?.map( + (k, e) => MapEntry( + k as String, + (e as List).map((e) => e as String).toSet(), + ), + ) ?? + const {}, + layoutApis: + (json['layoutApis'] as Map?)?.map( + (k, e) => MapEntry( + k as String, + (e as List).map((e) => e as String).toSet(), + ), + ) ?? + const {}, +); - writeNotNull('fonts', instance.fonts, - const DateTimeMapConverter().toJson(instance.fonts), const {}); - writeNotNull('layouts', instance.layouts, - const DateTimeMapConverter().toJson(instance.layouts), const {}); - writeNotNull('apis', instance.apis, - const DateTimeMapConverter().toJson(instance.apis), const {}); - writeNotNull('variables', instance.variables, - const DateTimeMapConverter().toJson(instance.variables), const {}); - writeNotNull('conditions', instance.conditions, - const DateTimeMapConverter().toJson(instance.conditions), const {}); - writeNotNull('layoutFonts', instance.layoutFonts, - instance.layoutFonts.map((k, e) => MapEntry(k, e.toList())), const {}); - writeNotNull('layoutApis', instance.layoutApis, - instance.layoutApis.map((k, e) => MapEntry(k, e.toList())), const {}); - return val; -} +Map _$SDKPublishUpdatesToJson( + SDKPublishUpdates instance, +) => { + 'fonts': const DateTimeMapConverter().toJson(instance.fonts), + 'layouts': const DateTimeMapConverter().toJson(instance.layouts), + 'apis': const DateTimeMapConverter().toJson(instance.apis), + 'variables': const DateTimeMapConverter().toJson(instance.variables), + 'conditions': const DateTimeMapConverter().toJson(instance.conditions), + 'layoutFonts': instance.layoutFonts.map((k, e) => MapEntry(k, e.toList())), + 'layoutApis': instance.layoutApis.map((k, e) => MapEntry(k, e.toList())), +}; SDKLayoutVariables _$SDKLayoutVariablesFromJson(Map json) => SDKLayoutVariables( - id: json['id'] as String, - variables: (json['variables'] as Map).map( + id: json['id'] as String, + variables: (json['variables'] as Map).map( + (k, e) => MapEntry( + k as String, + (e as Map).map( (k, e) => MapEntry( - k as String, - (e as Map).map( - (k, e) => MapEntry(k as String, - VariableData.fromJson(Map.from(e as Map))), - )), - ), - owner: json['owner'] as String, - teams: (json['teams'] as List?)?.map((e) => e as String).toSet(), - users: (json['users'] as List?)?.map((e) => e as String).toSet(), - roles: (json['roles'] as Map).map( - (k, e) => MapEntry(k as String, Role.fromJson(e as String)), + k as String, + VariableData.fromJson(Map.from(e as Map)), + ), ), - public: json['public'] as bool?, - ); + ), + ), + owner: json['owner'] as String, + teams: (json['teams'] as List?)?.map((e) => e as String).toSet(), + users: (json['users'] as List?)?.map((e) => e as String).toSet(), + roles: (json['roles'] as Map).map( + (k, e) => MapEntry(k as String, Role.fromJson(e as String)), + ), + public: json['public'] as bool?, +); Map _$SDKLayoutVariablesToJson(SDKLayoutVariables instance) => { @@ -270,8 +233,9 @@ Map _$SDKLayoutVariablesToJson(SDKLayoutVariables instance) => 'teams': instance.teams.toList(), 'public': instance.public, 'id': instance.id, - 'variables': instance.variables - .map((k, e) => MapEntry(k, e.map((k, e) => MapEntry(k, e.toJson())))), + 'variables': instance.variables.map( + (k, e) => MapEntry(k, e.map((k, e) => MapEntry(k, e.toJson()))), + ), }; SDKLayoutConditions _$SDKLayoutConditionsFromJson(Map json) => @@ -279,11 +243,14 @@ SDKLayoutConditions _$SDKLayoutConditionsFromJson(Map json) => id: json['id'] as String, conditions: (json['conditions'] as Map).map( (k, e) => MapEntry( - k as String, - (e as Map).map( - (k, e) => MapEntry(k as String, - BaseCondition.fromJson(Map.from(e as Map))), - )), + k as String, + (e as Map).map( + (k, e) => MapEntry( + k as String, + BaseCondition.fromJson(Map.from(e as Map)), + ), + ), + ), ), owner: json['owner'] as String, teams: (json['teams'] as List?)?.map((e) => e as String).toSet(), @@ -295,14 +262,15 @@ SDKLayoutConditions _$SDKLayoutConditionsFromJson(Map json) => ); Map _$SDKLayoutConditionsToJson( - SDKLayoutConditions instance) => - { - 'owner': instance.owner, - 'users': instance.users.toList(), - 'roles': instance.roles.map((k, e) => MapEntry(k, e.toJson())), - 'teams': instance.teams.toList(), - 'public': instance.public, - 'id': instance.id, - 'conditions': instance.conditions - .map((k, e) => MapEntry(k, e.map((k, e) => MapEntry(k, e.toJson())))), - }; + SDKLayoutConditions instance, +) => { + 'owner': instance.owner, + 'users': instance.users.toList(), + 'roles': instance.roles.map((k, e) => MapEntry(k, e.toJson())), + 'teams': instance.teams.toList(), + 'public': instance.public, + 'id': instance.id, + 'conditions': instance.conditions.map( + (k, e) => MapEntry(k, e.map((k, e) => MapEntry(k, e.toJson()))), + ), +}; diff --git a/lib/src/transformers/node_transformer.dart b/lib/src/transformers/node_transformer.dart index ff919774..8b3d657a 100644 --- a/lib/src/transformers/node_transformer.dart +++ b/lib/src/transformers/node_transformer.dart @@ -5,7 +5,7 @@ import 'package:flutter/widgets.dart'; import '../../codelessly_sdk.dart'; /// A class that holds information about how to build a node. -abstract class BuildSettings with EquatableMixin { +abstract class BuildSettings with Equatable { /// Whether to internally handle the node's opacity. final bool withOpacity; diff --git a/lib/src/transformers/node_transformers/passive_button_transformer.dart b/lib/src/transformers/node_transformers/passive_button_transformer.dart index 36fd4c81..d39cf75b 100644 --- a/lib/src/transformers/node_transformers/passive_button_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_button_transformer.dart @@ -169,6 +169,58 @@ class PassiveButtonWidget extends StatelessWidget { ) : label, ); + case ButtonTypeEnum.filled: + buttonWidget = FilledButton( + onPressed: enabled ? () => onPressed?.call(context) : null, + onLongPress: enabled ? () => onLongPress?.call(context) : null, + style: buttonStyle, + child: iconWidget != null + ? Row( + mainAxisSize: MainAxisSize.min, + verticalDirection: VerticalDirection.down, + children: [ + if (node.properties.placement == + IconPlacementEnum.start) ...{ + iconWidget, + if (node.properties.icon.show) + SizedBox(width: node.properties.gap), + }, + Flexible(child: label), + if (node.properties.placement == IconPlacementEnum.end) ...{ + if (node.properties.icon.show) + SizedBox(width: node.properties.gap), + iconWidget, + } + ], + ) + : label, + ); + case ButtonTypeEnum.filledTonal: + buttonWidget = FilledButton.tonal( + onPressed: enabled ? () => onPressed?.call(context) : null, + onLongPress: enabled ? () => onLongPress?.call(context) : null, + style: buttonStyle, + child: iconWidget != null + ? Row( + mainAxisSize: MainAxisSize.min, + verticalDirection: VerticalDirection.down, + children: [ + if (node.properties.placement == + IconPlacementEnum.start) ...{ + iconWidget, + if (node.properties.icon.show) + SizedBox(width: node.properties.gap), + }, + Flexible(child: label), + if (node.properties.placement == IconPlacementEnum.end) ...{ + if (node.properties.icon.show) + SizedBox(width: node.properties.gap), + iconWidget, + } + ], + ) + : label, + ); case ButtonTypeEnum.text: buttonWidget = TextButton( onPressed: enabled ? () => onPressed?.call(context) : null, diff --git a/lib/src/transformers/transformer_functions.dart b/lib/src/transformers/transformer_functions.dart index a0e6b1d2..dff06991 100644 --- a/lib/src/transformers/transformer_functions.dart +++ b/lib/src/transformers/transformer_functions.dart @@ -55,6 +55,38 @@ void convertNodeToButtonType(ButtonTypeEnum type, ButtonNode node) { ..shape = CShapeBorder.roundedRectangle ..borderWidth = 1 ..borderColor = null; + case ButtonTypeEnum.filled: + final brightness = ThemeData.estimateBrightnessForColor( + node.properties.buttonColor.toFlutterColor()); + final color = + (brightness == Brightness.light ? Colors.black : Colors.white) + .toColorRGBA(); + node.properties + ..labelStyle = node.properties.labelStyle.copyWith(fills: [ + color.toPaint(node.properties.labelStyle.fills.firstOrNull?.id) + ]) + ..icon = node.properties.icon.copyWith(color: color) + ..buttonColor = node.properties.buttonColor.copyWith(a: 1) + ..elevation = 0 + ..gap = node.properties.icon.show ? 8 : 0 + ..shape = CShapeBorder.stadium + ..borderColor = null + ..borderWidth = null; + case ButtonTypeEnum.filledTonal: + node.properties + ..buttonColor = node.properties.buttonColor.copyWith(a: 0.2) + ..labelStyle = node.properties.labelStyle.copyWith(fills: [ + node.properties.buttonColor + .copyWith(a: 1) + .toPaint(node.properties.labelStyle.fills.firstOrNull?.id) + ]) + ..icon = node.properties.icon + .copyWith(color: node.properties.buttonColor.copyWith(a: 1)) + ..elevation = 0 + ..gap = node.properties.icon.show ? 8 : 0 + ..shape = CShapeBorder.stadium + ..borderColor = null + ..borderWidth = null; case ButtonTypeEnum.icon: final brightness = ThemeData.estimateBrightnessForColor( node.properties.buttonColor.toFlutterColor()); @@ -179,6 +211,19 @@ ButtonStyle createMasterButtonStyle(ButtonNode node, shadowColor: shadowColor.toFlutterColor(), shape: getButtonShape(node, scopedValues), ); + case ButtonTypeEnum.filled: + case ButtonTypeEnum.filledTonal: + buttonStyle = FilledButton.styleFrom( + foregroundColor: labelColor, + elevation: elevation ?? node.properties.elevation, + shadowColor: shadowColor.toFlutterColor(), + backgroundColor: buttonColor.toFlutterColor(), + visualDensity: VisualDensity.standard, + padding: node.padding.flutterEdgeInsets, + textStyle: textStyle, + minimumSize: Size.zero, + shape: getButtonShape(node, scopedValues), + ); case ButtonTypeEnum.outlined: buttonStyle = OutlinedButton.styleFrom( foregroundColor: labelColor, @@ -448,6 +493,11 @@ OutlinedBorder getShape({ borderRadius: radius.borderRadius, side: side, ); + case CShapeBorder.roundedSuperellipse: + return RoundedSuperellipseBorder( + borderRadius: radius.borderRadius, + side: side, + ); } } diff --git a/lib/src/transformers/utils/dash_path.dart b/lib/src/transformers/utils/dash_path.dart index 68b0e62e..13314d25 100644 --- a/lib/src/transformers/utils/dash_path.dart +++ b/lib/src/transformers/utils/dash_path.dart @@ -2,7 +2,7 @@ import 'dart:ui'; /// Converts a [path] to a dashed [Path] with a given [length] and [gap]. /// The [distance] parameter can be used to offset the dash pattern. -Path dashPath(final Path path, double length, +Path dashPath(Path path, double length, [double? gap, double? distance = 0]) { gap ??= length; PathMetrics pathMetrics = path.computeMetrics(); diff --git a/lib/src/transformers/utils/indexed_item_provider.dart b/lib/src/transformers/utils/indexed_item_provider.dart index dc5270bf..c12ccba3 100644 --- a/lib/src/transformers/utils/indexed_item_provider.dart +++ b/lib/src/transformers/utils/indexed_item_provider.dart @@ -18,7 +18,7 @@ class IndexedItemProvider extends InheritedWidget { oldWidget.item != item; } -class IndexedItem with EquatableMixin { +class IndexedItem with Equatable { final int index; final Object? item; diff --git a/lib/src/ui/codelessly_context.dart b/lib/src/ui/codelessly_context.dart index 40ac3482..05ee46da 100644 --- a/lib/src/ui/codelessly_context.dart +++ b/lib/src/ui/codelessly_context.dart @@ -9,7 +9,7 @@ import '../../codelessly_sdk.dart'; /// Holds data passed from the Codelessly instance down the widget tree where /// all of the [WidgetNodeTransformer]s have access to it. -class CodelesslyContext with ChangeNotifier, EquatableMixin { +class CodelesslyContext with ChangeNotifier, Equatable { /// A map of data that is passed to loaded layouts for nodes to replace their /// values with. Map _data; diff --git a/lib/src/utils/extensions.dart b/lib/src/utils/extensions.dart index 24e57635..77eaf2d5 100644 --- a/lib/src/utils/extensions.dart +++ b/lib/src/utils/extensions.dart @@ -51,6 +51,8 @@ extension FABLocationHelper on FABLocation { return FloatingActionButtonLocation.endDocked; case FABLocation.miniEndDocked: return FloatingActionButtonLocation.miniEndDocked; + case FABLocation.endContained: + return FloatingActionButtonLocation.endContained; } } } @@ -258,6 +260,10 @@ extension TextModelEnumHelper on TextAlignHorizontalEnum { return TextAlign.right; case TextAlignHorizontalEnum.justified: return TextAlign.justify; + case TextAlignHorizontalEnum.start: + return TextAlign.start; + case TextAlignHorizontalEnum.end: + return TextAlign.end; } } } @@ -936,6 +942,10 @@ extension TextAlignHorizontalEnumExtensions on TextAlignHorizontalEnum { return TextAlign.right; case TextAlignHorizontalEnum.justified: return TextAlign.justify; + case TextAlignHorizontalEnum.start: + return TextAlign.start; + case TextAlignHorizontalEnum.end: + return TextAlign.end; } } } @@ -1069,6 +1079,10 @@ extension TextInputTypeEnumExtensions on TextInputTypeEnum { return TextInputType.url; case TextInputTypeEnum.visiblePassword: return TextInputType.visiblePassword; + case TextInputTypeEnum.webSearch: + return TextInputType.webSearch; + case TextInputTypeEnum.twitter: + return TextInputType.twitter; } } } @@ -2033,6 +2047,9 @@ extension AutovalidateModeCExt on AutovalidateModeC { AutovalidateModeC.always => AutovalidateMode.always, AutovalidateModeC.onUserInteraction => AutovalidateMode.onUserInteraction, + AutovalidateModeC.onUnfocus => AutovalidateMode.onUnfocus, + AutovalidateModeC.onUserInteractionIfError => + AutovalidateMode.onUserInteractionIfError, }; } diff --git a/lib/src/utils/utils.dart b/lib/src/utils/utils.dart index 77914750..bf71fd82 100644 --- a/lib/src/utils/utils.dart +++ b/lib/src/utils/utils.dart @@ -89,7 +89,7 @@ enum AlignmentEnum { } /// Parsed data from a variable path using [variablePathRegex]. -class VariableMatch with EquatableMixin { +class VariableMatch with Equatable { final String text; final String name; final String? path; diff --git a/pubspec.yaml b/pubspec.yaml index ffb52650..e5a5f125 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -5,8 +5,8 @@ homepage: https://codelessly.com/ repository: https://github.com/Codelessly/CodelesslySDK environment: - sdk: ">=3.5.0 <4.0.0" - flutter: ">=3.24.0" + sdk: ^3.13.0 + flutter: ">=3.47.0" platforms: android: @@ -20,15 +20,15 @@ dependencies: flutter: sdk: flutter # Codelessly - codelessly_api: 4.4.0 - codelessly_json_annotation: 4.9.0+3 + codelessly_api: ^5.0.0 + json_annotation: ^4.12.0 # Firebase - firebase_core: ^3.10.0 - firebase_auth: ^5.4.0 - cloud_firestore: ^5.6.1 - firebase_ui_firestore: ^1.7.1 - google_fonts: ^6.2.1 + firebase_core: ^4.15.0 + firebase_auth: ^6.7.0 + cloud_firestore: ^6.10.0 + firebase_ui_firestore: ^2.1.0 + google_fonts: ^9.0.0 # Core Flutter collection: '>=1.17.0 <2.0.0' @@ -45,7 +45,7 @@ dependencies: web: ^1.1.0 # 3rd Party Core Framework - equatable: '>=2.0.0 <3.0.0' + equatable: ^3.0.0 vector_math: ^2.1.4 provider: ^6.1.2 uuid: ^4.5.1 @@ -60,7 +60,7 @@ dependencies: hive_ce_flutter: ^2.2.0 # JSON - json_path: ^0.7.4 + json_path: ^0.9.0 rfc_6901: ^0.2.0 # JSON pointer spec jwt_decoder: ^2.0.1 @@ -70,9 +70,9 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - codelessly_json_serializable: 6.8.0+1 + json_serializable: ^6.14.1 build_runner: ^2.4.14 - flutter_lints: ^5.0.0 + flutter_lints: ^6.0.0 flutter: uses-material-design: true @@ -84,3 +84,10 @@ false_secrets: - /lib/src/firebase_options_prod.dart scripts: scripts.yaml + +# codelessly_api 5.0.0 is not on pub.dev yet. Remove once it is published. +dependency_overrides: + codelessly_api: + git: + url: https://github.com/Codelessly/CodelesslyAPI.git + ref: claude/hopeful-brown-c8o8x7 From 92dfbca58298587a913e8ddc940e0b9fd2fd3188 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 13 Oct 2026 02:06:10 +0000 Subject: [PATCH 02/22] Render the new AppBar and TabBar properties from codelessly_api 5 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01A67XoQYsLVvoMtiKcXXUxY --- CHANGELOG.md | 2 ++ .../passive_app_bar_transformer.dart | 6 ++++-- .../passive_tab_bar_transformer.dart | 3 +++ lib/src/utils/extensions.dart | 12 ++++++++++++ 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d4f7c02..ca44f3c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ RoundedSuperellipseBorder shapes, TextAlign start/end, the `endContained` FAB location, `webSearch`/`twitter` keyboards, and the `onUnfocus`/`onUserInteractionIfError` autovalidate modes. +- Render the new AppBar `foregroundColor`/`scrolledUnderElevation` and + TabBar `tabAlignment`/`dividerHeight` properties. ## 6.2.0 diff --git a/lib/src/transformers/node_transformers/passive_app_bar_transformer.dart b/lib/src/transformers/node_transformers/passive_app_bar_transformer.dart index d4f2d32b..59dee4b1 100644 --- a/lib/src/transformers/node_transformers/passive_app_bar_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_app_bar_transformer.dart @@ -84,8 +84,9 @@ class PassiveAppBarTransformer extends NodeWidgetTransformer { surfaceTintColor: node.properties.backgroundColor.toFlutterColor(), backgroundColor: node.properties.backgroundColor.toFlutterColor(), elevation: node.properties.elevation, - foregroundColor: + foregroundColor: node.properties.foregroundColor?.toFlutterColor() ?? node.properties.titleStyle.fills.firstOrNull?.toFlutterColor(), + scrolledUnderElevation: node.properties.scrolledUnderElevation, shadowColor: node.properties.shadowColor.toFlutterColor(), actions: [ for (final item @@ -280,8 +281,9 @@ class PassiveAppBarWidget extends StatelessWidget ? node.properties.automaticallyImplyLeading : false, title: title, - foregroundColor: + foregroundColor: node.properties.foregroundColor?.toFlutterColor() ?? node.properties.titleStyle.fills.firstOrNull?.toFlutterColor(), + scrolledUnderElevation: node.properties.scrolledUnderElevation, titleSpacing: node.properties.titleSpacing, shadowColor: node.properties.shadowColor.toFlutterColor(), actions: [ diff --git a/lib/src/transformers/node_transformers/passive_tab_bar_transformer.dart b/lib/src/transformers/node_transformers/passive_tab_bar_transformer.dart index 33c11890..2ebad1e6 100644 --- a/lib/src/transformers/node_transformers/passive_tab_bar_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_tab_bar_transformer.dart @@ -164,6 +164,9 @@ class _PassiveTabBarWidgetState extends State overlayColor: WidgetStateProperty.all( widget.node.properties.overlayColor?.toFlutterColor()), dividerColor: widget.node.properties.dividerColor?.toFlutterColor(), + dividerHeight: widget.node.properties.dividerHeight, + tabAlignment: widget.node.properties.tabAlignment + ?.toFlutter(isScrollable: widget.node.isScrollable), indicatorColor: widget.node.properties.indicatorColor.toFlutterColor(), indicatorPadding: diff --git a/lib/src/utils/extensions.dart b/lib/src/utils/extensions.dart index 77eaf2d5..9e1653b9 100644 --- a/lib/src/utils/extensions.dart +++ b/lib/src/utils/extensions.dart @@ -1880,6 +1880,18 @@ extension TabBarIndicatorSizeExtensions on TabBarIndicatorSizeEnum { } } +extension TabAlignmentCExtensions on TabAlignmentC { + /// Returns null for alignments that Flutter's [TabBar] rejects for the + /// given scrollability, so it falls back to its default. + TabAlignment? toFlutter({required bool isScrollable}) => switch (this) { + TabAlignmentC.start => isScrollable ? TabAlignment.start : null, + TabAlignmentC.startOffset => + isScrollable ? TabAlignment.startOffset : null, + TabAlignmentC.fill => isScrollable ? null : TabAlignment.fill, + TabAlignmentC.center => TabAlignment.center, + }; +} + extension TextAlignVerticalEnumExt on TextAlignVerticalEnum { TextAlignVertical get flutterTextAlignVertical { return switch (this) { From 8b583f10bf286ac1b97df906ce8d82df4677e17e Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 13 Oct 2026 02:14:12 +0000 Subject: [PATCH 03/22] Replace deprecated Flutter APIs and clear analyzer findings - ListView/GridView: cacheExtent -> scrollCacheExtent (ScrollCacheExtent.pixels). - Radio: manage the group through RadioGroup instead of the deprecated Radio.groupValue/onChanged. - Slider: ShowValueIndicator.always -> onDrag. - Switch: activeColor -> activeThumbColor. - Color: red/green/blue/alpha/opacity/value -> r/g/b/a/toARGB32. - Await Hive writes inside try so storage failures are caught and logged instead of escaping. - Apply dart fix and remove an unused constant; flutter analyze now reports no issues. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01A67XoQYsLVvoMtiKcXXUxY --- lib/src/auth/auth_manager.dart | 4 +- lib/src/cache/codelessly_cache_manager.dart | 4 +- lib/src/data/cloud_database.dart | 2 - lib/src/data/data_manager.dart | 4 +- lib/src/model/sdk_publish_model.dart | 2 +- .../passive_dropdown_transformer.dart | 2 +- .../passive_grid_view_transformer.dart | 6 +- .../passive_list_view_transformer.dart | 11 +++- .../passive_radio_transformer.dart | 61 ++++++++++++------- .../passive_rectangle_transformer.dart | 2 +- .../passive_slider_transformer.dart | 2 +- .../passive_switch_transformer.dart | 2 +- .../passive_text_field_transformer.dart | 3 +- .../transformers/transformer_functions.dart | 8 +-- lib/src/ui/codelessly_context.dart | 7 +-- lib/src/ui/codelessly_widget.dart | 2 +- lib/src/ui/codelessly_widget_controller.dart | 2 +- lib/src/utils/extensions.dart | 26 ++++---- lib/src/utils/image_builder.dart | 4 +- 19 files changed, 86 insertions(+), 68 deletions(-) diff --git a/lib/src/auth/auth_manager.dart b/lib/src/auth/auth_manager.dart index fc1ea3d1..3ebf970f 100644 --- a/lib/src/auth/auth_manager.dart +++ b/lib/src/auth/auth_manager.dart @@ -67,8 +67,8 @@ class AuthManager { required this.cacheManager, required this.firebaseAuth, required this.client, - AuthData? authData, - }) : _authData = authData { + this._authData, + }) { if (_authData != null) { _authStreamController.add(_authData); } diff --git a/lib/src/cache/codelessly_cache_manager.dart b/lib/src/cache/codelessly_cache_manager.dart index a1cb0785..e400716d 100644 --- a/lib/src/cache/codelessly_cache_manager.dart +++ b/lib/src/cache/codelessly_cache_manager.dart @@ -90,9 +90,9 @@ class CodelesslyCacheManager extends CacheManager { DebugLogger.instance.printFunction('store(key: $key)', name: name); try { if (value is String || value is num || value is bool || value is List) { - return box.put(key, value); + return await box.put(key, value); } - return box.put(key, jsonEncode(value)); + return await box.put(key, jsonEncode(value)); } catch (e, str) { ErrorLogger.instance.captureException( e, diff --git a/lib/src/data/cloud_database.dart b/lib/src/data/cloud_database.dart index d002d8ac..0557f724 100644 --- a/lib/src/data/cloud_database.dart +++ b/lib/src/data/cloud_database.dart @@ -9,8 +9,6 @@ import '../logging/debug_logger.dart'; import '../logging/stat_tracker.dart'; import '../utils/constants.dart'; -const String _label = 'Cloud Database'; - abstract final class PrivateDocumentFields { PrivateDocumentFields._(); diff --git a/lib/src/data/data_manager.dart b/lib/src/data/data_manager.dart index 2023d38b..45d27b4e 100644 --- a/lib/src/data/data_manager.dart +++ b/lib/src/data/data_manager.dart @@ -118,8 +118,8 @@ class DataManager { required this.localDataRepository, required this.firebaseFirestore, required this.errorLogger, - SDKPublishModel? publishModel, - }) : _publishModel = publishModel; + this._publishModel, + }); /// Initializes the instance. /// diff --git a/lib/src/model/sdk_publish_model.dart b/lib/src/model/sdk_publish_model.dart index 438f7610..12bd2af0 100644 --- a/lib/src/model/sdk_publish_model.dart +++ b/lib/src/model/sdk_publish_model.dart @@ -426,7 +426,7 @@ class SDKPublishLayout extends PrivacyBase { final String projectId; /// A list of the nodes that makes up this layout. - /// CanvasID -> Node> + /// `CanvasID -> Node>` @CanvasesMapConverter() final Map> canvases; diff --git a/lib/src/transformers/node_transformers/passive_dropdown_transformer.dart b/lib/src/transformers/node_transformers/passive_dropdown_transformer.dart index 0acfdafe..73641045 100644 --- a/lib/src/transformers/node_transformers/passive_dropdown_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_dropdown_transformer.dart @@ -48,7 +48,7 @@ class PassiveDropdownTransformer extends NodeWidgetTransformer { return buildDropdownFromNode(context, node, settings); } - void onTap(context, DropdownNode node) => + void onTap(BuildContext context, DropdownNode node) => FunctionsRepository.triggerAction(context, node: node, TriggerType.click); void onChanged(BuildContext context, DropdownNode node, int index, diff --git a/lib/src/transformers/node_transformers/passive_grid_view_transformer.dart b/lib/src/transformers/node_transformers/passive_grid_view_transformer.dart index 77fe753e..026d8e33 100644 --- a/lib/src/transformers/node_transformers/passive_grid_view_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_grid_view_transformer.dart @@ -4,6 +4,7 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:codelessly_api/codelessly_api.dart'; import 'package:firebase_ui_firestore/firebase_ui_firestore.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; import 'package:provider/provider.dart'; import '../../../codelessly_sdk.dart'; @@ -126,7 +127,7 @@ class PassiveGridViewWidget extends StatelessWidget { key: ValueKey(index), item: IndexedItem( index, - {if (id != null) 'id': id, ...?data}, + {'id': ?id, ...?data}, ), child: Builder( builder: (context) { @@ -244,7 +245,8 @@ class GridViewBuilder extends StatelessWidget { padding: padding, physics: physics, scrollDirection: scrollDirection, - cacheExtent: cacheExtent, + scrollCacheExtent: + cacheExtent == null ? null : ScrollCacheExtent.pixels(cacheExtent!), reverse: reverse, clipBehavior: clipBehavior, itemBuilder: itemBuilder!, diff --git a/lib/src/transformers/node_transformers/passive_list_view_transformer.dart b/lib/src/transformers/node_transformers/passive_list_view_transformer.dart index 0943c7e6..4c787ca2 100644 --- a/lib/src/transformers/node_transformers/passive_list_view_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_list_view_transformer.dart @@ -4,6 +4,7 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:codelessly_api/codelessly_api.dart'; import 'package:firebase_ui_firestore/firebase_ui_firestore.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; import 'package:provider/provider.dart'; import '../../../codelessly_sdk.dart'; @@ -133,7 +134,7 @@ class PassiveListViewWidget extends StatelessWidget { key: ValueKey(index), item: IndexedItem( index, - {if (id != null) 'id': id, ...?data}, + {'id': ?id, ...?data}, ), child: Builder(builder: (context) { // This builder is important to pass a context that has @@ -272,7 +273,9 @@ class ListViewBuilder extends StatelessWidget { padding: padding, physics: physics, scrollDirection: scrollDirection, - cacheExtent: cacheExtent, + scrollCacheExtent: cacheExtent == null + ? null + : ScrollCacheExtent.pixels(cacheExtent!), reverse: reverse, clipBehavior: clipBehavior, itemBuilder: itemBuilder!, @@ -288,7 +291,9 @@ class ListViewBuilder extends StatelessWidget { padding: padding, physics: physics, scrollDirection: scrollDirection, - cacheExtent: cacheExtent, + scrollCacheExtent: cacheExtent == null + ? null + : ScrollCacheExtent.pixels(cacheExtent!), reverse: reverse, itemExtent: itemExtent, clipBehavior: clipBehavior, diff --git a/lib/src/transformers/node_transformers/passive_radio_transformer.dart b/lib/src/transformers/node_transformers/passive_radio_transformer.dart index 94ea69ee..22aa4792 100644 --- a/lib/src/transformers/node_transformers/passive_radio_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_radio_transformer.dart @@ -24,8 +24,9 @@ class PassiveRadioTransformer extends NodeWidgetTransformer { required double width, String value = '', String? groupValue, - WidgetBuildSettings settings = - const WidgetBuildSettings(debugLabel: 'buildPreview'), + WidgetBuildSettings settings = const WidgetBuildSettings( + debugLabel: 'buildPreview', + ), }) { final node = RadioNode( id: '', @@ -69,7 +70,10 @@ class PassiveRadioTransformer extends NodeWidgetTransformer { } Widget buildFromNode( - BuildContext context, RadioNode node, WidgetBuildSettings settings) { + BuildContext context, + RadioNode node, + WidgetBuildSettings settings, + ) { return PassiveRadioWidget( node: node, settings: settings, @@ -79,11 +83,19 @@ class PassiveRadioTransformer extends NodeWidgetTransformer { void onChanged(BuildContext context, RadioNode node, String? value) { NodeStateProvider.setState(context, value); - FunctionsRepository.setPropertyValue(context, - node: node, property: 'groupValue', value: value); + FunctionsRepository.setPropertyValue( + context, + node: node, + property: 'groupValue', + value: value, + ); FunctionsRepository.triggerAction( - context, node: node, TriggerType.click, value: value); + context, + node: node, + TriggerType.click, + value: value, + ); } void onAction(Reaction reaction) { @@ -116,13 +128,15 @@ class PassiveRadioWidget extends StatelessWidget { context, variablesOverrides: variablesOverrides, ); - final value = PropertyValueDelegate.getPropertyValue( + final value = + PropertyValueDelegate.getPropertyValue( node, 'value', scopedValues: scopedValues, ) ?? node.value; - final groupValue = PropertyValueDelegate.getPropertyValue( + final groupValue = + PropertyValueDelegate.getPropertyValue( node, 'groupValue', scopedValues: scopedValues, @@ -136,22 +150,25 @@ class PassiveRadioWidget extends StatelessWidget { height: node.basicBoxLocal.height, child: Transform.scale( scale: scale, - child: Radio( - value: value, + child: RadioGroup( groupValue: groupValue, - autofocus: node.properties.autofocus, - activeColor: node.properties.activeColor.toFlutterColor(), - hoverColor: node.properties.hoverColor.toFlutterColor(), - focusColor: node.properties.focusColor.toFlutterColor(), onChanged: (value) => onChanged?.call(context, value), - toggleable: node.properties.toggleable, - splashRadius: node.properties.splashRadius, - fillColor: WidgetStateProperty.resolveWith((states) { - if (states.contains(WidgetState.selected)) { - return node.properties.activeColor.toFlutterColor(); - } - return node.properties.inactiveColor.toFlutterColor(); - }), + child: Radio( + value: value, + enabled: onChanged != null, + autofocus: node.properties.autofocus, + activeColor: node.properties.activeColor.toFlutterColor(), + hoverColor: node.properties.hoverColor.toFlutterColor(), + focusColor: node.properties.focusColor.toFlutterColor(), + toggleable: node.properties.toggleable, + splashRadius: node.properties.splashRadius, + fillColor: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.selected)) { + return node.properties.activeColor.toFlutterColor(); + } + return node.properties.inactiveColor.toFlutterColor(); + }), + ), ), ), ); diff --git a/lib/src/transformers/node_transformers/passive_rectangle_transformer.dart b/lib/src/transformers/node_transformers/passive_rectangle_transformer.dart index e0cf6ec9..91e8ba2b 100644 --- a/lib/src/transformers/node_transformers/passive_rectangle_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_rectangle_transformer.dart @@ -145,7 +145,7 @@ class PassiveRectangleWidget extends StatelessWidget { node, [ ...children, - if (portalWidget != null) portalWidget, + ?portalWidget, ], stackAlignment: stackAlignment, applyPadding: applyPadding, diff --git a/lib/src/transformers/node_transformers/passive_slider_transformer.dart b/lib/src/transformers/node_transformers/passive_slider_transformer.dart index efe03abc..3f030475 100644 --- a/lib/src/transformers/node_transformers/passive_slider_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_slider_transformer.dart @@ -164,7 +164,7 @@ class TransformerSlider extends StatelessWidget { ) : null, showValueIndicator: node.properties.showLabel - ? ShowValueIndicator.always + ? ShowValueIndicator.onDrag : ShowValueIndicator.never, activeTickMarkColor: node.properties.activeTickMarkColor.toFlutterColor(), diff --git a/lib/src/transformers/node_transformers/passive_switch_transformer.dart b/lib/src/transformers/node_transformers/passive_switch_transformer.dart index 592f6381..7644fda8 100644 --- a/lib/src/transformers/node_transformers/passive_switch_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_switch_transformer.dart @@ -134,7 +134,7 @@ class TransformerSwitch extends StatelessWidget { activeTrackColor: node.properties.activeTrackColor.toFlutterColor(), inactiveTrackColor: node.properties.inactiveTrackColor.toFlutterColor(), - activeColor: node.properties.activeThumbColor.toFlutterColor(), + activeThumbColor: node.properties.activeThumbColor.toFlutterColor(), inactiveThumbColor: node.properties.inactiveThumbColor.toFlutterColor(), hoverColor: node.properties.hoverColor.toFlutterColor(), diff --git a/lib/src/transformers/node_transformers/passive_text_field_transformer.dart b/lib/src/transformers/node_transformers/passive_text_field_transformer.dart index 96acd7b3..6a20b67a 100644 --- a/lib/src/transformers/node_transformers/passive_text_field_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_text_field_transformer.dart @@ -275,8 +275,7 @@ class _PassiveTextFieldWidgetState extends State { : null, inputFormatters: [ if (maxLength != null) LengthLimitingTextInputFormatter(maxLength), - if (properties.formatter.toFlutterFormatter() case var formatter?) - formatter, + ?properties.formatter.toFlutterFormatter(), ], maxLines: widget.node.properties.maxLines.orNullIf(!widget.node.isVerticalWrap), diff --git a/lib/src/transformers/transformer_functions.dart b/lib/src/transformers/transformer_functions.dart index dff06991..8f0d0915 100644 --- a/lib/src/transformers/transformer_functions.dart +++ b/lib/src/transformers/transformer_functions.dart @@ -239,10 +239,10 @@ ButtonStyle createMasterButtonStyle(ButtonNode node, ); case ButtonTypeEnum.icon: Color? primary; - if (buttonColor.toFlutterColor().opacity > 0 && - buttonColor.toFlutterColor().opacity < 0.7) { + if (buttonColor.toFlutterColor().a > 0 && + buttonColor.toFlutterColor().a < 0.7) { primary = buttonColor.toFlutterColor().withValues(alpha: 0.1); - } else if (buttonColor.toFlutterColor().opacity == 0) { + } else if (buttonColor.toFlutterColor().a == 0) { final iconColor = PropertyValueDelegate.getPropertyValue( node, 'iconColor', @@ -319,7 +319,7 @@ Widget? retrieveIconWidget( ); case IconTypeEnum.image: if (icon.iconImage == null) return null; - if (color != null && color.opacity == 0) { + if (color != null && color.a == 0) { // when opacity is zero, we need to display original colors of // the image icon which `ImageIcon` widget cannot do. So we // use the raw Image widget. diff --git a/lib/src/ui/codelessly_context.dart b/lib/src/ui/codelessly_context.dart index 05ee46da..74f96941 100644 --- a/lib/src/ui/codelessly_context.dart +++ b/lib/src/ui/codelessly_context.dart @@ -62,18 +62,17 @@ class CodelesslyContext with ChangeNotifier, Equatable { /// A notifier that is used to rebuild individual nodes. final ValueNotifier nodeRebuildNotifier = ValueNotifier(''); - /// Creates a [CodelesslyContext] with the given [data], [functions], and + /// Creates a [CodelesslyContext] with the given [_data], [functions], and /// [nodeValues]. CodelesslyContext({ - required Map data, + required this._data, required this.functions, required Map? externalComponentBuilders, required this.nodeValues, required this.variables, required this.conditions, required this.layoutID, - }) : _data = data, - _externalComponentBuilders = externalComponentBuilders ?? {}; + }) : _externalComponentBuilders = externalComponentBuilders ?? {}; /// Creates a [CodelesslyContext] with empty an empty map of each property. CodelesslyContext.empty({String? layoutID}) diff --git a/lib/src/ui/codelessly_widget.dart b/lib/src/ui/codelessly_widget.dart index fe70d2b8..7be45dfd 100644 --- a/lib/src/ui/codelessly_widget.dart +++ b/lib/src/ui/codelessly_widget.dart @@ -26,7 +26,7 @@ typedef CodelesslyWidgetErrorBuilder = Widget Function( ErrorLog error, ); -Widget _defaultLayoutBuilder(context, layout) => layout; +Widget _defaultLayoutBuilder(BuildContext context, Widget layout) => layout; /// SDK widget that requires the SDK to be initialized beforehand. /// diff --git a/lib/src/ui/codelessly_widget_controller.dart b/lib/src/ui/codelessly_widget_controller.dart index 64e6d231..fcf43381 100644 --- a/lib/src/ui/codelessly_widget_controller.dart +++ b/lib/src/ui/codelessly_widget_controller.dart @@ -7,7 +7,7 @@ import '../../codelessly_sdk.dart'; import '../logging/debug_logger.dart'; import '../logging/error_logger.dart'; -Widget _defaultLayoutBuilder(context, layout) => layout; +Widget _defaultLayoutBuilder(BuildContext context, Widget layout) => layout; /// The provides advanced explicit control over /// the state of the instantiated [CodelesslyWidget] that it is attached to. diff --git a/lib/src/utils/extensions.dart b/lib/src/utils/extensions.dart index 9e1653b9..cb37b03b 100644 --- a/lib/src/utils/extensions.dart +++ b/lib/src/utils/extensions.dart @@ -302,40 +302,38 @@ extension CornerRadiusHelper on CornerRadius { } extension ColorHelper on flutter.Color { - ColorRGB get colorRGB => - ColorRGB(r: red / 255.0, g: green / 255.0, b: blue / 255.0); + ColorRGB get colorRGB => ColorRGB(r: r, g: g, b: b); - ColorRGBA get colorRGBA => ColorRGBA( - r: red / 255.0, g: green / 255.0, b: blue / 255.0, a: alpha / 255.0); + ColorRGBA get colorRGBA => ColorRGBA(r: r, g: g, b: b, a: a); Color darken([double percent = 10]) { assert(1 <= percent && percent <= 100); var f = 1 - percent / 100; - return Color.fromARGB( - alpha, (red * f).round(), (green * f).round(), (blue * f).round()); + return Color.from(alpha: a, red: r * f, green: g * f, blue: b * f); } // lighten Color lighten([double percent = 10]) { assert(1 <= percent && percent <= 100); var p = percent / 100; - return Color.fromARGB( - alpha, - red + ((255 - red) * p).round(), - green + ((255 - green) * p).round(), - blue + ((255 - blue) * p).round()); + return Color.from( + alpha: a, + red: r + (1 - r) * p, + green: g + (1 - g) * p, + blue: b + (1 - b) * p, + ); } ColorRGB toColorRGB() => colorRGB; ColorRGBA toColorRGBA() => colorRGBA; - String get hex => value.toRadixString(16).padLeft(8, '0').toUpperCase(); + String get hex => toARGB32().toRadixString(16).padLeft(8, '0').toUpperCase(); PaintModel toPaint([String? id]) => PaintModel.solid( id: id ?? generateId(), visible: true, - opacity: opacity, + opacity: a, color: toColorRGB(), ); @@ -376,7 +374,7 @@ extension ColorRGBAHelper on ColorRGBA { PaintModel toPaint([String? id]) => PaintModel.solid( id: id ?? generateId(), visible: true, - opacity: toFlutterColor().opacity, + opacity: toFlutterColor().a, color: toRGB()); } diff --git a/lib/src/utils/image_builder.dart b/lib/src/utils/image_builder.dart index a3591dca..c45ac564 100644 --- a/lib/src/utils/image_builder.dart +++ b/lib/src/utils/image_builder.dart @@ -233,7 +233,7 @@ class _UltimateImageBuilderState extends State { repeat: repeat, color: widget.color, colorBlendMode: colorBlendMode, - errorBuilder: (context, _, __) => + errorBuilder: (context, _, _) => (widget.errorBuilder ?? _defaultErrorBuilder)(context), ); } else { @@ -307,7 +307,7 @@ class _UltimateImageBuilderState extends State { repeat: repeat, color: widget.color, colorBlendMode: colorBlendMode, - errorBuilder: (context, _, __) => + errorBuilder: (context, _, _) => (widget.errorBuilder ?? _defaultErrorBuilder)(context), ); } From dbd017fe4a5c5e3a10dbd575f63c730d5c24b61a Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 13 Oct 2026 02:19:50 +0000 Subject: [PATCH 04/22] Render the remaining API 5 parameters and fix two runtime errors - Radio, Slider, ListTile, CircularProgressIndicator, ExpansionTile and Text now render the parameters added in codelessly_api 5. - Slider track and value indicator shapes are mapped exhaustively; the gapped track, drop and rounded rectangle indicators used to fall back to other shapes. The gapped track also needs SliderThemeData.trackGap, without which Flutter asserts. - ScopedValues.of left a late field unset when no CodelesslyContext was in the tree, so any node widget built outside a layout threw a LateInitializationError. - Add widget smoke tests for the radio and every slider shape. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01A67XoQYsLVvoMtiKcXXUxY --- CHANGELOG.md | 13 ++++ .../passive_expansion_tile_transformer.dart | 1 + .../passive_list_tile_transformer.dart | 2 + ...passive_loading_indicator_transformer.dart | 2 + .../passive_radio_transformer.dart | 9 +++ .../passive_slider_transformer.dart | 59 +++++++++++---- .../transformers/transformer_functions.dart | 4 +- lib/src/transformers/utils/scoped_values.dart | 5 +- lib/src/transformers/utils/text_utils.dart | 1 + lib/src/utils/extensions.dart | 11 +++ test/smoke_test.dart | 72 +++++++++++++++++++ 11 files changed, 160 insertions(+), 19 deletions(-) create mode 100644 test/smoke_test.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index ca44f3c1..649e6e3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,19 @@ `onUnfocus`/`onUserInteractionIfError` autovalidate modes. - Render the new AppBar `foregroundColor`/`scrolledUnderElevation` and TabBar `tabAlignment`/`dividerHeight` properties. +- Render the other parameters added in codelessly_api 5: Radio side, + inner radius and background, Slider value indicator modes, secondary + track and padding, ListTile minimum height and title alignment, + CircularProgressIndicator stroke alignment and track gap, ExpansionTile + trailing icon visibility, and Text soft wrapping. +- Render the gapped slider track and the drop and rounded rectangle value + indicators; these previously fell back to other shapes. +- Replace deprecated Flutter APIs: scrollCacheExtent, RadioGroup, + ShowValueIndicator.onDrag, Switch activeThumbColor and the new Color + component accessors. +- Fix a LateInitializationError when a node widget is built without a + CodelesslyContext above it. +- Fix cache writes whose failures escaped the error handler. ## 6.2.0 diff --git a/lib/src/transformers/node_transformers/passive_expansion_tile_transformer.dart b/lib/src/transformers/node_transformers/passive_expansion_tile_transformer.dart index dc265043..21ca937e 100644 --- a/lib/src/transformers/node_transformers/passive_expansion_tile_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_expansion_tile_transformer.dart @@ -243,6 +243,7 @@ class PassiveExpansionTileWidget extends StatelessWidget { initiallyExpanded: initiallyExpanded ?? node.properties.initiallyExpanded, maintainState: node.properties.maintainState, + showTrailingIcon: node.properties.showTrailingIcon, tilePadding: node.properties.tilePadding?.flutterEdgeInsets, expandedCrossAxisAlignment: node.properties.expandedCrossAxisAlignment.flutterAxis, diff --git a/lib/src/transformers/node_transformers/passive_list_tile_transformer.dart b/lib/src/transformers/node_transformers/passive_list_tile_transformer.dart index 449ae819..684acf11 100644 --- a/lib/src/transformers/node_transformers/passive_list_tile_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_list_tile_transformer.dart @@ -292,6 +292,8 @@ class PassiveListTileWidget extends StatelessWidget { horizontalTitleGap: node.properties.horizontalTitleGap, minVerticalPadding: node.properties.minVerticalPadding, minLeadingWidth: node.properties.minLeadingWidth, + minTileHeight: node.properties.minTileHeight, + titleAlignment: node.properties.titleAlignment?.toFlutter(), title: title == null ? null : titleWidget ?? diff --git a/lib/src/transformers/node_transformers/passive_loading_indicator_transformer.dart b/lib/src/transformers/node_transformers/passive_loading_indicator_transformer.dart index 39dfb175..0102809f 100644 --- a/lib/src/transformers/node_transformers/passive_loading_indicator_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_loading_indicator_transformer.dart @@ -121,6 +121,8 @@ class PassiveLoadingIndicatorWidget extends StatelessWidget { color: props.color.toFlutterColor(), backgroundColor: props.backgroundColor.toFlutterColor(), strokeCap: props.strokeCap.flutterStrokeCap, + strokeAlign: props.strokeAlign, + trackGap: props.trackGap, ), ), ); diff --git a/lib/src/transformers/node_transformers/passive_radio_transformer.dart b/lib/src/transformers/node_transformers/passive_radio_transformer.dart index 22aa4792..6e9d0f38 100644 --- a/lib/src/transformers/node_transformers/passive_radio_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_radio_transformer.dart @@ -162,6 +162,15 @@ class PassiveRadioWidget extends StatelessWidget { focusColor: node.properties.focusColor.toFlutterColor(), toggleable: node.properties.toggleable, splashRadius: node.properties.splashRadius, + side: node.properties.side?.toFlutter(), + innerRadius: node.properties.innerRadius == null + ? null + : WidgetStatePropertyAll(node.properties.innerRadius), + backgroundColor: node.properties.backgroundColor == null + ? null + : WidgetStatePropertyAll( + node.properties.backgroundColor!.toFlutterColor(), + ), fillColor: WidgetStateProperty.resolveWith((states) { if (states.contains(WidgetState.selected)) { return node.properties.activeColor.toFlutterColor(); diff --git a/lib/src/transformers/node_transformers/passive_slider_transformer.dart b/lib/src/transformers/node_transformers/passive_slider_transformer.dart index 3f030475..7a6dd704 100644 --- a/lib/src/transformers/node_transformers/passive_slider_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_slider_transformer.dart @@ -128,17 +128,38 @@ class TransformerSlider extends StatelessWidget { @override Widget build(BuildContext context) { - final SliderTrackShape trackShape; - if (node.properties.trackShape == SliderTrackShapeEnum.rectangle) { - trackShape = const RectangularSliderTrackShape(); - } else { - trackShape = const RoundedRectSliderTrackShape(); - } - - final valueIndicatorShape = node.properties.valueIndicatorShape == - SliderValueIndicatorShape.rectangle - ? const RectangularSliderValueIndicatorShape() - : const PaddleSliderValueIndicatorShape(); + final SliderTrackShape trackShape = switch (node.properties.trackShape) { + SliderTrackShapeEnum.rectangle => const RectangularSliderTrackShape(), + SliderTrackShapeEnum.roundedRectangle => + const RoundedRectSliderTrackShape(), + SliderTrackShapeEnum.gapped => const GappedSliderTrackShape(), + }; + + final SliderComponentShape valueIndicatorShape = + switch (node.properties.valueIndicatorShape) { + SliderValueIndicatorShape.rectangle => + const RectangularSliderValueIndicatorShape(), + SliderValueIndicatorShape.paddle => + const PaddleSliderValueIndicatorShape(), + SliderValueIndicatorShape.drop => const DropSliderValueIndicatorShape(), + SliderValueIndicatorShape.roundedRectangle => + const RoundedRectSliderValueIndicatorShape(), + }; + + final ShowValueIndicator showValueIndicator = + switch (node.properties.showValueIndicator) { + null => node.properties.showLabel + ? ShowValueIndicator.onDrag + : ShowValueIndicator.never, + ShowValueIndicatorC.onlyForDiscrete => ShowValueIndicator.onlyForDiscrete, + ShowValueIndicatorC.onlyForContinuous => + ShowValueIndicator.onlyForContinuous, + ShowValueIndicatorC.onDrag => ShowValueIndicator.onDrag, + ShowValueIndicatorC.alwaysVisible => ShowValueIndicator.alwaysVisible, + ShowValueIndicatorC.never => ShowValueIndicator.never, + }; + final bool showsLabel = + showValueIndicator != ShowValueIndicator.never; return SizedBox( width: node.basicBoxLocal.width, @@ -158,14 +179,19 @@ class TransformerSlider extends StatelessWidget { node.properties.showThumb ? node.properties.thumbRadius : 0, ), trackShape: trackShape, + // GappedSliderTrackShape requires a gap; 6 is the Material 3 + // (2024) default. + trackGap: node.properties.trackShape == SliderTrackShapeEnum.gapped + ? 6 + : null, tickMarkShape: node.properties.isDiscrete ? RoundSliderTickMarkShape( tickMarkRadius: node.properties.tickMarkRadius, ) : null, - showValueIndicator: node.properties.showLabel - ? ShowValueIndicator.onDrag - : ShowValueIndicator.never, + showValueIndicator: showValueIndicator, + secondaryActiveTrackColor: + node.properties.secondaryActiveTrackColor?.toFlutterColor(), activeTickMarkColor: node.properties.activeTickMarkColor.toFlutterColor(), inactiveTickMarkColor: @@ -190,7 +216,10 @@ class TransformerSlider extends StatelessWidget { max: node.properties.max, divisions: node.properties.isDiscrete ? node.properties.divisions : null, - label: node.properties.showLabel ? getSliderLabel(node, value) : null, + label: showsLabel ? getSliderLabel(node, value) : null, + secondaryTrackValue: node.properties.secondaryTrackValue + ?.clamp(node.properties.min, node.properties.max), + padding: node.properties.padding?.flutterEdgeInsets, ), ), ); diff --git a/lib/src/transformers/transformer_functions.dart b/lib/src/transformers/transformer_functions.dart index 8f0d0915..2f9057e2 100644 --- a/lib/src/transformers/transformer_functions.dart +++ b/lib/src/transformers/transformer_functions.dart @@ -292,7 +292,9 @@ OutlinedBorder? getButtonShape(ButtonNode node, ScopedValues scopedValues) { } String? getSliderLabel(SliderNode node, double value) { - if (!node.properties.showLabel) return null; + final showValueIndicator = node.properties.showValueIndicator; + if (showValueIndicator == ShowValueIndicatorC.never) return null; + if (showValueIndicator == null && !node.properties.showLabel) return null; if (node.properties.label.trim().isEmpty) return kSliderDefaultLabel; final String valueString = node.properties.allowFractionalPoints ? value.toStringAsFixed(1) diff --git a/lib/src/transformers/utils/scoped_values.dart b/lib/src/transformers/utils/scoped_values.dart index 43d4cc73..d081987d 100644 --- a/lib/src/transformers/utils/scoped_values.dart +++ b/lib/src/transformers/utils/scoped_values.dart @@ -73,9 +73,8 @@ class ScopedValues { _variablesOverrides = variablesOverrides; _dataOverrides = dataOverrides; final codelesslyContext = context.read(); - if (codelesslyContext != null) { - _codelesslyContextRef = WeakReference(codelesslyContext); - } + _codelesslyContextRef = + codelesslyContext == null ? null : WeakReference(codelesslyContext); indexedItem = IndexedItemProvider.maybeOf(context); nodeState = NodeStateProvider.maybeOf(context)?.state; routeParams = diff --git a/lib/src/transformers/utils/text_utils.dart b/lib/src/transformers/utils/text_utils.dart index 873d795e..7e26a675 100644 --- a/lib/src/transformers/utils/text_utils.dart +++ b/lib/src/transformers/utils/text_utils.dart @@ -215,6 +215,7 @@ class TextUtils { textAlign: textNode.textAlignHorizontal.toFlutter(), maxLines: textNode.maxLines, overflow: textNode.overflow.flutterOverflow, + softWrap: textNode.softWrap, ); } diff --git a/lib/src/utils/extensions.dart b/lib/src/utils/extensions.dart index cb37b03b..fff15c7c 100644 --- a/lib/src/utils/extensions.dart +++ b/lib/src/utils/extensions.dart @@ -1878,6 +1878,17 @@ extension TabBarIndicatorSizeExtensions on TabBarIndicatorSizeEnum { } } +extension ListTileTitleAlignmentCExtensions on ListTileTitleAlignmentC { + ListTileTitleAlignment toFlutter() => switch (this) { + ListTileTitleAlignmentC.threeLine => ListTileTitleAlignment.threeLine, + ListTileTitleAlignmentC.titleHeight => + ListTileTitleAlignment.titleHeight, + ListTileTitleAlignmentC.top => ListTileTitleAlignment.top, + ListTileTitleAlignmentC.center => ListTileTitleAlignment.center, + ListTileTitleAlignmentC.bottom => ListTileTitleAlignment.bottom, + }; +} + extension TabAlignmentCExtensions on TabAlignmentC { /// Returns null for alignments that Flutter's [TabBar] rejects for the /// given scrollability, so it falls back to its default. diff --git a/test/smoke_test.dart b/test/smoke_test.dart new file mode 100644 index 00000000..57a2ba97 --- /dev/null +++ b/test/smoke_test.dart @@ -0,0 +1,72 @@ +import 'package:codelessly_api/codelessly_api.dart'; +import 'package:codelessly_sdk/codelessly_sdk.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +Widget host(Widget child) => MaterialApp( + home: Scaffold(body: Center(child: child)), +); + +void main() { + const settings = WidgetBuildSettings(debugLabel: 'test'); + + testWidgets('radio renders inside a RadioGroup', (tester) async { + final node = RadioNode( + id: 'r', + name: 'r', + basicBoxLocal: NodeBox(0, 0, 40, 40), + value: 'a', + groupValue: 'b', + properties: RadioProperties( + innerRadius: 5, + backgroundColor: ColorRGBA.white, + side: const BorderSideModel(width: 2), + ), + ); + String? changed; + await tester.pumpWidget( + host( + PassiveRadioWidget( + node: node, + onChanged: (_, v) => changed = v, + settings: settings, + ), + ), + ); + expect(find.byType(RadioGroup), findsOneWidget); + expect(tester.takeException(), isNull); + await tester.tap(find.byType(Radio)); + expect(changed, 'a'); + }); + + testWidgets('slider renders every shape and indicator mode', (tester) async { + for (final track in SliderTrackShapeEnum.values) { + for (final indicator in SliderValueIndicatorShape.values) { + final node = SliderNode( + id: 's', + name: 's', + basicBoxLocal: NodeBox(0, 0, 300, 48), + value: 20, + properties: SliderProperties( + trackShape: track, + valueIndicatorShape: indicator, + showValueIndicator: ShowValueIndicatorC.alwaysVisible, + secondaryTrackValue: 50, + padding: const EdgeInsetsModel.all(4), + ), + ); + await tester.pumpWidget( + host( + TransformerSlider( + node: node, + value: 20, + onChanged: (_) {}, + settings: settings, + ), + ), + ); + expect(tester.takeException(), isNull, reason: '$track $indicator'); + } + } + }); +} From 0846530a80430fb0058445ab9c21957ce2fd5a6b Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 13 Oct 2026 02:21:04 +0000 Subject: [PATCH 05/22] Render ExpansionTile shape and collapsedShape Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01A67XoQYsLVvoMtiKcXXUxY --- CHANGELOG.md | 2 +- .../passive_expansion_tile_transformer.dart | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 649e6e3d..017dfd04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,7 @@ inner radius and background, Slider value indicator modes, secondary track and padding, ListTile minimum height and title alignment, CircularProgressIndicator stroke alignment and track gap, ExpansionTile - trailing icon visibility, and Text soft wrapping. + trailing icon visibility and shapes, and Text soft wrapping. - Render the gapped slider track and the drop and rounded rectangle value indicators; these previously fell back to other shapes. - Replace deprecated Flutter APIs: scrollCacheExtent, RadioGroup, diff --git a/lib/src/transformers/node_transformers/passive_expansion_tile_transformer.dart b/lib/src/transformers/node_transformers/passive_expansion_tile_transformer.dart index 21ca937e..04f7942f 100644 --- a/lib/src/transformers/node_transformers/passive_expansion_tile_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_expansion_tile_transformer.dart @@ -244,6 +244,18 @@ class PassiveExpansionTileWidget extends StatelessWidget { initiallyExpanded ?? node.properties.initiallyExpanded, maintainState: node.properties.maintainState, showTrailingIcon: node.properties.showTrailingIcon, + shape: node.properties.shape == null + ? null + : getShape( + shape: node.properties.shape!, + radius: node.properties.cornerRadius, + ), + collapsedShape: node.properties.collapsedShape == null + ? null + : getShape( + shape: node.properties.collapsedShape!, + radius: node.properties.cornerRadius, + ), tilePadding: node.properties.tilePadding?.flutterEdgeInsets, expandedCrossAxisAlignment: node.properties.expandedCrossAxisAlignment.flutterAxis, From cc07bbe2fc50fb7a317bb87f958cfb5c8d7c15ce Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 13 Oct 2026 02:22:56 +0000 Subject: [PATCH 06/22] Render PageView allowImplicitScrolling and Text textScaleFactor Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01A67XoQYsLVvoMtiKcXXUxY --- CHANGELOG.md | 3 ++- .../node_transformers/passive_page_view_transformer.dart | 2 ++ lib/src/transformers/utils/text_utils.dart | 3 +++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 017dfd04..e7f9a088 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,8 @@ inner radius and background, Slider value indicator modes, secondary track and padding, ListTile minimum height and title alignment, CircularProgressIndicator stroke alignment and track gap, ExpansionTile - trailing icon visibility and shapes, and Text soft wrapping. + trailing icon visibility and shapes, Text soft wrapping and scaling, and + PageView implicit scrolling. - Render the gapped slider track and the drop and rounded rectangle value indicators; these previously fell back to other shapes. - Replace deprecated Flutter APIs: scrollCacheExtent, RadioGroup, diff --git a/lib/src/transformers/node_transformers/passive_page_view_transformer.dart b/lib/src/transformers/node_transformers/passive_page_view_transformer.dart index 74784d1c..0a0bbafb 100644 --- a/lib/src/transformers/node_transformers/passive_page_view_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_page_view_transformer.dart @@ -122,6 +122,8 @@ class _PassivePageViewWidgetState extends State { reverse: widget.node.reverse, clipBehavior: widget.node.clipsContent ? Clip.hardEdge : Clip.none, padEnds: widget.node.properties.padEnds, + allowImplicitScrolling: + widget.node.properties.allowImplicitScrolling, pageSnapping: widget.node.properties.pageSnapping, controller: controller, onPageChanged: (index) => widget.onPageChanged?.call(context, index), diff --git a/lib/src/transformers/utils/text_utils.dart b/lib/src/transformers/utils/text_utils.dart index 7e26a675..9c8d35f8 100644 --- a/lib/src/transformers/utils/text_utils.dart +++ b/lib/src/transformers/utils/text_utils.dart @@ -216,6 +216,9 @@ class TextUtils { maxLines: textNode.maxLines, overflow: textNode.overflow.flutterOverflow, softWrap: textNode.softWrap, + textScaler: textNode.textScaleFactor == null + ? null + : TextScaler.linear(textNode.textScaleFactor!), ); } From b2ef500ac8e906b39ed0a5940affd571c1781fbf Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 13 Oct 2026 02:30:35 +0000 Subject: [PATCH 07/22] Use the web webview implementation under WebAssembly dart.library.html is false when compiling to Wasm, so the conditional export fell back to the stub and web views never rendered there. Check dart.library.js_interop, which is true for both JS and Wasm builds. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01A67XoQYsLVvoMtiKcXXUxY --- lib/src/transformers/web/web_webview_platform.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/transformers/web/web_webview_platform.dart b/lib/src/transformers/web/web_webview_platform.dart index d3dfd29b..6f562cbc 100644 --- a/lib/src/transformers/web/web_webview_platform.dart +++ b/lib/src/transformers/web/web_webview_platform.dart @@ -1,4 +1,4 @@ library; export 'web_webview_platform_stub.dart' - if (dart.library.html) 'web_webview_platform_web.dart'; + if (dart.library.js_interop) 'web_webview_platform_web.dart'; From ff8cafa80feb62f2a63e04dc4976abb7a390ca75 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 13 Oct 2026 02:38:39 +0000 Subject: [PATCH 08/22] Fix crashes and hangs in auth, startup, data streams and widgets Auth - Windows decoded IdTokenResult.toString() instead of the token, so claim checks always failed there. - Waiting for project claims had no timeout and ignored errors from getIdTokenResult, so authentication could hang forever. Wait at most 30s and propagate errors. - reset() after dispose added to a closed stream; authenticate() force-unwrapped a missing token. - Stop logging the project auth token and the auth response body. Data - A publish model stream error never completed the first-event future, so initialization waited forever (e.g. on a permission error). - Background bundle refreshes and onLoad API calls failed as unhandled async errors; report them instead. - Cloud variables kept every previous Firestore listener alive, so older queries overwrote newer results. Keep one listener per variable. - Read font bytes with ByteData.sublistView. Widgets - CodelesslyWidget.didUpdateWidget threw when an explicit context was removed, adopted (and later disposed) the caller's controller when it was removed, wrote notifiers after unmount, and threw on a missing breakpoint. - Non-blocking reactions' errors escaped as uncaught errors; a failing blocking action now stops its chain and is reported. - PageView jumped before its controller had clients; TabBar asserted when tabs shrank below the initial index; Accordion ignored isExpanded, inverted enabled, and threw with fewer than two children. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01A67XoQYsLVvoMtiKcXXUxY --- lib/src/auth/auth_manager.dart | 89 +++++++++++-------- lib/src/data/cloud_database.dart | 33 ++++--- lib/src/data/data_manager.dart | 22 ++++- lib/src/functions/functions_repository.dart | 33 ++++++- .../passive_accordion_transformer.dart | 33 ++++--- .../passive_page_view_transformer.dart | 16 +++- .../passive_tab_bar_transformer.dart | 6 +- lib/src/ui/codelessly_widget.dart | 33 +++---- lib/src/ui/layout_builder.dart | 23 ++++- 9 files changed, 208 insertions(+), 80 deletions(-) diff --git a/lib/src/auth/auth_manager.dart b/lib/src/auth/auth_manager.dart index 3ebf970f..7d1aa332 100644 --- a/lib/src/auth/auth_manager.dart +++ b/lib/src/auth/auth_manager.dart @@ -1,6 +1,5 @@ import 'dart:async'; import 'dart:convert'; -import 'dart:io'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/foundation.dart'; @@ -189,7 +188,7 @@ class AuthManager { DebugLogger.instance.printFunction('reset()', name: name); DebugLogger.instance.printInfo('Invalidating...', name: name); _authData = null; - _authStreamController.add(_authData); + if (!_disposed) _authStreamController.add(_authData); _idTokenChangeListener?.cancel(); } @@ -237,7 +236,8 @@ class AuthManager { // https://github.com/firebase/flutterfire/issues/11768#issuecomment-1888363494 // A temporary fix for windows platform until the issue is resolved. if (!kIsWeb && defaultTargetPlatform == TargetPlatform.windows) { - claims = JwtDecoder.decode(result.toString()); + final String? token = result.token; + claims = token == null ? {} : JwtDecoder.decode(token); } else { claims = result.claims ?? {}; } @@ -270,6 +270,10 @@ class AuthManager { return projectIds.contains(projectId); } + /// How long [postAuthSuccess] waits for the project claim to appear on the + /// user's token before failing authentication. + static const Duration claimsPropagationTimeout = Duration(seconds: 30); + /// This method is called after successful authentication of the token. /// It checks if the user has access to the project via token claims and if /// not, it listens to Firebase Auth user changes until the claim appears @@ -314,37 +318,50 @@ class AuthManager { final Completer completer = Completer(); // Listen to Firebase Auth state changes. - _idTokenChangeListener = - firebaseAuth.userChanges().listen((User? event) async { - // If the completer is already completed, return. - // We check this because getIdTokenResult may take a while to complete - // and this userChanges() event may have already triggered a new event. - if (completer.isCompleted) return; - - DebugLogger.instance.printInfo( - 'Firebase Auth state changed. Checking claims...', - name: name); - _idTokenResult = await event?.getIdTokenResult(true); + _idTokenChangeListener = firebaseAuth.userChanges().listen( + (User? event) async { + // If the completer is already completed, return. + // We check this because getIdTokenResult may take a while to complete + // and this userChanges() event may have already triggered a new event. + if (completer.isCompleted) return; - // If the completer is already completed, return. - // We check this because getIdTokenResult may take a while to complete - // and this userChanges() event may have already triggered a new event. - if (completer.isCompleted) return; - - // Check if the user has access to the project. - if (checkClaimsForProject(_idTokenResult, authData.projectId)) { DebugLogger.instance.printInfo( - 'User has access to project since the claim exists. Completing Firebase Auth process.', + 'Firebase Auth state changed. Checking claims...', name: name); - completer.complete(event); - } - }); + try { + _idTokenResult = await event?.getIdTokenResult(true); + } catch (e, stackTrace) { + if (!completer.isCompleted) completer.completeError(e, stackTrace); + return; + } - // Wait for the completer to complete. - await completer.future; + // If the completer is already completed, return. + // We check this because getIdTokenResult may take a while to complete + // and this userChanges() event may have already triggered a new event. + if (completer.isCompleted) return; + + // Check if the user has access to the project. + if (checkClaimsForProject(_idTokenResult, authData.projectId)) { + DebugLogger.instance.printInfo( + 'User has access to project since the claim exists. Completing Firebase Auth process.', + name: name); + completer.complete(event); + } + }, + onError: (Object e, StackTrace stackTrace) { + if (!completer.isCompleted) completer.completeError(e, stackTrace); + }, + ); - // Cancel the listener for Firebase Auth state changes. - _idTokenChangeListener?.cancel(); + // Wait for the claim to arrive, but not forever: a failure here must + // surface as an authentication error instead of hanging initialization. + try { + await completer.future.timeout(claimsPropagationTimeout); + } finally { + // Cancel the listener for Firebase Auth state changes. + await _idTokenChangeListener?.cancel(); + _idTokenChangeListener = null; + } DebugLogger.instance.printInfo( 'Auth state changed successfully as expected. Firebase Auth complete.', @@ -358,8 +375,12 @@ class AuthManager { try { DebugLogger.instance.printInfo('Authenticating token...', name: name); + final String? userToken = _idTokenResult?.token; + if (userToken == null) { + throw StateError('Cannot authenticate without a Firebase ID token.'); + } final AuthData? authData = await verifyProjectAuthToken( - userToken: _idTokenResult!.token!, + userToken: userToken, config: config, client: client, postSuccess: postAuthSuccess, @@ -391,7 +412,7 @@ class AuthManager { message: 'Failed to authenticate token', type: 'auth_failed'); return; } - } on SocketException catch (e, str) { + } on http.ClientException catch (e, str) { ErrorLogger.instance.captureException(e, message: 'Network error during authentication', type: 'network_error', @@ -427,7 +448,7 @@ class AuthManager { required Future Function(AuthData authData) postSuccess, }) async { DebugLogger.instance.printFunction( - 'verifyProjectAuthToken(authToken: ${config.authToken}, slug: ${config.slug})', + 'verifyProjectAuthToken(slug: ${config.slug})', name: name, ); @@ -463,15 +484,13 @@ class AuthManager { // success. await postSuccess(authData); - DebugLogger.instance - .printInfo('Auth token response:\n${result.body}', name: name); return authData; } else { // If the status code of the response is not 200, the authentication failed. // Log the status code, reason, and body of the response. DebugLogger.instance.printInfo( - 'Failed to authenticate token.\nStatus Code: ${result.statusCode}.\nReason: ${result.reasonPhrase}\nBody: ${result.body}', + 'Failed to authenticate token.\nStatus Code: ${result.statusCode}.\nReason: ${result.reasonPhrase}', name: name); } } catch (e, stacktrace) { diff --git a/lib/src/data/cloud_database.dart b/lib/src/data/cloud_database.dart index 0557f724..5de6dea9 100644 --- a/lib/src/data/cloud_database.dart +++ b/lib/src/data/cloud_database.dart @@ -207,8 +207,25 @@ class FirestoreCloudDatabase extends CloudDatabase { /// Reference to the Firestore instance. final FirebaseFirestore firestore; - /// Subscriptions to the streams that are being listened to. - final List _subscriptions = []; + /// Active cloud streams keyed by the id of the variable they update. A + /// variable only follows its latest query, so starting a new stream for it + /// cancels the previous one. + final Map _subscriptions = {}; + + void _replaceSubscription( + Observable variable, + StreamSubscription subscription, + ) { + _subscriptions.remove(variable.value.id)?.cancel(); + _subscriptions[variable.value.id] = subscription; + } + + void _cancelSubscriptions() { + for (final subscription in _subscriptions.values) { + subscription.cancel(); + } + _subscriptions.clear(); + } /// Creates a [FirestoreCloudDatabase] with the given [identifier] and /// [firestore] instance. @@ -494,20 +511,17 @@ class FirestoreCloudDatabase extends CloudDatabase { }, ); - // Add subscription to the list of subscriptions. - _subscriptions.add(subscription); + _replaceSubscription(variable, subscription); } @override void reset() { - // Cancel all subscriptions. - _subscriptions.forEach((sub) => sub.cancel()); + _cancelSubscriptions(); } @override void dispose() { - // Cancel all subscriptions. - _subscriptions.forEach((sub) => sub.cancel()); + _cancelSubscriptions(); super.dispose(); } @@ -580,8 +594,7 @@ class FirestoreCloudDatabase extends CloudDatabase { }, ); - // Add subscription to the list of subscriptions. - _subscriptions.add(subscription); + _replaceSubscription(variable, subscription); } } diff --git a/lib/src/data/data_manager.dart b/lib/src/data/data_manager.dart index 45d27b4e..f17bd576 100644 --- a/lib/src/data/data_manager.dart +++ b/lib/src/data/data_manager.dart @@ -208,6 +208,18 @@ class DataManager { if (_publishModel == null) { await publishBundleFuture; + } else { + // Background refresh: report failures instead of leaving them as + // unhandled async errors. + unawaited(publishBundleFuture.catchError((Object e, StackTrace st) { + errorLogger.captureException( + e, + message: + 'Failed to refresh publish bundle for slug $slug in background.', + type: 'bundle_download_failed', + stackTrace: st, + ); + })); } _logTime(stopwatch); @@ -344,6 +356,11 @@ class DataManager { // in cache, or it's going to be updated with new data. final Future firstPublishEvent = listenToPublishModel(authData.projectId); + if (_publishModel != null) { + // The cached model is used and the first event is not awaited. Its + // errors are already reported by the stream listener. + firstPublishEvent.ignore(); + } // If the publish model is still null, then we need to wait for the first // publish model to arrive from the server via the stream above. @@ -750,6 +767,9 @@ class DataManager { type: 'publish_model_stream_failed', stackTrace: str, ); + // Without this, initialization would wait forever for a first event + // that never comes, e.g. on a Firestore permission error. + if (!completer.isCompleted) completer.completeError(error, str); }); return completer.future; @@ -1590,7 +1610,7 @@ class DataManager { name: name); final FontLoader fontLoader = FontLoader(font.fullFontName); - fontLoader.addFont(Future.value(ByteData.view(fontBytes.buffer))); + fontLoader.addFont(Future.value(ByteData.sublistView(fontBytes))); return fontLoader.load().then((_) { DebugLogger.instance.printInfo( diff --git a/lib/src/functions/functions_repository.dart b/lib/src/functions/functions_repository.dart index 8ec65101..a246e9e0 100644 --- a/lib/src/functions/functions_repository.dart +++ b/lib/src/functions/functions_repository.dart @@ -14,6 +14,7 @@ import '../../codelessly_sdk.dart'; import '../logging/stat_tracker.dart'; import '../ui/codelessly_dialog_widget.dart'; import '../logging/debug_logger.dart'; +import '../logging/error_logger.dart'; /// Enum representing the types of API requests. enum ApiRequestType { @@ -1012,19 +1013,47 @@ ${response.body.contains('{') ? const JsonEncoder.withIndent(' ').convert(json. ); if (!reaction.action.nonBlocking) { // Await only if this action is not a non-blocking. It must not be - // awaited if it is non-blocking. - await future; + // awaited if it is non-blocking. A failed action stops the chain, + // since later actions usually depend on it. + try { + await future; + } catch (e, stackTrace) { + _reportActionError(reaction, e, stackTrace); + return true; + } } else { DebugLogger.instance.printInfo( 'Skipping awaiting for action ${reaction.name} as it is non-blocking...', name: name, ); + if (future is Future) { + unawaited( + future.then( + (_) {}, + onError: (Object e, StackTrace stackTrace) => + _reportActionError(reaction, e, stackTrace), + ), + ); + } } } return true; } + static void _reportActionError( + Reaction reaction, + Object error, + StackTrace stackTrace, + ) { + ErrorLogger.instance.captureException( + error, + message: 'Action ${reaction.name} failed', + type: 'action_failed', + stackTrace: stackTrace, + ); + } + static void callFunction(BuildContext context, CallFunctionAction action) { final ScopedValues scopedValues = ScopedValues.of(context); final CodelesslyContext codelesslyContext = diff --git a/lib/src/transformers/node_transformers/passive_accordion_transformer.dart b/lib/src/transformers/node_transformers/passive_accordion_transformer.dart index e190e294..573aea3f 100644 --- a/lib/src/transformers/node_transformers/passive_accordion_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_accordion_transformer.dart @@ -11,14 +11,26 @@ class PassiveAccordionTransformer extends NodeWidgetTransformer { AccordionNode node, BuildContext context, WidgetBuildSettings settings) { return PassiveAccordionWidget( node: node, - topWidget: manager.buildWidgetFromNode(getNode(node.children[0]), context, - settings: settings), - bottomWidget: manager.buildWidgetFromNode( - getNode(node.children[1]), context, - settings: settings), + isInitiallyExpanded: node.properties.isExpanded, + topWidget: _buildChild(node, 0, context, settings), + bottomWidget: _buildChild(node, 1, context, settings), ); } + /// Builds the accordion's [index]th child, or nothing while the accordion + /// is still missing it. + Widget _buildChild( + AccordionNode node, + int index, + BuildContext context, + WidgetBuildSettings settings, + ) { + final String? childId = node.children.elementAtOrNull(index); + if (childId == null) return const SizedBox.shrink(); + return manager.buildWidgetFromNode(getNode(childId), context, + settings: settings); + } + Widget buildPreview({ required BuildContext context, AccordionNode? node, @@ -40,12 +52,9 @@ class PassiveAccordionTransformer extends NodeWidgetTransformer { ); return PassiveAccordionWidget( enabled: enabled, - topWidget: title ?? - manager.buildWidgetFromNode(getNode(node.children[0]), context, - settings: settings), - bottomWidget: content ?? - manager.buildWidgetFromNode(getNode(node.children[1]), context, - settings: settings), + isInitiallyExpanded: node.properties.isExpanded, + topWidget: title ?? _buildChild(node, 0, context, settings), + bottomWidget: content ?? _buildChild(node, 1, context, settings), node: node, ); } @@ -104,7 +113,7 @@ class _AccordionWidgetState extends State { height: 48, child: TextButton( onPressed: () { - if (widget.enabled) return; + if (!widget.enabled) return; setState(() => isExpanded = !isExpanded); }, child: Icon( diff --git a/lib/src/transformers/node_transformers/passive_page_view_transformer.dart b/lib/src/transformers/node_transformers/passive_page_view_transformer.dart index 0a0bbafb..5d071c9d 100644 --- a/lib/src/transformers/node_transformers/passive_page_view_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_page_view_transformer.dart @@ -69,7 +69,7 @@ class _PassivePageViewWidgetState extends State { super.didUpdateWidget(oldWidget); if (widget.node.properties.initialPage != oldWidget.node.properties.initialPage) { - controller.jumpToPage(widget.node.properties.initialPage); + _jumpToPage(widget.node.properties.initialPage); } final ScopedValues scopedValues = ScopedValues.of(context); @@ -87,11 +87,23 @@ class _PassivePageViewWidgetState extends State { ); if (currentPropertyValue != null && controller.page?.round() != currentPropertyValue) { - controller.jumpToPage(currentPropertyValue); + _jumpToPage(currentPropertyValue); } } } + /// Jumps once the controller is attached; didUpdateWidget can run before + /// the page view has laid out, when jumpToPage would throw. + void _jumpToPage(int page) { + if (controller.hasClients) { + controller.jumpToPage(page); + return; + } + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted && controller.hasClients) controller.jumpToPage(page); + }); + } + @override Widget build(BuildContext context) { if (widget.node.children.isEmpty) { diff --git a/lib/src/transformers/node_transformers/passive_tab_bar_transformer.dart b/lib/src/transformers/node_transformers/passive_tab_bar_transformer.dart index 2ebad1e6..ab648056 100644 --- a/lib/src/transformers/node_transformers/passive_tab_bar_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_tab_bar_transformer.dart @@ -106,6 +106,10 @@ class _PassiveTabBarWidgetState extends State late int initialIndex; late int length; + /// TabController asserts on an index outside the tabs, which happens when + /// data-bound tabs shrink below the configured initial index. + int get _clampedIndex => length == 0 ? 0 : initialIndex.clamp(0, length - 1); + @override void initState() { super.initState(); @@ -114,7 +118,7 @@ class _PassiveTabBarWidgetState extends State controller = TabController( length: widget.node.properties.tabs.length, vsync: this, - initialIndex: initialIndex, + initialIndex: _clampedIndex, ); } diff --git a/lib/src/ui/codelessly_widget.dart b/lib/src/ui/codelessly_widget.dart index 7be45dfd..f1c3f26f 100644 --- a/lib/src/ui/codelessly_widget.dart +++ b/lib/src/ui/codelessly_widget.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:provider/provider.dart'; @@ -345,12 +346,14 @@ class _CodelesslyWidgetState extends State { // Breakpoint changed. Everything needs to be reloaded. canvasID = newCanvasID; final breakpoint = _effectiveController - .publishModel!.layouts[effectiveLayoutID!]!.breakpoints - .firstWhere((breakpoint) => breakpoint.nodeId == canvasID); + .publishModel!.layouts[effectiveLayoutID!]?.breakpoints + .firstWhereOrNull((breakpoint) => breakpoint.nodeId == canvasID); // Notify breakpoints listeners. - _effectiveController.effectiveCodelessly - .notifyBreakpointsListeners(context, breakpoint); + if (breakpoint != null) { + _effectiveController.effectiveCodelessly + .notifyBreakpointsListeners(context, breakpoint); + } } } } @@ -364,13 +367,10 @@ class _CodelesslyWidgetState extends State { widget.codelesslyContext == null) { // explicitly provided context is removed codelesslyContext = codelesslyContext.copyWith(); - } else if (oldWidget.codelesslyContext == null && - widget.codelesslyContext != null) { - // explicitly provided context is added + } else { + // explicitly provided context is added or replaced codelesslyContext = widget.codelesslyContext!; } - // explicitly provided context is updated - codelesslyContext = widget.codelesslyContext!; } if (widget.data != codelesslyContext.data) { @@ -379,6 +379,7 @@ class _CodelesslyWidgetState extends State { // the build method is called since setting the data will trigger a // rebuild and it will crash if this happens when a rebuild is ongoing. SchedulerBinding.instance.addPostFrameCallback((timeStamp) { + if (!mounted) return; for (final notifier in codelesslyContext.variables.values) { notifier.value = notifier.value.copyWith(value: widget.data[notifier.value.name]); @@ -397,15 +398,17 @@ class _CodelesslyWidgetState extends State { } if (widget.controller == null && oldWidget.controller != null) { - _controller = oldWidget.controller ?? createDefaultController(); + // The external controller was removed. It belongs to the caller, so + // create and own a new internal one instead of adopting it. + _controller = createDefaultController(); + _effectiveController.initialize(); + codelesslyContext.layoutID = _effectiveController.layoutID; } else if (widget.controller != null && oldWidget.controller == null) { _controller!.dispose(); _controller = null; - } - - // If the controller is intrinsic, then we need to dispose of the old one - // and create a new one if any of the values change. - if (widget.controller == null) { + } else if (widget.controller == null) { + // If the controller is intrinsic, then we need to dispose of the old one + // and create a new one if any of the values change. if (widget.publishSource != oldWidget.publishSource || widget.layoutID != oldWidget.layoutID || widget.codelessly != oldWidget.codelessly) { diff --git a/lib/src/ui/layout_builder.dart b/lib/src/ui/layout_builder.dart index 04e0f412..2ae3a249 100644 --- a/lib/src/ui/layout_builder.dart +++ b/lib/src/ui/layout_builder.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:codelessly_api/codelessly_api.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; @@ -5,6 +7,7 @@ import 'package:provider/provider.dart'; import '../../codelessly_sdk.dart'; import '../logging/debug_logger.dart'; +import '../logging/error_logger.dart'; /// A widget that retrieves a layout from a [CodelesslyWidgetController]. class CodelesslyLayoutRetriever extends StatefulWidget { @@ -284,8 +287,24 @@ class _CodelesslyLayoutBuilderState extends State { 'Making api request for onLoad canvas action', name: CodelesslyLayoutRetriever.name, ); - FunctionsRepository.makeApiRequestFromAction( - canvasAction, context, notifier); + // The variable carries the error state; report the failure instead + // of leaving it as an unhandled async error. + unawaited( + FunctionsRepository.makeApiRequestFromAction( + canvasAction, + context, + notifier, + ).then( + (_) {}, + onError: (Object e, StackTrace stackTrace) => + ErrorLogger.instance.captureException( + e, + message: 'onLoad API call failed', + type: 'api_call_failed', + stackTrace: stackTrace, + ), + ), + ); } } } From 4832bfdedac0b6cf9bcfbc90a78098db80225092 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 13 Oct 2026 02:42:03 +0000 Subject: [PATCH 09/22] Render Material Symbols and Cupertino icons Icons other than Material icons had no source and requested SvgPicture.network(''), so they never showed; two-tone Material icons built "materialiconstwo tone" into their URL. - Material Symbols load Google's prebuilt SVGs, with fill, weight, grade and optical size from the icon model snapped to the published variants (verified against fonts.gstatic.com). - Cupertino icons render from the cupertino_icons font through a generated const map from codepoint to CupertinoIcons, which keeps icon tree shaking working for apps (tool/generate_cupertino_icons.py). - Icons without any source render an empty box instead of a failing network request. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01A67XoQYsLVvoMtiKcXXUxY --- lib/codelessly_sdk.dart | 1 + .../transformers/transformer_functions.dart | 4 + .../widget_node_transformer_manager.dart | 41 +- .../utils/cupertino_icons_by_codepoint.dart | 1243 +++++++++++++++++ lib/src/utils/extensions.dart | 60 +- pubspec.yaml | 1 + test/icon_test.dart | 106 ++ tool/generate_cupertino_icons.py | 39 + 8 files changed, 1471 insertions(+), 24 deletions(-) create mode 100644 lib/src/utils/cupertino_icons_by_codepoint.dart create mode 100644 test/icon_test.dart create mode 100644 tool/generate_cupertino_icons.py diff --git a/lib/codelessly_sdk.dart b/lib/codelessly_sdk.dart index dfe64f0d..9e16e0f4 100644 --- a/lib/codelessly_sdk.dart +++ b/lib/codelessly_sdk.dart @@ -30,6 +30,7 @@ export 'src/ui/codelessly_context.dart'; export 'src/ui/codelessly_widget.dart'; export 'src/ui/codelessly_widget_controller.dart'; export 'src/ui/stripe_painter.dart'; +export 'src/utils/cupertino_icons_by_codepoint.dart'; export 'src/utils/extensions.dart'; export 'src/utils/flutter_icons_data_map.dart'; export 'src/utils/flutter_icons_names_map.dart'; diff --git a/lib/src/transformers/transformer_functions.dart b/lib/src/transformers/transformer_functions.dart index 2f9057e2..5ed1fa3f 100644 --- a/lib/src/transformers/transformer_functions.dart +++ b/lib/src/transformers/transformer_functions.dart @@ -318,6 +318,10 @@ Widget? retrieveIconWidget( icon: icon.icon!, size: size ?? icon.size ?? kDefaultIconSize, color: color, + fill: icon.fill, + weight: icon.weight, + grade: icon.grade, + opticalSize: icon.opticalSize, ); case IconTypeEnum.image: if (icon.iconImage == null) return null; diff --git a/lib/src/transformers/widget_node_transformer_manager.dart b/lib/src/transformers/widget_node_transformer_manager.dart index 3b293784..bb5a77b1 100644 --- a/lib/src/transformers/widget_node_transformer_manager.dart +++ b/lib/src/transformers/widget_node_transformer_manager.dart @@ -336,18 +336,30 @@ class AdaptiveSizeFitBox extends StatelessWidget { } } +/// Renders an [IconModel]: Material icons and symbols from Google's prebuilt +/// SVGs, Cupertino icons from the cupertino_icons font. class SvgIcon extends StatelessWidget { final IconModel icon; final Color? color; final double? size; final BoxFit fit; + /// Material Symbols axes; see [IconModelExt.toSvgUrl]. + final double? fill; + final double? weight; + final double? grade; + final double? opticalSize; + const SvgIcon({ super.key, required this.icon, this.color, this.size, this.fit = BoxFit.contain, + this.fill, + this.weight, + this.grade, + this.opticalSize, }); @override @@ -368,21 +380,32 @@ class SvgIcon extends StatelessWidget { ? Colors.black : Colors.white, }; - final url = icon.toSvgUrl(); + final double dimension = size ?? Theme.of(context).iconTheme.size ?? 24; - if (url != null) { - // log('SVG ICON URL: $url'); - } else { - log('SVG ICON URL: 404 | name: ${icon.name} | type: ${icon.type} | ' + final IconData? cupertinoIcon = icon is CupertinoIcon + ? cupertinoIconsByCodepoint[icon.codepoint] + : null; + if (cupertinoIcon != null) { + return Icon(cupertinoIcon, size: dimension, color: iconColor); + } + + final url = icon.toSvgUrl( + fill: fill, + weight: weight, + grade: grade, + opticalSize: opticalSize, + ); + if (url == null) { + log('No icon source | name: ${icon.name} | type: ${icon.type} | ' 'codepoint: ${icon.codepoint} | fontFamily: ${icon.fontFamily} | ' - 'fontPackage: ${icon.fontPackage}${icon is MaterialIcon ? ' | ' - 'style: ${(icon as MaterialIcon).style}' : ''}'); + 'fontPackage: ${icon.fontPackage}'); + return SizedBox.square(dimension: dimension); } return SizedBox.square( - dimension: size ?? Theme.of(context).iconTheme.size ?? 24, + dimension: dimension, child: SvgPicture.network( - url ?? '', + url, fit: fit, colorFilter: ColorFilter.mode(iconColor, BlendMode.srcIn), ), diff --git a/lib/src/utils/cupertino_icons_by_codepoint.dart b/lib/src/utils/cupertino_icons_by_codepoint.dart new file mode 100644 index 00000000..450f0b75 --- /dev/null +++ b/lib/src/utils/cupertino_icons_by_codepoint.dart @@ -0,0 +1,1243 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND. +// Generated by tool/generate_cupertino_icons.py from Flutter's +// CupertinoIcons. + +import 'package:flutter/cupertino.dart'; + +/// Flutter's [CupertinoIcons] keyed by codepoint. +const Map cupertinoIconsByCodepoint = { + 0xf111: CupertinoIcons.battery_charging, + 0xf112: CupertinoIcons.battery_empty, + 0xf113: CupertinoIcons.battery_full, + 0xf114: CupertinoIcons.battery_75_percent, + 0xf115: CupertinoIcons.battery_25_percent, + 0xf116: CupertinoIcons.bluetooth, + 0xf21c: CupertinoIcons.restart, + 0xf21d: CupertinoIcons.reply_all, + 0xf21e: CupertinoIcons.reply_thick_solid, + 0xf220: CupertinoIcons.share_up, + 0xf221: CupertinoIcons.shuffle_thick, + 0xf2bf: CupertinoIcons.pen, + 0xf2c1: CupertinoIcons.car_detailed, + 0xf2d7: CupertinoIcons.clear_thick, + 0xf2f7: CupertinoIcons.gear_big, + 0xf35d: CupertinoIcons.down_arrow, + 0xf366: CupertinoIcons.up_arrow, + 0xf36d: CupertinoIcons.bus, + 0xf36e: CupertinoIcons.clear_thick_circled, + 0xf36f: CupertinoIcons.car, + 0xf37d: CupertinoIcons.fullscreen_exit, + 0xf37e: CupertinoIcons.pencil, + 0xf37f: CupertinoIcons.delete_simple, + 0xf386: CupertinoIcons.fullscreen, + 0xf38a: CupertinoIcons.folder_open, + 0xf3a8: CupertinoIcons.refresh_thick, + 0xf3af: CupertinoIcons.train_style_one, + 0xf3b4: CupertinoIcons.train_style_two, + 0xf3b7: CupertinoIcons.volume_down, + 0xf3b8: CupertinoIcons.volume_mute, + 0xf3b9: CupertinoIcons.volume_off, + 0xf3ba: CupertinoIcons.volume_up, + 0xf3c9: CupertinoIcons.collections, + 0xf3ca: CupertinoIcons.collections_solid, + 0xf3cf: CupertinoIcons.back, + 0xf3d1: CupertinoIcons.forward, + 0xf3d2: CupertinoIcons.left_chevron, + 0xf3d3: CupertinoIcons.right_chevron, + 0xf3e1: CupertinoIcons.bell, + 0xf3e2: CupertinoIcons.bell_solid, + 0xf3e7: CupertinoIcons.book, + 0xf3e8: CupertinoIcons.book_solid, + 0xf3e9: CupertinoIcons.bookmark, + 0xf3ea: CupertinoIcons.bookmark_solid, + 0xf3f5: CupertinoIcons.photo_camera, + 0xf3f6: CupertinoIcons.photo_camera_solid, + 0xf3f7: CupertinoIcons.shopping_cart, + 0xf3fb: CupertinoIcons.conversation_bubble, + 0xf3fd: CupertinoIcons.check_mark, + 0xf3fe: CupertinoIcons.check_mark_circled, + 0xf3ff: CupertinoIcons.check_mark_circled_solid, + 0xf400: CupertinoIcons.circle_filled, + 0xf401: CupertinoIcons.circle, + 0xf402: CupertinoIcons.time, + 0xf403: CupertinoIcons.time_solid, + 0xf404: CupertinoIcons.clear, + 0xf405: CupertinoIcons.clear_circled, + 0xf406: CupertinoIcons.clear_circled_solid, + 0xf411: CupertinoIcons.settings, + 0xf412: CupertinoIcons.settings_solid, + 0xf417: CupertinoIcons.create, + 0xf419: CupertinoIcons.profile_circled, + 0xf422: CupertinoIcons.mail, + 0xf423: CupertinoIcons.mail_solid, + 0xf424: CupertinoIcons.eye, + 0xf425: CupertinoIcons.eye_solid, + 0xf42c: CupertinoIcons.flag, + 0xf430: CupertinoIcons.lab_flask, + 0xf431: CupertinoIcons.lab_flask_solid, + 0xf434: CupertinoIcons.folder, + 0xf435: CupertinoIcons.folder_solid, + 0xf43a: CupertinoIcons.game_controller, + 0xf43b: CupertinoIcons.game_controller_solid, + 0xf43c: CupertinoIcons.gear, + 0xf43d: CupertinoIcons.gear_solid, + 0xf442: CupertinoIcons.heart, + 0xf443: CupertinoIcons.heart_solid, + 0xf447: CupertinoIcons.home, + 0xf449: CupertinoIcons.loop, + 0xf44a: CupertinoIcons.loop_thick, + 0xf44c: CupertinoIcons.info, + 0xf455: CupertinoIcons.placemark, + 0xf456: CupertinoIcons.location_solid, + 0xf45f: CupertinoIcons.mic_off, + 0xf460: CupertinoIcons.mic, + 0xf461: CupertinoIcons.mic_solid, + 0xf463: CupertinoIcons.minus_circled, + 0xf46a: CupertinoIcons.ellipsis, + 0xf46b: CupertinoIcons.music_note, + 0xf46c: CupertinoIcons.double_music_note, + 0xf471: CupertinoIcons.news, + 0xf472: CupertinoIcons.news_solid, + 0xf477: CupertinoIcons.pause, + 0xf478: CupertinoIcons.pause_solid, + 0xf479: CupertinoIcons.paw, + 0xf47a: CupertinoIcons.paw_solid, + 0xf47b: CupertinoIcons.group, + 0xf47c: CupertinoIcons.group_solid, + 0xf47d: CupertinoIcons.person, + 0xf47e: CupertinoIcons.person_solid, + 0xf47f: CupertinoIcons.person_add, + 0xf480: CupertinoIcons.person_add_solid, + 0xf487: CupertinoIcons.play_arrow, + 0xf488: CupertinoIcons.play_arrow_solid, + 0xf489: CupertinoIcons.add, + 0xf48a: CupertinoIcons.plus_circled, + 0xf48b: CupertinoIcons.add_circled_solid, + 0xf48c: CupertinoIcons.tag, + 0xf48d: CupertinoIcons.tag_solid, + 0xf48e: CupertinoIcons.tags, + 0xf48f: CupertinoIcons.tags_solid, + 0xf49a: CupertinoIcons.refresh, + 0xf49b: CupertinoIcons.refresh_circled, + 0xf49c: CupertinoIcons.refresh_circled_solid, + 0xf49d: CupertinoIcons.refresh_thin, + 0xf49e: CupertinoIcons.switch_camera, + 0xf49f: CupertinoIcons.switch_camera_solid, + 0xf4a5: CupertinoIcons.search, + 0xf4a8: CupertinoIcons.shuffle_medium, + 0xf4a9: CupertinoIcons.shuffle, + 0xf4b6: CupertinoIcons.brightness, + 0xf4b7: CupertinoIcons.brightness_solid, + 0xf4b8: CupertinoIcons.phone, + 0xf4b9: CupertinoIcons.phone_solid, + 0xf4be: CupertinoIcons.clock, + 0xf4bf: CupertinoIcons.clock_solid, + 0xf4c4: CupertinoIcons.delete, + 0xf4c5: CupertinoIcons.delete_solid, + 0xf4c6: CupertinoIcons.reply, + 0xf4c8: CupertinoIcons.padlock, + 0xf4c9: CupertinoIcons.padlock_solid, + 0xf4ca: CupertinoIcons.share, + 0xf4cb: CupertinoIcons.share_solid, + 0xf4cc: CupertinoIcons.video_camera, + 0xf4cd: CupertinoIcons.video_camera_solid, + 0xf4d4: CupertinoIcons.airplane, + 0xf4d5: CupertinoIcons.alarm, + 0xf4d6: CupertinoIcons.alarm_fill, + 0xf4d7: CupertinoIcons.alt, + 0xf4d8: CupertinoIcons.ant, + 0xf4d9: CupertinoIcons.ant_circle, + 0xf4da: CupertinoIcons.ant_circle_fill, + 0xf4db: CupertinoIcons.ant_fill, + 0xf4dc: CupertinoIcons.antenna_radiowaves_left_right, + 0xf4dd: CupertinoIcons.app, + 0xf4de: CupertinoIcons.app_badge, + 0xf4df: CupertinoIcons.app_badge_fill, + 0xf4e0: CupertinoIcons.app_fill, + 0xf4e1: CupertinoIcons.archivebox, + 0xf4e2: CupertinoIcons.archivebox_fill, + 0xf4e3: CupertinoIcons.arrow_2_circlepath, + 0xf4e4: CupertinoIcons.arrow_2_circlepath_circle, + 0xf4e5: CupertinoIcons.arrow_2_circlepath_circle_fill, + 0xf4e6: CupertinoIcons.arrow_2_squarepath, + 0xf4e7: CupertinoIcons.arrow_3_trianglepath, + 0xf4e8: CupertinoIcons.arrow_branch, + 0xf4e9: CupertinoIcons.arrow_counterclockwise_circle, + 0xf4ea: CupertinoIcons.arrow_counterclockwise_circle_fill, + 0xf4eb: CupertinoIcons.arrow_down_circle, + 0xf4ec: CupertinoIcons.arrow_down_circle_fill, + 0xf4ed: CupertinoIcons.arrow_down_doc, + 0xf4ee: CupertinoIcons.arrow_down_doc_fill, + 0xf4ef: CupertinoIcons.arrow_down_left, + 0xf4f0: CupertinoIcons.arrow_down_left_circle, + 0xf4f1: CupertinoIcons.arrow_down_left_circle_fill, + 0xf4f2: CupertinoIcons.arrow_down_left_square, + 0xf4f3: CupertinoIcons.arrow_down_left_square_fill, + 0xf4f4: CupertinoIcons.arrow_down_right, + 0xf4f5: CupertinoIcons.arrow_down_right_circle, + 0xf4f6: CupertinoIcons.arrow_down_right_circle_fill, + 0xf4f7: CupertinoIcons.arrow_down_right_square, + 0xf4f8: CupertinoIcons.arrow_down_right_square_fill, + 0xf4f9: CupertinoIcons.arrow_down_square, + 0xf4fa: CupertinoIcons.arrow_down_square_fill, + 0xf4fb: CupertinoIcons.arrow_down_to_line, + 0xf4fc: CupertinoIcons.arrow_down_to_line_alt, + 0xf4fd: CupertinoIcons.arrow_left, + 0xf4fe: CupertinoIcons.arrow_left_circle, + 0xf4ff: CupertinoIcons.arrow_left_circle_fill, + 0xf500: CupertinoIcons.arrow_left_right, + 0xf501: CupertinoIcons.arrow_left_right_circle, + 0xf502: CupertinoIcons.arrow_left_right_circle_fill, + 0xf503: CupertinoIcons.arrow_left_right_square, + 0xf504: CupertinoIcons.arrow_left_right_square_fill, + 0xf505: CupertinoIcons.arrow_left_square, + 0xf506: CupertinoIcons.arrow_left_square_fill, + 0xf507: CupertinoIcons.arrow_left_to_line, + 0xf508: CupertinoIcons.arrow_left_to_line_alt, + 0xf509: CupertinoIcons.arrow_merge, + 0xf50a: CupertinoIcons.arrow_right, + 0xf50b: CupertinoIcons.arrow_right_arrow_left, + 0xf50c: CupertinoIcons.arrow_right_arrow_left_circle, + 0xf50d: CupertinoIcons.arrow_right_arrow_left_circle_fill, + 0xf50e: CupertinoIcons.arrow_right_arrow_left_square, + 0xf50f: CupertinoIcons.arrow_right_arrow_left_square_fill, + 0xf510: CupertinoIcons.arrow_right_circle, + 0xf511: CupertinoIcons.arrow_right_circle_fill, + 0xf512: CupertinoIcons.arrow_right_square, + 0xf513: CupertinoIcons.arrow_right_square_fill, + 0xf514: CupertinoIcons.arrow_right_to_line, + 0xf515: CupertinoIcons.arrow_right_to_line_alt, + 0xf516: CupertinoIcons.arrow_swap, + 0xf517: CupertinoIcons.arrow_turn_down_left, + 0xf518: CupertinoIcons.arrow_turn_down_right, + 0xf519: CupertinoIcons.arrow_turn_left_down, + 0xf51a: CupertinoIcons.arrow_turn_left_up, + 0xf51b: CupertinoIcons.arrow_turn_right_down, + 0xf51c: CupertinoIcons.arrow_turn_right_up, + 0xf51d: CupertinoIcons.arrow_turn_up_left, + 0xf51e: CupertinoIcons.arrow_turn_up_right, + 0xf51f: CupertinoIcons.arrow_up_arrow_down, + 0xf520: CupertinoIcons.arrow_up_arrow_down_circle, + 0xf521: CupertinoIcons.arrow_up_arrow_down_circle_fill, + 0xf522: CupertinoIcons.arrow_up_arrow_down_square, + 0xf523: CupertinoIcons.arrow_up_arrow_down_square_fill, + 0xf524: CupertinoIcons.arrow_up_bin, + 0xf525: CupertinoIcons.arrow_up_bin_fill, + 0xf526: CupertinoIcons.arrow_up_circle, + 0xf527: CupertinoIcons.arrow_up_circle_fill, + 0xf528: CupertinoIcons.arrow_up_doc, + 0xf529: CupertinoIcons.arrow_up_doc_fill, + 0xf52a: CupertinoIcons.arrow_up_down, + 0xf52b: CupertinoIcons.arrow_up_down_circle, + 0xf52c: CupertinoIcons.arrow_up_down_circle_fill, + 0xf52d: CupertinoIcons.arrow_up_down_square, + 0xf52e: CupertinoIcons.arrow_up_down_square_fill, + 0xf52f: CupertinoIcons.arrow_up_left, + 0xf530: CupertinoIcons.arrow_up_left_circle, + 0xf531: CupertinoIcons.arrow_up_left_circle_fill, + 0xf532: CupertinoIcons.arrow_up_left_square, + 0xf533: CupertinoIcons.arrow_up_left_square_fill, + 0xf534: CupertinoIcons.arrow_up_right, + 0xf535: CupertinoIcons.arrow_up_right_circle, + 0xf536: CupertinoIcons.arrow_up_right_circle_fill, + 0xf537: CupertinoIcons.arrow_up_right_diamond, + 0xf538: CupertinoIcons.arrow_up_right_diamond_fill, + 0xf539: CupertinoIcons.arrow_up_right_square, + 0xf53a: CupertinoIcons.arrow_up_right_square_fill, + 0xf53b: CupertinoIcons.arrow_up_square, + 0xf53c: CupertinoIcons.arrow_up_square_fill, + 0xf53d: CupertinoIcons.arrow_up_to_line, + 0xf53e: CupertinoIcons.arrow_up_to_line_alt, + 0xf53f: CupertinoIcons.arrow_uturn_down, + 0xf540: CupertinoIcons.arrow_uturn_down_circle, + 0xf541: CupertinoIcons.arrow_uturn_down_circle_fill, + 0xf542: CupertinoIcons.arrow_uturn_down_square, + 0xf543: CupertinoIcons.arrow_uturn_down_square_fill, + 0xf544: CupertinoIcons.arrow_uturn_left, + 0xf545: CupertinoIcons.arrow_uturn_left_circle, + 0xf546: CupertinoIcons.arrow_uturn_left_circle_fill, + 0xf547: CupertinoIcons.arrow_uturn_left_square, + 0xf548: CupertinoIcons.arrow_uturn_left_square_fill, + 0xf549: CupertinoIcons.arrow_uturn_right, + 0xf54a: CupertinoIcons.arrow_uturn_right_circle, + 0xf54b: CupertinoIcons.arrow_uturn_right_circle_fill, + 0xf54c: CupertinoIcons.arrow_uturn_right_square, + 0xf54d: CupertinoIcons.arrow_uturn_right_square_fill, + 0xf54e: CupertinoIcons.arrow_uturn_up, + 0xf54f: CupertinoIcons.arrow_uturn_up_circle, + 0xf550: CupertinoIcons.arrow_uturn_up_circle_fill, + 0xf551: CupertinoIcons.arrow_uturn_up_square, + 0xf552: CupertinoIcons.arrow_uturn_up_square_fill, + 0xf553: CupertinoIcons.arrowshape_turn_up_left_circle, + 0xf554: CupertinoIcons.arrowshape_turn_up_left_circle_fill, + 0xf555: CupertinoIcons.arrowshape_turn_up_left_fill, + 0xf556: CupertinoIcons.arrowshape_turn_up_right, + 0xf557: CupertinoIcons.arrowshape_turn_up_right_circle, + 0xf558: CupertinoIcons.arrowshape_turn_up_right_circle_fill, + 0xf559: CupertinoIcons.arrowshape_turn_up_right_fill, + 0xf55a: CupertinoIcons.arrowtriangle_down, + 0xf55b: CupertinoIcons.arrowtriangle_down_circle, + 0xf55c: CupertinoIcons.arrowtriangle_down_circle_fill, + 0xf55d: CupertinoIcons.arrowtriangle_down_fill, + 0xf55e: CupertinoIcons.arrowtriangle_down_square, + 0xf55f: CupertinoIcons.arrowtriangle_down_square_fill, + 0xf560: CupertinoIcons.arrowtriangle_left, + 0xf561: CupertinoIcons.arrowtriangle_left_circle, + 0xf562: CupertinoIcons.arrowtriangle_left_circle_fill, + 0xf563: CupertinoIcons.arrowtriangle_left_fill, + 0xf564: CupertinoIcons.arrowtriangle_left_square, + 0xf565: CupertinoIcons.arrowtriangle_left_square_fill, + 0xf566: CupertinoIcons.arrowtriangle_right, + 0xf567: CupertinoIcons.arrowtriangle_right_circle, + 0xf568: CupertinoIcons.arrowtriangle_right_circle_fill, + 0xf569: CupertinoIcons.arrowtriangle_right_fill, + 0xf56a: CupertinoIcons.arrowtriangle_right_square, + 0xf56b: CupertinoIcons.arrowtriangle_right_square_fill, + 0xf56c: CupertinoIcons.arrowtriangle_up, + 0xf56d: CupertinoIcons.arrowtriangle_up_circle, + 0xf56e: CupertinoIcons.arrowtriangle_up_circle_fill, + 0xf56f: CupertinoIcons.arrowtriangle_up_fill, + 0xf570: CupertinoIcons.arrowtriangle_up_square, + 0xf571: CupertinoIcons.arrowtriangle_up_square_fill, + 0xf572: CupertinoIcons.asterisk_circle, + 0xf573: CupertinoIcons.asterisk_circle_fill, + 0xf574: CupertinoIcons.at, + 0xf575: CupertinoIcons.at_badge_minus, + 0xf576: CupertinoIcons.at_badge_plus, + 0xf577: CupertinoIcons.backward, + 0xf578: CupertinoIcons.backward_end, + 0xf579: CupertinoIcons.backward_end_alt, + 0xf57a: CupertinoIcons.backward_end_alt_fill, + 0xf57b: CupertinoIcons.backward_end_fill, + 0xf57c: CupertinoIcons.backward_fill, + 0xf57d: CupertinoIcons.badge_plus_radiowaves_right, + 0xf57e: CupertinoIcons.bag, + 0xf57f: CupertinoIcons.bag_badge_minus, + 0xf580: CupertinoIcons.bag_badge_plus, + 0xf581: CupertinoIcons.bag_fill, + 0xf582: CupertinoIcons.bag_fill_badge_minus, + 0xf583: CupertinoIcons.bag_fill_badge_plus, + 0xf584: CupertinoIcons.bandage, + 0xf585: CupertinoIcons.bandage_fill, + 0xf586: CupertinoIcons.barcode, + 0xf587: CupertinoIcons.barcode_viewfinder, + 0xf588: CupertinoIcons.bed_double, + 0xf589: CupertinoIcons.bed_double_fill, + 0xf58a: CupertinoIcons.bell_circle, + 0xf58b: CupertinoIcons.bell_circle_fill, + 0xf58c: CupertinoIcons.bell_slash, + 0xf58d: CupertinoIcons.bell_slash_fill, + 0xf58e: CupertinoIcons.bin_xmark, + 0xf58f: CupertinoIcons.bin_xmark_fill, + 0xf590: CupertinoIcons.bold, + 0xf591: CupertinoIcons.bold_italic_underline, + 0xf592: CupertinoIcons.bold_underline, + 0xf593: CupertinoIcons.bolt, + 0xf594: CupertinoIcons.bolt_badge_a, + 0xf595: CupertinoIcons.bolt_badge_a_fill, + 0xf596: CupertinoIcons.bolt_circle, + 0xf597: CupertinoIcons.bolt_circle_fill, + 0xf598: CupertinoIcons.bolt_fill, + 0xf599: CupertinoIcons.bolt_horizontal, + 0xf59a: CupertinoIcons.bolt_horizontal_circle, + 0xf59b: CupertinoIcons.bolt_horizontal_circle_fill, + 0xf59c: CupertinoIcons.bolt_horizontal_fill, + 0xf59d: CupertinoIcons.bolt_slash, + 0xf59e: CupertinoIcons.bolt_slash_fill, + 0xf59f: CupertinoIcons.book_circle, + 0xf5a0: CupertinoIcons.book_circle_fill, + 0xf5a1: CupertinoIcons.briefcase, + 0xf5a2: CupertinoIcons.briefcase_fill, + 0xf5a3: CupertinoIcons.bubble_left, + 0xf5a4: CupertinoIcons.bubble_left_bubble_right, + 0xf5a5: CupertinoIcons.bubble_left_bubble_right_fill, + 0xf5a6: CupertinoIcons.bubble_left_fill, + 0xf5a7: CupertinoIcons.bubble_middle_bottom, + 0xf5a8: CupertinoIcons.bubble_middle_bottom_fill, + 0xf5a9: CupertinoIcons.bubble_middle_top, + 0xf5aa: CupertinoIcons.bubble_middle_top_fill, + 0xf5ab: CupertinoIcons.bubble_right, + 0xf5ac: CupertinoIcons.bubble_right_fill, + 0xf5ad: CupertinoIcons.burn, + 0xf5ae: CupertinoIcons.burst, + 0xf5af: CupertinoIcons.burst_fill, + 0xf5b0: CupertinoIcons.calendar, + 0xf5b1: CupertinoIcons.calendar_badge_minus, + 0xf5b2: CupertinoIcons.calendar_badge_plus, + 0xf5b3: CupertinoIcons.calendar_circle, + 0xf5b4: CupertinoIcons.calendar_circle_fill, + 0xf5b5: CupertinoIcons.camera_circle, + 0xf5b6: CupertinoIcons.camera_circle_fill, + 0xf5b7: CupertinoIcons.camera_on_rectangle, + 0xf5b8: CupertinoIcons.camera_on_rectangle_fill, + 0xf5b9: CupertinoIcons.camera_viewfinder, + 0xf5ba: CupertinoIcons.capslock, + 0xf5bb: CupertinoIcons.capslock_fill, + 0xf5bc: CupertinoIcons.capsule, + 0xf5bd: CupertinoIcons.capsule_fill, + 0xf5be: CupertinoIcons.captions_bubble, + 0xf5bf: CupertinoIcons.captions_bubble_fill, + 0xf5c0: CupertinoIcons.cart_badge_minus, + 0xf5c1: CupertinoIcons.cart_badge_plus, + 0xf5c2: CupertinoIcons.cart_fill, + 0xf5c3: CupertinoIcons.cart_fill_badge_minus, + 0xf5c4: CupertinoIcons.cart_fill_badge_plus, + 0xf5c5: CupertinoIcons.chart_bar, + 0xf5c6: CupertinoIcons.chart_bar_fill, + 0xf5c7: CupertinoIcons.chart_pie, + 0xf5c8: CupertinoIcons.chart_pie_fill, + 0xf5c9: CupertinoIcons.checkmark_rectangle, + 0xf5ca: CupertinoIcons.checkmark_rectangle_fill, + 0xf5cb: CupertinoIcons.checkmark_seal, + 0xf5cc: CupertinoIcons.checkmark_seal_fill, + 0xf5cd: CupertinoIcons.checkmark_shield, + 0xf5ce: CupertinoIcons.checkmark_shield_fill, + 0xf5cf: CupertinoIcons.checkmark_square, + 0xf5d0: CupertinoIcons.checkmark_square_fill, + 0xf5d1: CupertinoIcons.chevron_compact_down, + 0xf5d2: CupertinoIcons.chevron_compact_left, + 0xf5d3: CupertinoIcons.chevron_compact_right, + 0xf5d4: CupertinoIcons.chevron_compact_up, + 0xf5d5: CupertinoIcons.chevron_down, + 0xf5d6: CupertinoIcons.chevron_down_circle, + 0xf5d7: CupertinoIcons.chevron_down_circle_fill, + 0xf5d8: CupertinoIcons.chevron_down_square, + 0xf5d9: CupertinoIcons.chevron_down_square_fill, + 0xf5da: CupertinoIcons.chevron_left_2, + 0xf5db: CupertinoIcons.chevron_left_circle, + 0xf5dc: CupertinoIcons.chevron_left_circle_fill, + 0xf5dd: CupertinoIcons.chevron_left_slash_chevron_right, + 0xf5de: CupertinoIcons.chevron_left_square, + 0xf5df: CupertinoIcons.chevron_left_square_fill, + 0xf5e0: CupertinoIcons.chevron_right_2, + 0xf5e1: CupertinoIcons.chevron_right_circle, + 0xf5e2: CupertinoIcons.chevron_right_circle_fill, + 0xf5e3: CupertinoIcons.chevron_right_square, + 0xf5e4: CupertinoIcons.chevron_right_square_fill, + 0xf5e5: CupertinoIcons.chevron_up, + 0xf5e6: CupertinoIcons.chevron_up_chevron_down, + 0xf5e7: CupertinoIcons.chevron_up_circle, + 0xf5e8: CupertinoIcons.chevron_up_circle_fill, + 0xf5e9: CupertinoIcons.chevron_up_square, + 0xf5ea: CupertinoIcons.chevron_up_square_fill, + 0xf5eb: CupertinoIcons.circle_bottomthird_split, + 0xf5ec: CupertinoIcons.circle_grid_3x3, + 0xf5ed: CupertinoIcons.circle_grid_3x3_fill, + 0xf5ee: CupertinoIcons.circle_grid_hex, + 0xf5ef: CupertinoIcons.circle_grid_hex_fill, + 0xf5f0: CupertinoIcons.circle_lefthalf_fill, + 0xf5f1: CupertinoIcons.circle_righthalf_fill, + 0xf5f3: CupertinoIcons.clear_fill, + 0xf5f4: CupertinoIcons.cloud, + 0xf5f5: CupertinoIcons.cloud_bolt, + 0xf5f6: CupertinoIcons.cloud_bolt_fill, + 0xf5f7: CupertinoIcons.cloud_bolt_rain, + 0xf5f8: CupertinoIcons.cloud_bolt_rain_fill, + 0xf5f9: CupertinoIcons.cloud_drizzle, + 0xf5fa: CupertinoIcons.cloud_drizzle_fill, + 0xf5fb: CupertinoIcons.cloud_fill, + 0xf5fc: CupertinoIcons.cloud_fog, + 0xf5fd: CupertinoIcons.cloud_fog_fill, + 0xf5fe: CupertinoIcons.cloud_hail, + 0xf5ff: CupertinoIcons.cloud_hail_fill, + 0xf600: CupertinoIcons.cloud_heavyrain, + 0xf601: CupertinoIcons.cloud_heavyrain_fill, + 0xf602: CupertinoIcons.cloud_moon, + 0xf603: CupertinoIcons.cloud_moon_bolt, + 0xf604: CupertinoIcons.cloud_moon_bolt_fill, + 0xf605: CupertinoIcons.cloud_moon_fill, + 0xf606: CupertinoIcons.cloud_moon_rain, + 0xf607: CupertinoIcons.cloud_moon_rain_fill, + 0xf608: CupertinoIcons.cloud_rain, + 0xf609: CupertinoIcons.cloud_rain_fill, + 0xf60a: CupertinoIcons.cloud_sleet, + 0xf60b: CupertinoIcons.cloud_sleet_fill, + 0xf60c: CupertinoIcons.cloud_snow, + 0xf60d: CupertinoIcons.cloud_snow_fill, + 0xf60e: CupertinoIcons.cloud_sun, + 0xf60f: CupertinoIcons.cloud_sun_bolt, + 0xf610: CupertinoIcons.cloud_sun_bolt_fill, + 0xf611: CupertinoIcons.cloud_sun_fill, + 0xf612: CupertinoIcons.cloud_sun_rain, + 0xf613: CupertinoIcons.cloud_sun_rain_fill, + 0xf614: CupertinoIcons.command, + 0xf615: CupertinoIcons.control, + 0xf616: CupertinoIcons.creditcard, + 0xf617: CupertinoIcons.creditcard_fill, + 0xf618: CupertinoIcons.crop, + 0xf619: CupertinoIcons.crop_rotate, + 0xf61a: CupertinoIcons.cube, + 0xf61b: CupertinoIcons.cube_box, + 0xf61c: CupertinoIcons.cube_box_fill, + 0xf61d: CupertinoIcons.cube_fill, + 0xf61e: CupertinoIcons.cursor_rays, + 0xf61f: CupertinoIcons.decrease_indent, + 0xf620: CupertinoIcons.decrease_quotelevel, + 0xf621: CupertinoIcons.delete_left, + 0xf622: CupertinoIcons.delete_left_fill, + 0xf623: CupertinoIcons.delete_right, + 0xf624: CupertinoIcons.delete_right_fill, + 0xf625: CupertinoIcons.desktopcomputer, + 0xf626: CupertinoIcons.dial, + 0xf627: CupertinoIcons.dial_fill, + 0xf628: CupertinoIcons.divide, + 0xf629: CupertinoIcons.divide_circle, + 0xf62a: CupertinoIcons.divide_circle_fill, + 0xf62b: CupertinoIcons.divide_square, + 0xf62c: CupertinoIcons.divide_square_fill, + 0xf62d: CupertinoIcons.doc, + 0xf62e: CupertinoIcons.doc_append, + 0xf62f: CupertinoIcons.doc_circle, + 0xf630: CupertinoIcons.doc_circle_fill, + 0xf631: CupertinoIcons.doc_fill, + 0xf632: CupertinoIcons.doc_on_clipboard, + 0xf633: CupertinoIcons.doc_on_clipboard_fill, + 0xf634: CupertinoIcons.doc_on_doc, + 0xf635: CupertinoIcons.doc_on_doc_fill, + 0xf636: CupertinoIcons.doc_plaintext, + 0xf637: CupertinoIcons.doc_richtext, + 0xf638: CupertinoIcons.doc_text, + 0xf639: CupertinoIcons.doc_text_fill, + 0xf63a: CupertinoIcons.doc_text_search, + 0xf63b: CupertinoIcons.doc_text_viewfinder, + 0xf63c: CupertinoIcons.dot_radiowaves_left_right, + 0xf63d: CupertinoIcons.dot_radiowaves_right, + 0xf63e: CupertinoIcons.dot_square, + 0xf63f: CupertinoIcons.dot_square_fill, + 0xf640: CupertinoIcons.drop_triangle, + 0xf641: CupertinoIcons.drop_triangle_fill, + 0xf642: CupertinoIcons.ear, + 0xf643: CupertinoIcons.eject, + 0xf644: CupertinoIcons.eject_fill, + 0xf645: CupertinoIcons.ellipses_bubble, + 0xf646: CupertinoIcons.ellipses_bubble_fill, + 0xf647: CupertinoIcons.ellipsis_circle, + 0xf648: CupertinoIcons.ellipsis_circle_fill, + 0xf649: CupertinoIcons.envelope_badge, + 0xf64a: CupertinoIcons.envelope_badge_fill, + 0xf64b: CupertinoIcons.envelope_circle, + 0xf64c: CupertinoIcons.envelope_circle_fill, + 0xf64d: CupertinoIcons.envelope_open, + 0xf64e: CupertinoIcons.envelope_open_fill, + 0xf64f: CupertinoIcons.equal, + 0xf650: CupertinoIcons.equal_circle, + 0xf651: CupertinoIcons.equal_circle_fill, + 0xf652: CupertinoIcons.equal_square, + 0xf653: CupertinoIcons.equal_square_fill, + 0xf654: CupertinoIcons.escape, + 0xf655: CupertinoIcons.exclamationmark, + 0xf656: CupertinoIcons.exclamationmark_bubble, + 0xf657: CupertinoIcons.exclamationmark_bubble_fill, + 0xf658: CupertinoIcons.exclamationmark_circle, + 0xf659: CupertinoIcons.exclamationmark_circle_fill, + 0xf65a: CupertinoIcons.exclamationmark_octagon, + 0xf65b: CupertinoIcons.exclamationmark_octagon_fill, + 0xf65c: CupertinoIcons.exclamationmark_shield, + 0xf65d: CupertinoIcons.exclamationmark_shield_fill, + 0xf65e: CupertinoIcons.exclamationmark_square, + 0xf65f: CupertinoIcons.exclamationmark_square_fill, + 0xf660: CupertinoIcons.exclamationmark_triangle, + 0xf661: CupertinoIcons.exclamationmark_triangle_fill, + 0xf662: CupertinoIcons.eye_slash, + 0xf663: CupertinoIcons.eye_slash_fill, + 0xf664: CupertinoIcons.eyedropper, + 0xf665: CupertinoIcons.eyedropper_full, + 0xf666: CupertinoIcons.eyedropper_halffull, + 0xf667: CupertinoIcons.eyeglasses, + 0xf668: CupertinoIcons.f_cursive, + 0xf669: CupertinoIcons.f_cursive_circle, + 0xf66a: CupertinoIcons.f_cursive_circle_fill, + 0xf66b: CupertinoIcons.film, + 0xf66c: CupertinoIcons.film_fill, + 0xf66d: CupertinoIcons.flag_circle, + 0xf66e: CupertinoIcons.flag_circle_fill, + 0xf66f: CupertinoIcons.flag_fill, + 0xf670: CupertinoIcons.flag_slash, + 0xf671: CupertinoIcons.flag_slash_fill, + 0xf672: CupertinoIcons.flame, + 0xf673: CupertinoIcons.flame_fill, + 0xf674: CupertinoIcons.flowchart, + 0xf675: CupertinoIcons.flowchart_fill, + 0xf676: CupertinoIcons.folder_badge_minus, + 0xf677: CupertinoIcons.folder_badge_person_crop, + 0xf678: CupertinoIcons.folder_badge_plus, + 0xf679: CupertinoIcons.folder_circle, + 0xf67a: CupertinoIcons.folder_circle_fill, + 0xf67b: CupertinoIcons.folder_fill_badge_minus, + 0xf67c: CupertinoIcons.folder_fill_badge_person_crop, + 0xf67d: CupertinoIcons.folder_fill_badge_plus, + 0xf67f: CupertinoIcons.forward_end, + 0xf680: CupertinoIcons.forward_end_alt, + 0xf681: CupertinoIcons.forward_end_alt_fill, + 0xf682: CupertinoIcons.forward_end_fill, + 0xf683: CupertinoIcons.forward_fill, + 0xf684: CupertinoIcons.function, + 0xf685: CupertinoIcons.fx, + 0xf686: CupertinoIcons.gauge, + 0xf687: CupertinoIcons.gauge_badge_minus, + 0xf688: CupertinoIcons.gauge_badge_plus, + 0xf689: CupertinoIcons.gift, + 0xf68a: CupertinoIcons.gift_alt, + 0xf68b: CupertinoIcons.gift_alt_fill, + 0xf68c: CupertinoIcons.gift_fill, + 0xf68d: CupertinoIcons.globe, + 0xf68e: CupertinoIcons.gobackward, + 0xf68f: CupertinoIcons.gobackward_10, + 0xf690: CupertinoIcons.gobackward_15, + 0xf691: CupertinoIcons.gobackward_30, + 0xf692: CupertinoIcons.gobackward_45, + 0xf693: CupertinoIcons.gobackward_60, + 0xf694: CupertinoIcons.gobackward_75, + 0xf695: CupertinoIcons.gobackward_90, + 0xf696: CupertinoIcons.gobackward_minus, + 0xf697: CupertinoIcons.goforward, + 0xf698: CupertinoIcons.goforward_10, + 0xf699: CupertinoIcons.goforward_15, + 0xf69a: CupertinoIcons.goforward_30, + 0xf69b: CupertinoIcons.goforward_45, + 0xf69c: CupertinoIcons.goforward_60, + 0xf69d: CupertinoIcons.goforward_75, + 0xf69e: CupertinoIcons.goforward_90, + 0xf69f: CupertinoIcons.goforward_plus, + 0xf6a0: CupertinoIcons.greaterthan, + 0xf6a1: CupertinoIcons.greaterthan_circle, + 0xf6a2: CupertinoIcons.greaterthan_circle_fill, + 0xf6a3: CupertinoIcons.greaterthan_square, + 0xf6a4: CupertinoIcons.greaterthan_square_fill, + 0xf6a5: CupertinoIcons.grid, + 0xf6a6: CupertinoIcons.grid_circle, + 0xf6a7: CupertinoIcons.grid_circle_fill, + 0xf6a8: CupertinoIcons.guitars, + 0xf6a9: CupertinoIcons.hammer, + 0xf6aa: CupertinoIcons.hammer_fill, + 0xf6ab: CupertinoIcons.hand_draw, + 0xf6ac: CupertinoIcons.hand_draw_fill, + 0xf6ad: CupertinoIcons.hand_point_left, + 0xf6ae: CupertinoIcons.hand_point_left_fill, + 0xf6af: CupertinoIcons.hand_point_right, + 0xf6b0: CupertinoIcons.hand_point_right_fill, + 0xf6b1: CupertinoIcons.hand_raised, + 0xf6b2: CupertinoIcons.hand_raised_fill, + 0xf6b3: CupertinoIcons.hand_raised_slash, + 0xf6b4: CupertinoIcons.hand_raised_slash_fill, + 0xf6b5: CupertinoIcons.hand_thumbsdown, + 0xf6b6: CupertinoIcons.hand_thumbsdown_fill, + 0xf6b7: CupertinoIcons.hand_thumbsup, + 0xf6b8: CupertinoIcons.hand_thumbsup_fill, + 0xf6b9: CupertinoIcons.hare, + 0xf6ba: CupertinoIcons.hare_fill, + 0xf6bb: CupertinoIcons.headphones, + 0xf6bc: CupertinoIcons.heart_circle, + 0xf6bd: CupertinoIcons.heart_circle_fill, + 0xf6be: CupertinoIcons.heart_slash, + 0xf6bf: CupertinoIcons.heart_slash_circle, + 0xf6c0: CupertinoIcons.heart_slash_circle_fill, + 0xf6c1: CupertinoIcons.heart_slash_fill, + 0xf6c2: CupertinoIcons.helm, + 0xf6c3: CupertinoIcons.hexagon, + 0xf6c4: CupertinoIcons.hexagon_fill, + 0xf6c5: CupertinoIcons.hifispeaker, + 0xf6c6: CupertinoIcons.hifispeaker_fill, + 0xf6c7: CupertinoIcons.hourglass, + 0xf6c8: CupertinoIcons.hourglass_bottomhalf_fill, + 0xf6c9: CupertinoIcons.hourglass_tophalf_fill, + 0xf6ca: CupertinoIcons.house_fill, + 0xf6cb: CupertinoIcons.hurricane, + 0xf6cc: CupertinoIcons.increase_indent, + 0xf6cd: CupertinoIcons.increase_quotelevel, + 0xf6cf: CupertinoIcons.info_circle_fill, + 0xf6d0: CupertinoIcons.italic, + 0xf6d1: CupertinoIcons.keyboard, + 0xf6d2: CupertinoIcons.keyboard_chevron_compact_down, + 0xf6d3: CupertinoIcons.largecircle_fill_circle, + 0xf6d4: CupertinoIcons.lasso, + 0xf6d5: CupertinoIcons.leaf_arrow_circlepath, + 0xf6d6: CupertinoIcons.lessthan, + 0xf6d7: CupertinoIcons.lessthan_circle, + 0xf6d8: CupertinoIcons.lessthan_circle_fill, + 0xf6d9: CupertinoIcons.lessthan_square, + 0xf6da: CupertinoIcons.lessthan_square_fill, + 0xf6db: CupertinoIcons.light_max, + 0xf6dc: CupertinoIcons.light_min, + 0xf6dd: CupertinoIcons.lightbulb, + 0xf6de: CupertinoIcons.lightbulb_fill, + 0xf6df: CupertinoIcons.lightbulb_slash, + 0xf6e0: CupertinoIcons.lightbulb_slash_fill, + 0xf6e1: CupertinoIcons.line_horizontal_3, + 0xf6e2: CupertinoIcons.line_horizontal_3_decrease, + 0xf6e3: CupertinoIcons.line_horizontal_3_decrease_circle, + 0xf6e4: CupertinoIcons.line_horizontal_3_decrease_circle_fill, + 0xf6e5: CupertinoIcons.link, + 0xf6e6: CupertinoIcons.link_circle, + 0xf6e7: CupertinoIcons.link_circle_fill, + 0xf6e8: CupertinoIcons.list_bullet, + 0xf6e9: CupertinoIcons.list_bullet_below_rectangle, + 0xf6ea: CupertinoIcons.list_bullet_indent, + 0xf6eb: CupertinoIcons.list_dash, + 0xf6ec: CupertinoIcons.list_number, + 0xf6ed: CupertinoIcons.list_number_rtl, + 0xf6ee: CupertinoIcons.location, + 0xf6ef: CupertinoIcons.location_circle, + 0xf6f0: CupertinoIcons.location_circle_fill, + 0xf6f1: CupertinoIcons.location_fill, + 0xf6f2: CupertinoIcons.location_north, + 0xf6f3: CupertinoIcons.location_north_fill, + 0xf6f4: CupertinoIcons.location_north_line, + 0xf6f5: CupertinoIcons.location_north_line_fill, + 0xf6f6: CupertinoIcons.location_slash, + 0xf6f7: CupertinoIcons.location_slash_fill, + 0xf6f8: CupertinoIcons.lock_circle, + 0xf6f9: CupertinoIcons.lock_circle_fill, + 0xf6fa: CupertinoIcons.lock_open, + 0xf6fb: CupertinoIcons.lock_open_fill, + 0xf6fc: CupertinoIcons.lock_rotation, + 0xf6fd: CupertinoIcons.lock_rotation_open, + 0xf6fe: CupertinoIcons.lock_shield, + 0xf6ff: CupertinoIcons.lock_shield_fill, + 0xf700: CupertinoIcons.lock_slash, + 0xf701: CupertinoIcons.lock_slash_fill, + 0xf702: CupertinoIcons.macwindow, + 0xf703: CupertinoIcons.map, + 0xf704: CupertinoIcons.map_fill, + 0xf705: CupertinoIcons.map_pin, + 0xf706: CupertinoIcons.map_pin_ellipse, + 0xf707: CupertinoIcons.map_pin_slash, + 0xf708: CupertinoIcons.memories, + 0xf709: CupertinoIcons.memories_badge_minus, + 0xf70a: CupertinoIcons.memories_badge_plus, + 0xf70b: CupertinoIcons.metronome, + 0xf70c: CupertinoIcons.mic_circle, + 0xf70d: CupertinoIcons.mic_circle_fill, + 0xf70e: CupertinoIcons.mic_slash_fill, + 0xf70f: CupertinoIcons.minus, + 0xf710: CupertinoIcons.minus_circle_fill, + 0xf711: CupertinoIcons.minus_rectangle, + 0xf712: CupertinoIcons.minus_rectangle_fill, + 0xf713: CupertinoIcons.minus_slash_plus, + 0xf714: CupertinoIcons.minus_square, + 0xf715: CupertinoIcons.minus_square_fill, + 0xf716: CupertinoIcons.moon, + 0xf717: CupertinoIcons.moon_circle, + 0xf718: CupertinoIcons.moon_circle_fill, + 0xf719: CupertinoIcons.moon_fill, + 0xf71a: CupertinoIcons.moon_stars, + 0xf71b: CupertinoIcons.moon_stars_fill, + 0xf71c: CupertinoIcons.moon_zzz, + 0xf71d: CupertinoIcons.moon_zzz_fill, + 0xf71e: CupertinoIcons.multiply, + 0xf71f: CupertinoIcons.multiply_circle, + 0xf720: CupertinoIcons.multiply_circle_fill, + 0xf721: CupertinoIcons.multiply_square, + 0xf722: CupertinoIcons.multiply_square_fill, + 0xf723: CupertinoIcons.music_house, + 0xf724: CupertinoIcons.music_house_fill, + 0xf725: CupertinoIcons.music_mic, + 0xf726: CupertinoIcons.music_note_list, + 0xf727: CupertinoIcons.nosign, + 0xf728: CupertinoIcons.number, + 0xf729: CupertinoIcons.number_circle, + 0xf72a: CupertinoIcons.number_circle_fill, + 0xf72b: CupertinoIcons.number_square, + 0xf72c: CupertinoIcons.number_square_fill, + 0xf72d: CupertinoIcons.option, + 0xf72e: CupertinoIcons.paintbrush, + 0xf72f: CupertinoIcons.paintbrush_fill, + 0xf730: CupertinoIcons.pano, + 0xf731: CupertinoIcons.pano_fill, + 0xf732: CupertinoIcons.paperclip, + 0xf733: CupertinoIcons.paperplane, + 0xf734: CupertinoIcons.paperplane_fill, + 0xf735: CupertinoIcons.paragraph, + 0xf736: CupertinoIcons.pause_circle, + 0xf737: CupertinoIcons.pause_circle_fill, + 0xf738: CupertinoIcons.pause_rectangle, + 0xf739: CupertinoIcons.pause_rectangle_fill, + 0xf73a: CupertinoIcons.pencil_circle, + 0xf73b: CupertinoIcons.pencil_circle_fill, + 0xf73c: CupertinoIcons.pencil_ellipsis_rectangle, + 0xf73d: CupertinoIcons.pencil_outline, + 0xf73e: CupertinoIcons.pencil_slash, + 0xf73f: CupertinoIcons.percent, + 0xf740: CupertinoIcons.person_2, + 0xf741: CupertinoIcons.person_2_fill, + 0xf742: CupertinoIcons.person_2_square_stack, + 0xf743: CupertinoIcons.person_2_square_stack_fill, + 0xf744: CupertinoIcons.person_badge_minus, + 0xf745: CupertinoIcons.person_badge_minus_fill, + 0xf746: CupertinoIcons.person_circle, + 0xf747: CupertinoIcons.person_circle_fill, + 0xf748: CupertinoIcons.person_crop_circle_badge_checkmark, + 0xf749: CupertinoIcons.person_crop_circle_badge_exclam, + 0xf74a: CupertinoIcons.person_crop_circle_badge_minus, + 0xf74b: CupertinoIcons.person_crop_circle_badge_plus, + 0xf74c: CupertinoIcons.person_crop_circle_badge_xmark, + 0xf74d: CupertinoIcons.person_crop_circle_fill, + 0xf74e: CupertinoIcons.person_crop_circle_fill_badge_checkmark, + 0xf74f: CupertinoIcons.person_crop_circle_fill_badge_exclam, + 0xf750: CupertinoIcons.person_crop_circle_fill_badge_minus, + 0xf751: CupertinoIcons.person_crop_circle_fill_badge_plus, + 0xf752: CupertinoIcons.person_crop_circle_fill_badge_xmark, + 0xf753: CupertinoIcons.person_crop_rectangle, + 0xf754: CupertinoIcons.person_crop_rectangle_fill, + 0xf755: CupertinoIcons.person_crop_square, + 0xf756: CupertinoIcons.person_crop_square_fill, + 0xf757: CupertinoIcons.personalhotspot, + 0xf758: CupertinoIcons.perspective, + 0xf759: CupertinoIcons.phone_arrow_down_left, + 0xf75a: CupertinoIcons.phone_arrow_right, + 0xf75b: CupertinoIcons.phone_arrow_up_right, + 0xf75c: CupertinoIcons.phone_badge_plus, + 0xf75d: CupertinoIcons.phone_circle, + 0xf75e: CupertinoIcons.phone_circle_fill, + 0xf75f: CupertinoIcons.phone_down, + 0xf760: CupertinoIcons.phone_down_circle, + 0xf761: CupertinoIcons.phone_down_circle_fill, + 0xf762: CupertinoIcons.phone_down_fill, + 0xf763: CupertinoIcons.phone_fill_arrow_down_left, + 0xf764: CupertinoIcons.phone_fill_arrow_right, + 0xf765: CupertinoIcons.phone_fill_arrow_up_right, + 0xf766: CupertinoIcons.phone_fill_badge_plus, + 0xf767: CupertinoIcons.photo, + 0xf768: CupertinoIcons.photo_fill, + 0xf769: CupertinoIcons.photo_fill_on_rectangle_fill, + 0xf76a: CupertinoIcons.photo_on_rectangle, + 0xf76b: CupertinoIcons.pin, + 0xf76c: CupertinoIcons.pin_fill, + 0xf76d: CupertinoIcons.pin_slash, + 0xf76e: CupertinoIcons.pin_slash_fill, + 0xf76f: CupertinoIcons.play_circle, + 0xf770: CupertinoIcons.play_circle_fill, + 0xf771: CupertinoIcons.play_rectangle, + 0xf772: CupertinoIcons.play_rectangle_fill, + 0xf773: CupertinoIcons.playpause, + 0xf774: CupertinoIcons.playpause_fill, + 0xf775: CupertinoIcons.plus_app, + 0xf776: CupertinoIcons.plus_app_fill, + 0xf777: CupertinoIcons.plus_bubble, + 0xf778: CupertinoIcons.plus_bubble_fill, + 0xf779: CupertinoIcons.plus_rectangle, + 0xf77a: CupertinoIcons.plus_rectangle_fill, + 0xf77b: CupertinoIcons.plus_rectangle_fill_on_rectangle_fill, + 0xf77c: CupertinoIcons.plus_rectangle_on_rectangle, + 0xf77d: CupertinoIcons.plus_slash_minus, + 0xf77e: CupertinoIcons.plus_square, + 0xf77f: CupertinoIcons.plus_square_fill, + 0xf780: CupertinoIcons.plus_square_fill_on_square_fill, + 0xf781: CupertinoIcons.plus_square_on_square, + 0xf782: CupertinoIcons.plusminus, + 0xf783: CupertinoIcons.plusminus_circle, + 0xf784: CupertinoIcons.plusminus_circle_fill, + 0xf785: CupertinoIcons.power, + 0xf786: CupertinoIcons.printer, + 0xf787: CupertinoIcons.printer_fill, + 0xf788: CupertinoIcons.projective, + 0xf789: CupertinoIcons.purchased, + 0xf78a: CupertinoIcons.purchased_circle, + 0xf78b: CupertinoIcons.purchased_circle_fill, + 0xf78c: CupertinoIcons.qrcode, + 0xf78d: CupertinoIcons.qrcode_viewfinder, + 0xf78e: CupertinoIcons.question, + 0xf78f: CupertinoIcons.question_circle, + 0xf790: CupertinoIcons.question_circle_fill, + 0xf791: CupertinoIcons.question_diamond, + 0xf792: CupertinoIcons.question_diamond_fill, + 0xf793: CupertinoIcons.question_square, + 0xf794: CupertinoIcons.question_square_fill, + 0xf795: CupertinoIcons.quote_bubble, + 0xf796: CupertinoIcons.quote_bubble_fill, + 0xf797: CupertinoIcons.radiowaves_left, + 0xf798: CupertinoIcons.radiowaves_right, + 0xf799: CupertinoIcons.rays, + 0xf79a: CupertinoIcons.recordingtape, + 0xf79b: CupertinoIcons.rectangle, + 0xf79c: CupertinoIcons.rectangle_3_offgrid, + 0xf79d: CupertinoIcons.rectangle_3_offgrid_fill, + 0xf79e: CupertinoIcons.rectangle_arrow_up_right_arrow_down_left, + 0xf79f: CupertinoIcons.rectangle_arrow_up_right_arrow_down_left_slash, + 0xf7a0: CupertinoIcons.rectangle_badge_checkmark, + 0xf7a1: CupertinoIcons.rectangle_badge_xmark, + 0xf7a2: CupertinoIcons.rectangle_compress_vertical, + 0xf7a3: CupertinoIcons.rectangle_dock, + 0xf7a4: CupertinoIcons.rectangle_expand_vertical, + 0xf7a5: CupertinoIcons.rectangle_fill, + 0xf7a6: CupertinoIcons.rectangle_fill_badge_checkmark, + 0xf7a7: CupertinoIcons.rectangle_fill_badge_xmark, + 0xf7a8: CupertinoIcons.rectangle_fill_on_rectangle_angled_fill, + 0xf7a9: CupertinoIcons.rectangle_fill_on_rectangle_fill, + 0xf7aa: CupertinoIcons.rectangle_grid_1x2, + 0xf7ab: CupertinoIcons.rectangle_grid_1x2_fill, + 0xf7ac: CupertinoIcons.rectangle_grid_2x2, + 0xf7ad: CupertinoIcons.rectangle_grid_2x2_fill, + 0xf7ae: CupertinoIcons.rectangle_grid_3x2, + 0xf7af: CupertinoIcons.rectangle_grid_3x2_fill, + 0xf7b0: CupertinoIcons.rectangle_on_rectangle, + 0xf7b1: CupertinoIcons.rectangle_on_rectangle_angled, + 0xf7b2: CupertinoIcons.rectangle_paperclip, + 0xf7b3: CupertinoIcons.rectangle_split_3x1, + 0xf7b4: CupertinoIcons.rectangle_split_3x1_fill, + 0xf7b5: CupertinoIcons.rectangle_split_3x3, + 0xf7b6: CupertinoIcons.rectangle_split_3x3_fill, + 0xf7b7: CupertinoIcons.rectangle_stack_badge_minus, + 0xf7b8: CupertinoIcons.rectangle_stack_badge_person_crop, + 0xf7b9: CupertinoIcons.rectangle_stack_badge_plus, + 0xf7ba: CupertinoIcons.rectangle_stack_fill_badge_minus, + 0xf7bb: CupertinoIcons.rectangle_stack_fill_badge_person_crop, + 0xf7bc: CupertinoIcons.rectangle_stack_fill_badge_plus, + 0xf7bd: CupertinoIcons.rectangle_stack_person_crop, + 0xf7be: CupertinoIcons.rectangle_stack_person_crop_fill, + 0xf7bf: CupertinoIcons.repeat, + 0xf7c0: CupertinoIcons.repeat_1, + 0xf7c1: CupertinoIcons.return_icon, + 0xf7c2: CupertinoIcons.rhombus, + 0xf7c3: CupertinoIcons.rhombus_fill, + 0xf7c4: CupertinoIcons.rosette, + 0xf7c5: CupertinoIcons.rotate_left, + 0xf7c6: CupertinoIcons.rotate_left_fill, + 0xf7c7: CupertinoIcons.rotate_right, + 0xf7c8: CupertinoIcons.rotate_right_fill, + 0xf7c9: CupertinoIcons.scissors, + 0xf7ca: CupertinoIcons.scope, + 0xf7cb: CupertinoIcons.scribble, + 0xf7cc: CupertinoIcons.search_circle, + 0xf7cd: CupertinoIcons.search_circle_fill, + 0xf7ce: CupertinoIcons.selection_pin_in_out, + 0xf7cf: CupertinoIcons.shield, + 0xf7d0: CupertinoIcons.shield_fill, + 0xf7d1: CupertinoIcons.shield_lefthalf_fill, + 0xf7d2: CupertinoIcons.shield_slash, + 0xf7d3: CupertinoIcons.shield_slash_fill, + 0xf7d4: CupertinoIcons.shift, + 0xf7d5: CupertinoIcons.shift_fill, + 0xf7d6: CupertinoIcons.sidebar_left, + 0xf7d7: CupertinoIcons.sidebar_right, + 0xf7d8: CupertinoIcons.signature, + 0xf7d9: CupertinoIcons.skew, + 0xf7da: CupertinoIcons.slash_circle, + 0xf7db: CupertinoIcons.slash_circle_fill, + 0xf7dc: CupertinoIcons.slider_horizontal_3, + 0xf7dd: CupertinoIcons.slider_horizontal_below_rectangle, + 0xf7de: CupertinoIcons.slowmo, + 0xf7df: CupertinoIcons.smallcircle_circle, + 0xf7e0: CupertinoIcons.smallcircle_circle_fill, + 0xf7e1: CupertinoIcons.smallcircle_fill_circle, + 0xf7e2: CupertinoIcons.smallcircle_fill_circle_fill, + 0xf7e3: CupertinoIcons.smiley, + 0xf7e4: CupertinoIcons.smiley_fill, + 0xf7e5: CupertinoIcons.smoke, + 0xf7e6: CupertinoIcons.smoke_fill, + 0xf7e7: CupertinoIcons.snow, + 0xf7e8: CupertinoIcons.sparkles, + 0xf7e9: CupertinoIcons.speaker, + 0xf7ea: CupertinoIcons.speaker_1, + 0xf7eb: CupertinoIcons.speaker_2, + 0xf7ec: CupertinoIcons.speaker_2_fill, + 0xf7ed: CupertinoIcons.speaker_3, + 0xf7ee: CupertinoIcons.speaker_slash, + 0xf7ef: CupertinoIcons.speaker_slash_fill_rtl, + 0xf7f0: CupertinoIcons.speaker_slash_rtl, + 0xf7f1: CupertinoIcons.speaker_zzz, + 0xf7f2: CupertinoIcons.speaker_zzz_fill, + 0xf7f3: CupertinoIcons.speaker_zzz_fill_rtl, + 0xf7f4: CupertinoIcons.speaker_zzz_rtl, + 0xf7f5: CupertinoIcons.speedometer, + 0xf7f6: CupertinoIcons.sportscourt, + 0xf7f7: CupertinoIcons.sportscourt_fill, + 0xf7f8: CupertinoIcons.square, + 0xf7f9: CupertinoIcons.square_arrow_down, + 0xf7fa: CupertinoIcons.square_arrow_down_fill, + 0xf7fb: CupertinoIcons.square_arrow_down_on_square, + 0xf7fc: CupertinoIcons.square_arrow_down_on_square_fill, + 0xf7fd: CupertinoIcons.square_arrow_up_on_square, + 0xf7fe: CupertinoIcons.square_arrow_up_on_square_fill, + 0xf7ff: CupertinoIcons.square_fill, + 0xf800: CupertinoIcons.square_fill_line_vertical_square, + 0xf801: CupertinoIcons.square_fill_line_vertical_square_fill, + 0xf802: CupertinoIcons.square_fill_on_circle_fill, + 0xf803: CupertinoIcons.square_fill_on_square_fill, + 0xf804: CupertinoIcons.square_grid_2x2, + 0xf805: CupertinoIcons.square_grid_2x2_fill, + 0xf806: CupertinoIcons.square_grid_3x2, + 0xf807: CupertinoIcons.square_grid_3x2_fill, + 0xf808: CupertinoIcons.square_grid_4x3_fill, + 0xf809: CupertinoIcons.square_lefthalf_fill, + 0xf80a: CupertinoIcons.square_line_vertical_square, + 0xf80b: CupertinoIcons.square_line_vertical_square_fill, + 0xf80c: CupertinoIcons.square_on_circle, + 0xf80d: CupertinoIcons.square_on_square, + 0xf80e: CupertinoIcons.square_righthalf_fill, + 0xf80f: CupertinoIcons.square_split_1x2, + 0xf810: CupertinoIcons.square_split_1x2_fill, + 0xf811: CupertinoIcons.square_split_2x1, + 0xf812: CupertinoIcons.square_split_2x1_fill, + 0xf813: CupertinoIcons.square_split_2x2, + 0xf814: CupertinoIcons.square_split_2x2_fill, + 0xf815: CupertinoIcons.square_stack, + 0xf816: CupertinoIcons.square_stack_3d_down_dottedline, + 0xf817: CupertinoIcons.square_stack_3d_down_right, + 0xf818: CupertinoIcons.square_stack_3d_down_right_fill, + 0xf819: CupertinoIcons.square_stack_3d_up, + 0xf81a: CupertinoIcons.square_stack_3d_up_fill, + 0xf81b: CupertinoIcons.square_stack_3d_up_slash, + 0xf81c: CupertinoIcons.square_stack_3d_up_slash_fill, + 0xf81d: CupertinoIcons.square_stack_fill, + 0xf81e: CupertinoIcons.squares_below_rectangle, + 0xf81f: CupertinoIcons.star, + 0xf820: CupertinoIcons.star_circle, + 0xf821: CupertinoIcons.star_circle_fill, + 0xf822: CupertinoIcons.star_fill, + 0xf823: CupertinoIcons.star_lefthalf_fill, + 0xf824: CupertinoIcons.star_slash, + 0xf825: CupertinoIcons.star_slash_fill, + 0xf826: CupertinoIcons.staroflife, + 0xf827: CupertinoIcons.staroflife_fill, + 0xf828: CupertinoIcons.stop, + 0xf829: CupertinoIcons.stop_circle, + 0xf82a: CupertinoIcons.stop_circle_fill, + 0xf82b: CupertinoIcons.stop_fill, + 0xf82c: CupertinoIcons.stopwatch, + 0xf82d: CupertinoIcons.stopwatch_fill, + 0xf82e: CupertinoIcons.strikethrough, + 0xf82f: CupertinoIcons.suit_club, + 0xf830: CupertinoIcons.suit_club_fill, + 0xf831: CupertinoIcons.suit_diamond, + 0xf832: CupertinoIcons.suit_diamond_fill, + 0xf833: CupertinoIcons.suit_heart, + 0xf834: CupertinoIcons.suit_heart_fill, + 0xf835: CupertinoIcons.suit_spade, + 0xf836: CupertinoIcons.suit_spade_fill, + 0xf837: CupertinoIcons.sum, + 0xf838: CupertinoIcons.sun_dust, + 0xf839: CupertinoIcons.sun_dust_fill, + 0xf83a: CupertinoIcons.sun_haze, + 0xf83b: CupertinoIcons.sun_haze_fill, + 0xf83c: CupertinoIcons.sun_min, + 0xf83d: CupertinoIcons.sun_min_fill, + 0xf83e: CupertinoIcons.sunrise, + 0xf83f: CupertinoIcons.sunrise_fill, + 0xf840: CupertinoIcons.sunset, + 0xf841: CupertinoIcons.sunset_fill, + 0xf842: CupertinoIcons.t_bubble, + 0xf843: CupertinoIcons.t_bubble_fill, + 0xf844: CupertinoIcons.table, + 0xf845: CupertinoIcons.table_badge_more, + 0xf846: CupertinoIcons.table_badge_more_fill, + 0xf847: CupertinoIcons.table_fill, + 0xf848: CupertinoIcons.tag_circle, + 0xf849: CupertinoIcons.tag_circle_fill, + 0xf84a: CupertinoIcons.text_aligncenter, + 0xf84b: CupertinoIcons.text_alignleft, + 0xf84c: CupertinoIcons.text_alignright, + 0xf84d: CupertinoIcons.text_append, + 0xf84e: CupertinoIcons.text_badge_checkmark, + 0xf84f: CupertinoIcons.text_badge_minus, + 0xf850: CupertinoIcons.text_badge_plus, + 0xf851: CupertinoIcons.text_badge_star, + 0xf852: CupertinoIcons.text_badge_xmark, + 0xf853: CupertinoIcons.text_bubble, + 0xf854: CupertinoIcons.text_bubble_fill, + 0xf855: CupertinoIcons.text_cursor, + 0xf856: CupertinoIcons.text_insert, + 0xf857: CupertinoIcons.text_justify, + 0xf858: CupertinoIcons.text_justifyleft, + 0xf859: CupertinoIcons.text_justifyright, + 0xf85a: CupertinoIcons.text_quote, + 0xf85b: CupertinoIcons.textbox, + 0xf85c: CupertinoIcons.textformat, + 0xf85d: CupertinoIcons.textformat_123, + 0xf85e: CupertinoIcons.textformat_abc, + 0xf85f: CupertinoIcons.textformat_abc_dottedunderline, + 0xf860: CupertinoIcons.textformat_alt, + 0xf861: CupertinoIcons.textformat_size, + 0xf862: CupertinoIcons.textformat_subscript, + 0xf863: CupertinoIcons.textformat_superscript, + 0xf864: CupertinoIcons.thermometer, + 0xf865: CupertinoIcons.thermometer_snowflake, + 0xf866: CupertinoIcons.thermometer_sun, + 0xf867: CupertinoIcons.timelapse, + 0xf868: CupertinoIcons.timer, + 0xf869: CupertinoIcons.tornado, + 0xf86a: CupertinoIcons.tortoise, + 0xf86b: CupertinoIcons.tortoise_fill, + 0xf86c: CupertinoIcons.tram_fill, + 0xf86d: CupertinoIcons.trash_circle, + 0xf86e: CupertinoIcons.trash_circle_fill, + 0xf86f: CupertinoIcons.trash_slash, + 0xf870: CupertinoIcons.trash_slash_fill, + 0xf871: CupertinoIcons.tray, + 0xf872: CupertinoIcons.tray_2, + 0xf873: CupertinoIcons.tray_2_fill, + 0xf874: CupertinoIcons.tray_arrow_down, + 0xf875: CupertinoIcons.tray_arrow_down_fill, + 0xf876: CupertinoIcons.tray_arrow_up, + 0xf877: CupertinoIcons.tray_arrow_up_fill, + 0xf878: CupertinoIcons.tray_fill, + 0xf879: CupertinoIcons.tray_full, + 0xf87a: CupertinoIcons.tray_full_fill, + 0xf87b: CupertinoIcons.triangle, + 0xf87c: CupertinoIcons.triangle_fill, + 0xf87d: CupertinoIcons.triangle_lefthalf_fill, + 0xf87e: CupertinoIcons.triangle_righthalf_fill, + 0xf87f: CupertinoIcons.tropicalstorm, + 0xf880: CupertinoIcons.tuningfork, + 0xf881: CupertinoIcons.tv, + 0xf882: CupertinoIcons.tv_circle, + 0xf883: CupertinoIcons.tv_circle_fill, + 0xf884: CupertinoIcons.tv_fill, + 0xf885: CupertinoIcons.tv_music_note, + 0xf886: CupertinoIcons.tv_music_note_fill, + 0xf887: CupertinoIcons.uiwindow_split_2x1, + 0xf888: CupertinoIcons.umbrella, + 0xf889: CupertinoIcons.umbrella_fill, + 0xf88a: CupertinoIcons.underline, + 0xf88b: CupertinoIcons.view_2d, + 0xf88c: CupertinoIcons.view_3d, + 0xf88d: CupertinoIcons.viewfinder, + 0xf88e: CupertinoIcons.viewfinder_circle, + 0xf88f: CupertinoIcons.viewfinder_circle_fill, + 0xf890: CupertinoIcons.wand_rays, + 0xf891: CupertinoIcons.wand_rays_inverse, + 0xf892: CupertinoIcons.wand_stars, + 0xf893: CupertinoIcons.wand_stars_inverse, + 0xf894: CupertinoIcons.waveform, + 0xf895: CupertinoIcons.waveform_circle, + 0xf896: CupertinoIcons.waveform_circle_fill, + 0xf897: CupertinoIcons.waveform_path, + 0xf898: CupertinoIcons.waveform_path_badge_minus, + 0xf899: CupertinoIcons.waveform_path_badge_plus, + 0xf89a: CupertinoIcons.waveform_path_ecg, + 0xf89b: CupertinoIcons.wifi, + 0xf89c: CupertinoIcons.wifi_exclamationmark, + 0xf89d: CupertinoIcons.wifi_slash, + 0xf89e: CupertinoIcons.wind, + 0xf89f: CupertinoIcons.wind_snow, + 0xf8a0: CupertinoIcons.wrench, + 0xf8a1: CupertinoIcons.wrench_fill, + 0xf8a2: CupertinoIcons.xmark_octagon, + 0xf8a3: CupertinoIcons.xmark_octagon_fill, + 0xf8a4: CupertinoIcons.xmark_rectangle, + 0xf8a5: CupertinoIcons.xmark_rectangle_fill, + 0xf8a6: CupertinoIcons.xmark_seal, + 0xf8a7: CupertinoIcons.xmark_seal_fill, + 0xf8a8: CupertinoIcons.xmark_shield, + 0xf8a9: CupertinoIcons.xmark_shield_fill, + 0xf8aa: CupertinoIcons.xmark_square, + 0xf8ab: CupertinoIcons.xmark_square_fill, + 0xf8ac: CupertinoIcons.zoom_in, + 0xf8ad: CupertinoIcons.zoom_out, + 0xf8ae: CupertinoIcons.zzz, + 0xf8af: CupertinoIcons.at_circle, + 0xf8b0: CupertinoIcons.at_circle_fill, + 0xf8b1: CupertinoIcons.bars, + 0xf8b2: CupertinoIcons.bitcoin, + 0xf8b3: CupertinoIcons.bitcoin_circle, + 0xf8b4: CupertinoIcons.bitcoin_circle_fill, + 0xf8b5: CupertinoIcons.building_2_fill, + 0xf8b6: CupertinoIcons.calendar_today, + 0xf8b7: CupertinoIcons.chart_bar_alt_fill, + 0xf8b8: CupertinoIcons.chart_bar_circle, + 0xf8b9: CupertinoIcons.chart_bar_circle_fill, + 0xf8ba: CupertinoIcons.chart_bar_square, + 0xf8bb: CupertinoIcons.chart_bar_square_fill, + 0xf8bc: CupertinoIcons.chat_bubble_2, + 0xf8bd: CupertinoIcons.chat_bubble_2_fill, + 0xf8be: CupertinoIcons.chat_bubble_fill, + 0xf8bf: CupertinoIcons.chat_bubble_text, + 0xf8c0: CupertinoIcons.chat_bubble_text_fill, + 0xf8c1: CupertinoIcons.checkmark_alt, + 0xf8c2: CupertinoIcons.checkmark_alt_circle, + 0xf8c3: CupertinoIcons.checkmark_alt_circle_fill, + 0xf8c4: CupertinoIcons.cloud_download, + 0xf8c5: CupertinoIcons.cloud_download_fill, + 0xf8c6: CupertinoIcons.cloud_upload, + 0xf8c7: CupertinoIcons.cloud_upload_fill, + 0xf8c8: CupertinoIcons.color_filter, + 0xf8c9: CupertinoIcons.color_filter_fill, + 0xf8ca: CupertinoIcons.compass, + 0xf8cb: CupertinoIcons.compass_fill, + 0xf8cc: CupertinoIcons.device_desktop, + 0xf8cd: CupertinoIcons.device_laptop, + 0xf8ce: CupertinoIcons.device_phone_landscape, + 0xf8cf: CupertinoIcons.device_phone_portrait, + 0xf8d0: CupertinoIcons.doc_chart, + 0xf8d1: CupertinoIcons.doc_chart_fill, + 0xf8d2: CupertinoIcons.doc_checkmark, + 0xf8d3: CupertinoIcons.doc_checkmark_fill, + 0xf8d4: CupertinoIcons.doc_person, + 0xf8d5: CupertinoIcons.doc_person_fill, + 0xf8d6: CupertinoIcons.download_circle, + 0xf8d7: CupertinoIcons.download_circle_fill, + 0xf8d8: CupertinoIcons.drop, + 0xf8d9: CupertinoIcons.drop_fill, + 0xf8da: CupertinoIcons.ellipsis_vertical, + 0xf8db: CupertinoIcons.ellipsis_vertical_circle, + 0xf8dc: CupertinoIcons.ellipsis_vertical_circle_fill, + 0xf8dd: CupertinoIcons.floppy_disk, + 0xf8de: CupertinoIcons.gamecontroller_alt_fill, + 0xf8df: CupertinoIcons.graph_circle, + 0xf8e0: CupertinoIcons.graph_circle_fill, + 0xf8e1: CupertinoIcons.graph_square, + 0xf8e2: CupertinoIcons.graph_square_fill, + 0xf8e3: CupertinoIcons.house_alt, + 0xf8e4: CupertinoIcons.house_alt_fill, + 0xf8e5: CupertinoIcons.layers, + 0xf8e6: CupertinoIcons.layers_alt, + 0xf8e7: CupertinoIcons.layers_alt_fill, + 0xf8e8: CupertinoIcons.layers_fill, + 0xf8e9: CupertinoIcons.money_dollar, + 0xf8ea: CupertinoIcons.money_dollar_circle, + 0xf8eb: CupertinoIcons.money_dollar_circle_fill, + 0xf8ec: CupertinoIcons.money_euro, + 0xf8ed: CupertinoIcons.money_euro_circle, + 0xf8ee: CupertinoIcons.money_euro_circle_fill, + 0xf8ef: CupertinoIcons.money_pound, + 0xf8f0: CupertinoIcons.money_pound_circle, + 0xf8f1: CupertinoIcons.money_pound_circle_fill, + 0xf8f2: CupertinoIcons.money_rubl, + 0xf8f3: CupertinoIcons.money_rubl_circle, + 0xf8f4: CupertinoIcons.money_rubl_circle_fill, + 0xf8f5: CupertinoIcons.money_yen, + 0xf8f6: CupertinoIcons.money_yen_circle, + 0xf8f7: CupertinoIcons.money_yen_circle_fill, + 0xf8f8: CupertinoIcons.move, + 0xf8f9: CupertinoIcons.music_albums, + 0xf8fa: CupertinoIcons.music_albums_fill, + 0xf8fb: CupertinoIcons.person_2_alt, + 0xf8fc: CupertinoIcons.person_alt, + 0xf8fd: CupertinoIcons.person_alt_circle, + 0xf8fe: CupertinoIcons.person_alt_circle_fill, + 0xf8ff: CupertinoIcons.piano, + 0xf900: CupertinoIcons.resize, + 0xf901: CupertinoIcons.resize_h, + 0xf902: CupertinoIcons.resize_v, + 0xf903: CupertinoIcons.rocket, + 0xf904: CupertinoIcons.rocket_fill, + 0xf905: CupertinoIcons.scissors_alt, + 0xf906: CupertinoIcons.sort_down, + 0xf907: CupertinoIcons.sort_down_circle, + 0xf908: CupertinoIcons.sort_down_circle_fill, + 0xf909: CupertinoIcons.sort_up, + 0xf90a: CupertinoIcons.sort_up_circle, + 0xf90b: CupertinoIcons.sort_up_circle_fill, + 0xf90c: CupertinoIcons.square_arrow_left, + 0xf90d: CupertinoIcons.square_arrow_left_fill, + 0xf90e: CupertinoIcons.square_arrow_right, + 0xf90f: CupertinoIcons.square_arrow_right_fill, + 0xf910: CupertinoIcons.square_favorites, + 0xf911: CupertinoIcons.square_favorites_alt, + 0xf912: CupertinoIcons.square_favorites_alt_fill, + 0xf913: CupertinoIcons.square_favorites_fill, + 0xf914: CupertinoIcons.square_list, + 0xf915: CupertinoIcons.square_list_fill, + 0xf916: CupertinoIcons.ticket, + 0xf917: CupertinoIcons.ticket_fill, + 0xf918: CupertinoIcons.tickets, + 0xf919: CupertinoIcons.tickets_fill, + 0xf91a: CupertinoIcons.timer_fill, + 0xf91b: CupertinoIcons.today, + 0xf91c: CupertinoIcons.today_fill, + 0xf91d: CupertinoIcons.tree, + 0xf91e: CupertinoIcons.upload_circle, + 0xf91f: CupertinoIcons.upload_circle_fill, + 0xf920: CupertinoIcons.videocam_circle, + 0xf921: CupertinoIcons.videocam_circle_fill, +}; diff --git a/lib/src/utils/extensions.dart b/lib/src/utils/extensions.dart index fff15c7c..2f52c070 100644 --- a/lib/src/utils/extensions.dart +++ b/lib/src/utils/extensions.dart @@ -1151,26 +1151,56 @@ extension IconModelExt on IconModel { return null; } - String? toSvgUrl() { - if (this is MaterialIcon) { - final icon = this as MaterialIcon; - // e.g https://fonts.gstatic.com/s/i/materialiconsoutlined/home/v16/24px.svg - /// The template url for the svg icons. - /// {{style}}: The style of the icon. e.g. materialiconsoutlined - /// {{name}}: The name of the icon. e.g. home - /// {{version}}: The version of the icon. e.g. 16 - /// {{size}}: The size of the icon. e.g. 24 - return 'https://fonts.gstatic.com/s/i/{{style}}/{{name}}/v{{version}}/{{size}}px.svg' - .replaceAll( - '{{style}}', 'materialicons${icon.style.styleName.toLowerCase()}') - .replaceAll('{{name}}', icon.name.toLowerCase()) - .replaceAll('{{version}}', '${icon.version ?? 1}') - .replaceAll('{{size}}', '24'); + /// URL of Google's prebuilt SVG for this icon, or null when there is + /// none (Cupertino and custom font icons). + /// + /// Material Symbols are variable fonts; their SVGs exist only for some + /// axis values, so [fill], [weight], [grade] and [opticalSize] are snapped + /// to the nearest available variant. + String? toSvgUrl({ + double? fill, + double? weight, + double? grade, + double? opticalSize, + }) { + final IconModel icon = this; + if (icon is MaterialIcon) { + // e.g. https://fonts.gstatic.com/s/i/materialiconsoutlined/home/v16/24px.svg + final String style = + icon.style.styleName.toLowerCase().replaceAll(' ', ''); + return 'https://fonts.gstatic.com/s/i/materialicons$style/' + '${icon.name.toLowerCase()}/v${icon.version ?? 1}/24px.svg'; + } + if (icon is MaterialSymbol) { + // e.g. https://fonts.gstatic.com/s/i/short-term/release/materialsymbolsrounded/home/wght300gradN25fill1/40px.svg + return 'https://fonts.gstatic.com/s/i/short-term/release/' + 'materialsymbols${icon.style.name}/${icon.name.toLowerCase()}/' + '${materialSymbolVariant(fill: fill, weight: weight, grade: grade)}/' + '${_nearest(opticalSize ?? 24, const [20, 24, 40, 48])}px.svg'; } return null; } } +/// The path segment naming a Material Symbols SVG variant, e.g. +/// `wght700grad200fill1`, or `default` when every axis is at its default. +String materialSymbolVariant({double? fill, double? weight, double? grade}) { + final int w = + weight == null ? 400 : ((weight / 100).round() * 100).clamp(100, 700); + final int g = grade == null ? 0 : _nearest(grade, const [-25, 0, 200]); + final bool filled = (fill ?? 0) >= 0.5; + final String variant = [ + if (w != 400) 'wght$w', + if (g != 0) 'grad${g < 0 ? 'N${-g}' : g}', + if (filled) 'fill1', + ].join(); + return variant.isEmpty ? 'default' : variant; +} + +int _nearest(double value, List options) => options.reduce( + (a, b) => (a - value).abs() <= (b - value).abs() ? a : b, + ); + extension MaterialIconExt on MaterialIcon { String? get flutterID => flutterIconNames[styledName]; diff --git a/pubspec.yaml b/pubspec.yaml index e5a5f125..91f6b85a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -49,6 +49,7 @@ dependencies: vector_math: ^2.1.4 provider: ^6.1.2 uuid: ^4.5.1 + cupertino_icons: ^1.0.9 flutter_svg: ^2.0.17 # File and Image diff --git a/test/icon_test.dart b/test/icon_test.dart new file mode 100644 index 00000000..494fdb56 --- /dev/null +++ b/test/icon_test.dart @@ -0,0 +1,106 @@ +import 'package:codelessly_api/codelessly_api.dart'; +import 'package:codelessly_sdk/codelessly_sdk.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('Material icon URLs', () { + MaterialIcon icon(MaterialIconStyle style) => MaterialIcon( + codepoint: 0xe88a, + fontFamily: 'MaterialIcons', + name: 'home', + style: style, + version: 16, + ); + + test('use the style in the path', () { + expect( + icon(MaterialIconStyle.outlined).toSvgUrl(), + 'https://fonts.gstatic.com/s/i/materialiconsoutlined/home/v16/24px.svg', + ); + expect( + icon(MaterialIconStyle.filled).toSvgUrl(), + 'https://fonts.gstatic.com/s/i/materialicons/home/v16/24px.svg', + ); + }); + + test('two-tone has no space in the path', () { + expect( + icon(MaterialIconStyle.twoTone).toSvgUrl(), + 'https://fonts.gstatic.com/s/i/materialiconstwotone/home/v16/24px.svg', + ); + }); + }); + + group('Material Symbol URLs', () { + final symbol = MaterialSymbol( + codepoint: 0xe88a, + fontFamily: 'MaterialSymbolsRounded', + name: 'home', + style: MaterialSymbolStyle.rounded, + ); + + test('default axes', () { + expect( + symbol.toSvgUrl(), + 'https://fonts.gstatic.com/s/i/short-term/release/' + 'materialsymbolsrounded/home/default/24px.svg', + ); + }); + + test('axes are snapped to the prebuilt variants', () { + expect( + symbol.toSvgUrl(fill: 1, weight: 680, grade: -20, opticalSize: 46), + 'https://fonts.gstatic.com/s/i/short-term/release/' + 'materialsymbolsrounded/home/wght700gradN25fill1/48px.svg', + ); + }); + + test('variant names', () { + expect(materialSymbolVariant(), 'default'); + expect(materialSymbolVariant(weight: 400, grade: 0, fill: 0), 'default'); + expect(materialSymbolVariant(weight: 100), 'wght100'); + expect(materialSymbolVariant(grade: 150), 'grad200'); + expect(materialSymbolVariant(fill: 0.6), 'fill1'); + expect( + materialSymbolVariant(weight: 300, grade: -25, fill: 1), + 'wght300gradN25fill1', + ); + }); + }); + + testWidgets('Cupertino icons render from the cupertino_icons font', ( + tester, + ) async { + await tester.pumpWidget( + MaterialApp( + home: SvgIcon( + icon: CupertinoIcon( + codepoint: CupertinoIcons.heart.codePoint, + name: 'heart', + ), + size: 30, + ), + ), + ); + final Icon icon = tester.widget(find.byType(Icon)); + expect(icon.icon, CupertinoIcons.heart); + expect(icon.size, 30); + }); + + testWidgets('Icons without a source render an empty box', (tester) async { + await tester.pumpWidget( + MaterialApp( + home: Center( + child: SvgIcon( + icon: CupertinoIcon(codepoint: 0x1, name: 'unknown'), + size: 20, + ), + ), + ), + ); + expect(find.byType(Icon), findsNothing); + expect(tester.getSize(find.byType(SizedBox).last), const Size(20, 20)); + }); +} diff --git a/tool/generate_cupertino_icons.py b/tool/generate_cupertino_icons.py new file mode 100644 index 00000000..2b7bfab5 --- /dev/null +++ b/tool/generate_cupertino_icons.py @@ -0,0 +1,39 @@ +"""Generates lib/src/utils/cupertino_icons_by_codepoint.dart. + +Maps each CupertinoIcons codepoint to its const IconData so Cupertino icon +models can render with the cupertino_icons font while icon tree shaking still +works. Run with the Flutter SDK whose CupertinoIcons should be mirrored: + + python3 tool/generate_cupertino_icons.py /path/to/flutter +""" +import re +import sys +from pathlib import Path + +flutter = Path(sys.argv[1] if len(sys.argv) > 1 else '/root/flutter') +source = (flutter / 'packages/flutter/lib/src/cupertino/icons.dart').read_text() + +entries = {} +for match in re.finditer( + r'static const IconData (\w+) = IconData\(\s*(0x[0-9a-fA-F]+)', source +): + name, codepoint = match.group(1), int(match.group(2), 16) + # Several names alias one glyph; the first declared name wins. + entries.setdefault(codepoint, name) + +lines = [ + '// GENERATED CODE - DO NOT MODIFY BY HAND.', + '// Generated by tool/generate_cupertino_icons.py from Flutter\'s', + '// CupertinoIcons.', + '', + "import 'package:flutter/cupertino.dart';", + '', + '/// Flutter\'s [CupertinoIcons] keyed by codepoint.', + 'const Map cupertinoIconsByCodepoint = {', +] +for codepoint in sorted(entries): + lines.append(f' 0x{codepoint:x}: CupertinoIcons.{entries[codepoint]},') +lines.append('};') +out = Path(__file__).resolve().parent.parent / 'lib/src/utils/cupertino_icons_by_codepoint.dart' +out.write_text('\n'.join(lines) + '\n') +print(f'{len(entries)} icons -> {out}') From 86a8928a18ae83ee216e9b6a38c4fdcb18b0a4fe Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 13 Oct 2026 02:43:03 +0000 Subject: [PATCH 10/22] Let apps opt layouts into their theme and text scaling Layouts render with a default ThemeData and no text scaling so they match the editor. Add CodelesslyConfig.useHostTheme and maxTextScaleFactor so apps can follow their own theme and, within a bound, the platform text size. The dropdown now extends the surrounding theme instead of replacing it. CodelesslyConfig.copyWith also dropped debugLog and accepted an unused staggerDownloadQueue argument. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01A67XoQYsLVvoMtiKcXXUxY --- lib/src/codelessly_config.dart | 32 +++++++++++++++++-- .../passive_dropdown_transformer.dart | 2 +- lib/src/ui/layout_builder.dart | 11 +++++-- 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/lib/src/codelessly_config.dart b/lib/src/codelessly_config.dart index b70d221f..0b65f6b2 100644 --- a/lib/src/codelessly_config.dart +++ b/lib/src/codelessly_config.dart @@ -84,6 +84,23 @@ class CodelesslyConfig with Equatable { /// The Firebase project ID for this instance. final String firebaseProjectId; + /// Whether layouts render with the app's [Theme] instead of a default + /// [ThemeData]. + /// + /// Layouts are styled explicitly in the Codelessly editor, so by default + /// they render with a default theme to look exactly as designed. Enable this + /// to let theme-derived defaults (color scheme, typography, brightness) + /// follow the app. + final bool useHostTheme; + + /// The largest text scale factor layouts follow from the platform's text + /// size setting. + /// + /// Layouts are designed at fixed sizes, so the default of 1 ignores the + /// setting. Raise it to let text grow for accessibility, at the risk of + /// text overflowing fixed-size boxes. + final double maxTextScaleFactor; + /// Creates a new instance of [CodelesslyConfig]. /// /// [authToken] is the token required to authenticate and initialize the SDK. @@ -108,7 +125,10 @@ class CodelesslyConfig with Equatable { this.firebaseProjectId = CodelesslyConstants.kProdFirebaseProjectId, PublishSource? publishSource, this.debugLog = false, - }) : firebaseOptions = + this.useHostTheme = false, + this.maxTextScaleFactor = 1, + }) : assert(maxTextScaleFactor >= 1), + firebaseOptions = firebaseOptions ?? DefaultFirebaseOptionsProd.currentPlatform, publishSource = publishSource ?? (isPreview ? PublishSource.preview : PublishSource.publish); @@ -121,7 +141,6 @@ class CodelesslyConfig with Equatable { bool? automaticallySendCrashReports, bool? isPreview, bool? preload, - bool? staggerDownloadQueue, FirebaseOptions? firebaseOptions, String? firebaseCloudFunctionsBaseURL, String? baseURL, @@ -129,6 +148,9 @@ class CodelesslyConfig with Equatable { String? clientType, String? firebaseProjectId, PublishSource? publishSource, + bool? debugLog, + bool? useHostTheme, + double? maxTextScaleFactor, }) => CodelesslyConfig( authToken: authToken ?? this.authToken, @@ -145,6 +167,9 @@ class CodelesslyConfig with Equatable { clientType: clientType ?? this.clientType, firebaseProjectId: firebaseProjectId ?? this.firebaseProjectId, publishSource: publishSource ?? this.publishSource, + debugLog: debugLog ?? this.debugLog, + useHostTheme: useHostTheme ?? this.useHostTheme, + maxTextScaleFactor: maxTextScaleFactor ?? this.maxTextScaleFactor, ); @override @@ -161,6 +186,9 @@ class CodelesslyConfig with Equatable { clientType, firebaseProjectId, publishSource, + debugLog, + useHostTheme, + maxTextScaleFactor, ]; } diff --git a/lib/src/transformers/node_transformers/passive_dropdown_transformer.dart b/lib/src/transformers/node_transformers/passive_dropdown_transformer.dart index 73641045..96f1b9a0 100644 --- a/lib/src/transformers/node_transformers/passive_dropdown_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_dropdown_transformer.dart @@ -184,7 +184,7 @@ class PassiveDropdownWidget extends StatelessWidget { return AdaptiveNodeBox( node: node, child: Theme( - data: ThemeData( + data: Theme.of(context).copyWith( hoverColor: node.properties.hoverColor?.toFlutterColor(), splashColor: node.properties.splashColor?.toFlutterColor(), ), diff --git a/lib/src/ui/layout_builder.dart b/lib/src/ui/layout_builder.dart index 2ae3a249..6521359c 100644 --- a/lib/src/ui/layout_builder.dart +++ b/lib/src/ui/layout_builder.dart @@ -341,10 +341,17 @@ class _CodelesslyLayoutBuilderState extends State { @override Widget build(BuildContext context) { + final CodelesslyConfig config = widget.controller.config; + final MediaQueryData mediaQuery = MediaQuery.of(context); return MediaQuery( - data: MediaQuery.of(context).copyWith(textScaler: TextScaler.noScaling), + data: mediaQuery.copyWith( + textScaler: config.maxTextScaleFactor <= 1 + ? TextScaler.noScaling + : mediaQuery.textScaler + .clamp(maxScaleFactor: config.maxTextScaleFactor), + ), child: Theme( - data: ThemeData(), + data: config.useHostTheme ? Theme.of(context) : ThemeData(), child: transformerManager.buildWidgetFromNode( canvasNode, context, From d475ab1d82b9c79755fe4e7631af9594828b23bb Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 13 Oct 2026 02:46:18 +0000 Subject: [PATCH 11/22] Reload web views on data changes and route output through the logger - Web views loaded their source once, so URLs bound to variables never updated. Reload when the resolved source changes. - TabBar ignored showDivider. - Replace print calls in library code with DebugLogger, so output follows the debugLog setting. ConditionPrinter.prettify no longer prints as a side effect. - Remove the font parser's main() CLI from the public library, which also took dart:io out of it. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01A67XoQYsLVvoMtiKcXXUxY --- .../passive_embedded_video_transformer.dart | 11 +-- .../passive_rectangle_transformer.dart | 7 +- .../passive_tab_bar_transformer.dart | 8 ++- .../passive_web_view_transformer_io.dart | 71 ++++++++++++++----- .../passive_web_view_transformer_web.dart | 10 +-- .../utils/condition_evaluator.dart | 9 +-- lib/src/utils/font_parser.dart | 10 --- lib/src/utils/image_builder.dart | 4 +- 8 files changed, 81 insertions(+), 49 deletions(-) diff --git a/lib/src/transformers/node_transformers/passive_embedded_video_transformer.dart b/lib/src/transformers/node_transformers/passive_embedded_video_transformer.dart index bdc3d52d..689c01db 100644 --- a/lib/src/transformers/node_transformers/passive_embedded_video_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_embedded_video_transformer.dart @@ -9,6 +9,7 @@ import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart'; import '../../../codelessly_sdk.dart'; import '../web/web_webview_platform.dart'; +import '../../logging/debug_logger.dart'; class PassiveEmbeddedVideoTransformer extends NodeWidgetTransformer { @@ -94,8 +95,10 @@ class _PassiveEmbeddedVideoWidgetState void initState() { super.initState(); if (!isPlatformSupportedForWebView || widget.settings.isPreview) { - print('Unsupported platform: $defaultTargetPlatform for embedded ' - 'video.'); + DebugLogger.instance.printInfo( + 'Unsupported platform: $defaultTargetPlatform for embedded video.', + name: 'EmbeddedVideo', + ); return; } final PlatformWebViewControllerCreationParams params; @@ -126,7 +129,6 @@ class _PassiveEmbeddedVideoWidgetState } // _controller.setUserAgent('chrome'); if (_controller.platform is AndroidWebViewController) { - print('setting android config'); final controller = _controller.platform as AndroidWebViewController; controller.setMediaPlaybackRequiresUserGesture( !widget.node.properties.autoPlay); @@ -135,7 +137,6 @@ class _PassiveEmbeddedVideoWidgetState controller.setUserAgent( 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36'); } else if (_controller.platform is WebKitWebViewController) { - print('setting webkit config'); final controller = _controller.platform as WebKitWebViewController; if (widget.node.properties.source == EmbeddedVideoSource.youtube) { controller.setUserAgent( @@ -163,7 +164,7 @@ class _PassiveEmbeddedVideoWidgetState baseUrl: config.baseURL, ); } - print('Loading $embedUrl'); + DebugLogger.instance.printInfo('Loading $embedUrl', name: 'EmbeddedVideo'); _controller.loadRequest(Uri.parse(embedUrl)); } diff --git a/lib/src/transformers/node_transformers/passive_rectangle_transformer.dart b/lib/src/transformers/node_transformers/passive_rectangle_transformer.dart index 91e8ba2b..4294940d 100644 --- a/lib/src/transformers/node_transformers/passive_rectangle_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_rectangle_transformer.dart @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; import 'package:vector_math/vector_math_64.dart' as vec_math; import '../../../codelessly_sdk.dart'; +import '../../logging/debug_logger.dart'; class PassiveRectangleTransformer extends NodeWidgetTransformer { PassiveRectangleTransformer(super.getNode, super.manager); @@ -433,8 +434,10 @@ Gradient? retrieveGradient(PaintModel paint) { return null; } catch (error, stacktrace) { - print(error); - print(stacktrace); + DebugLogger.instance.printInfo( + 'Failed to build gradient: $error\n$stacktrace', + name: 'PassiveRectangleTransformer', + ); return null; } } diff --git a/lib/src/transformers/node_transformers/passive_tab_bar_transformer.dart b/lib/src/transformers/node_transformers/passive_tab_bar_transformer.dart index ab648056..f806b1ae 100644 --- a/lib/src/transformers/node_transformers/passive_tab_bar_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_tab_bar_transformer.dart @@ -167,8 +167,12 @@ class _PassiveTabBarWidgetState extends State padding: widget.node.padding.flutterEdgeInsets, overlayColor: WidgetStateProperty.all( widget.node.properties.overlayColor?.toFlutterColor()), - dividerColor: widget.node.properties.dividerColor?.toFlutterColor(), - dividerHeight: widget.node.properties.dividerHeight, + dividerColor: widget.node.properties.showDivider + ? widget.node.properties.dividerColor?.toFlutterColor() + : Colors.transparent, + dividerHeight: widget.node.properties.showDivider + ? widget.node.properties.dividerHeight + : 0, tabAlignment: widget.node.properties.tabAlignment ?.toFlutter(isScrollable: widget.node.isScrollable), indicatorColor: diff --git a/lib/src/transformers/node_transformers/passive_web_view_transformer_io.dart b/lib/src/transformers/node_transformers/passive_web_view_transformer_io.dart index 7f50c525..33d0d4f5 100644 --- a/lib/src/transformers/node_transformers/passive_web_view_transformer_io.dart +++ b/lib/src/transformers/node_transformers/passive_web_view_transformer_io.dart @@ -10,6 +10,8 @@ import 'package:webview_flutter/webview_flutter.dart'; import 'package:webview_flutter_android/webview_flutter_android.dart'; import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart'; +import '../../logging/debug_logger.dart'; + import '../../../codelessly_sdk.dart'; class PassiveWebViewTransformer extends NodeWidgetTransformer { @@ -161,7 +163,10 @@ class _RawWebViewWidgetState extends State { void initState() { super.initState(); if (!isPlatformSupportedForWebView || widget.settings.isPreview) { - print('Unsupported platform: $defaultTargetPlatform for WebView.'); + DebugLogger.instance.printInfo( + 'Unsupported platform: $defaultTargetPlatform for WebView.', + name: 'RawWebViewWidget', + ); return; } @@ -205,7 +210,10 @@ class _RawWebViewWidgetState extends State { void didChangeDependencies() { super.didChangeDependencies(); if (!isPlatformSupportedForWebView || widget.settings.isPreview) { - print('Unsupported platform: $defaultTargetPlatform for WebView.'); + DebugLogger.instance.printInfo( + 'Unsupported platform: $defaultTargetPlatform for WebView.', + name: 'RawWebViewWidget', + ); return; } if (!_isDataLoaded) { @@ -214,37 +222,66 @@ class _RawWebViewWidgetState extends State { } } - Future _loadData() async { + /// The source most recently loaded into [_controller]. + String? _loadedSource; + + @override + void didUpdateWidget(covariant RawWebViewWidget oldWidget) { + super.didUpdateWidget(oldWidget); + // Reload when the properties or the variables they read changed. + if (_isDataLoaded) _loadData(); + } + + /// The URL (or Flutter asset path) the properties currently point to. + ({String source, bool isAsset}) _resolveSource() { final ScopedValues scopedValues = ScopedValues.of(context); final WebViewProperties props = widget.properties; switch (props.webviewType) { case WebViewType.webpage: final WebPageWebViewProperties properties = props as WebPageWebViewProperties; - url = PropertyValueDelegate.getVariableValueFromPath( - properties.input, - scopedValues: scopedValues) ?? - properties.input; switch (properties.pageSourceType) { case WebViewWebpageSourceType.url: - return _controller.loadRequest(Uri.parse(url)); + return ( + source: PropertyValueDelegate.getVariableValueFromPath( + properties.input, + scopedValues: scopedValues, + ) ?? + properties.input, + isAsset: false, + ); case WebViewWebpageSourceType.html: - final String content = _buildHtmlContent(properties.input); - return _controller.loadRequest(Uri.parse(content)); + return ( + source: _buildHtmlContent(properties.input), + isAsset: false, + ); case WebViewWebpageSourceType.asset: - // provided from onWebViewCreated callback. - return _controller.loadFlutterAsset(properties.input); + return (source: properties.input, isAsset: true); } case WebViewType.googleMaps: - url = buildGoogleMapsURL( - props as GoogleMapsWebViewProperties, scopedValues); - return _controller.loadRequest(Uri.parse(url)); + return ( + source: buildGoogleMapsURL( + props as GoogleMapsWebViewProperties, scopedValues), + isAsset: false, + ); case WebViewType.twitter: - url = buildTwitterURL(props as TwitterWebViewProperties, scopedValues); - return _controller.loadRequest(Uri.parse(url)); + return ( + source: + buildTwitterURL(props as TwitterWebViewProperties, scopedValues), + isAsset: false, + ); } } + Future _loadData() async { + final (:source, :isAsset) = _resolveSource(); + if (source == _loadedSource) return; + _loadedSource = source; + if (isAsset) return _controller.loadFlutterAsset(source); + url = source; + return _controller.loadRequest(Uri.parse(source)); + } + @override Widget build(BuildContext context) { final WebViewProperties props = widget.properties; diff --git a/lib/src/transformers/node_transformers/passive_web_view_transformer_web.dart b/lib/src/transformers/node_transformers/passive_web_view_transformer_web.dart index 51025903..ab1e0f0d 100644 --- a/lib/src/transformers/node_transformers/passive_web_view_transformer_web.dart +++ b/lib/src/transformers/node_transformers/passive_web_view_transformer_web.dart @@ -156,19 +156,13 @@ class _RawWebViewWidgetState extends State { @override void initState() { super.initState(); - if (widget.settings.isPreview) { - print('Preview mode for WebView.'); - return; - } + if (widget.settings.isPreview) return; } @override void didChangeDependencies() { super.didChangeDependencies(); - if (widget.settings.isPreview) { - print('Preview mode for WebView.'); - return; - } + if (widget.settings.isPreview) return; if (!_isDataLoaded) { _isDataLoaded = true; _loadData(); diff --git a/lib/src/transformers/utils/condition_evaluator.dart b/lib/src/transformers/utils/condition_evaluator.dart index 5c9e71f3..23068d9d 100644 --- a/lib/src/transformers/utils/condition_evaluator.dart +++ b/lib/src/transformers/utils/condition_evaluator.dart @@ -2,6 +2,7 @@ import 'package:codelessly_api/codelessly_api.dart'; import 'package:collection/collection.dart'; import '../../../codelessly_sdk.dart'; +import '../../logging/debug_logger.dart'; class ConditionEvaluator implements @@ -273,13 +274,13 @@ class ConditionPrinter final StringBuffer _buffer = StringBuffer(); void printCondition(BaseCondition condition) { - _buffer.clear(); - condition.accept(this); - print('\n${_buffer.toString()}'); + DebugLogger.instance + .printInfo('\n${prettify(condition)}', name: 'ConditionPrinter'); } String prettify(BaseCondition condition) { - printCondition(condition); + _buffer.clear(); + condition.accept(this); return _buffer.toString(); } diff --git a/lib/src/utils/font_parser.dart b/lib/src/utils/font_parser.dart index abe60285..d95e9e49 100644 --- a/lib/src/utils/font_parser.dart +++ b/lib/src/utils/font_parser.dart @@ -1,17 +1,9 @@ -import 'dart:convert'; -import 'dart:io'; import 'dart:typed_data'; import 'package:codelessly_api/codelessly_api.dart'; import '../model/font_family.dart'; -void main(List args) { - final file = File(args[0]); - final result = FontParser.parse(file.readAsBytesSync()); - print('Result: ${const JsonEncoder.withIndent(' ').convert(result)}'); -} - /// Compares the given [a] [FontVariantModel] with the [b] [FontVariantModel] /// and returns the score. /// @@ -280,7 +272,6 @@ class FontParser { str = bin.readUnicode(data, soff, slen ~/ 2); } else if (platformID == 1) { str = bin.readASCII(data, soff, slen); - print('reading unknown MAC encoding $encodingID as ASCII'); } else { throw Exception( 'unknown encoding $encodingID, platformID: $platformID'); @@ -306,7 +297,6 @@ class FontParser { tname = p; break; } - print(obj); return obj[tname]; } } diff --git a/lib/src/utils/image_builder.dart b/lib/src/utils/image_builder.dart index c45ac564..28484d2d 100644 --- a/lib/src/utils/image_builder.dart +++ b/lib/src/utils/image_builder.dart @@ -5,6 +5,7 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:google_fonts/google_fonts.dart'; import '../../codelessly_sdk.dart'; +import '../logging/debug_logger.dart'; /// Ultimate widget to build image from url, bytes or paint. Supports Gifs and /// SVGs too! @@ -254,7 +255,8 @@ class _UltimateImageBuilderState extends State { widget.loadingBuilder!(context, child) : null, errorBuilder: (context, error, stackTrace) { - print('Image Loading Error: $error'); + DebugLogger.instance + .printInfo('Image Loading Error: $error', name: 'ImageBuilder'); return (widget.errorBuilder ?? _defaultErrorBuilder)(context); }, ); From 66541b444a3f397d8ea4321b1086296c9c56f081 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 13 Oct 2026 02:48:16 +0000 Subject: [PATCH 12/22] Harden link taps and disposal; drop unused dependencies - Text, divider, loading indicator and web view links called launchUrl(Uri.parse(...)), which threw on malformed URLs and left launch failures unhandled. Route them through FunctionsRepository.openLink. - LocalDatabase and CodelessCacheManager closed their Hive boxes in `void dispose() async`, so close failures were unhandled; report them. - Remove the unused uuid, intl and path_provider dependencies. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01A67XoQYsLVvoMtiKcXXUxY --- lib/src/cache/codelessly_cache_manager.dart | 17 ++++++++++++++--- lib/src/data/local_database.dart | 17 +++++++++++++---- lib/src/functions/functions_repository.dart | 19 ++++++++++++++++++- .../passive_divider_transformer.dart | 3 +-- ...passive_loading_indicator_transformer.dart | 3 +-- .../passive_text_transformer.dart | 3 +-- .../passive_web_view_transformer_io.dart | 3 +-- .../passive_web_view_transformer_web.dart | 3 +-- pubspec.yaml | 3 --- 9 files changed, 50 insertions(+), 21 deletions(-) diff --git a/lib/src/cache/codelessly_cache_manager.dart b/lib/src/cache/codelessly_cache_manager.dart index e400716d..f3764c84 100644 --- a/lib/src/cache/codelessly_cache_manager.dart +++ b/lib/src/cache/codelessly_cache_manager.dart @@ -79,10 +79,21 @@ class CodelesslyCacheManager extends CacheManager { } @override - void dispose() async { + void dispose() { DebugLogger.instance.printFunction('dispose()', name: name); - box.close(); - filesBox.close(); + unawaited( + Future.wait([box.close(), filesBox.close()]).catchError( + (Object e, StackTrace stackTrace) { + ErrorLogger.instance.captureException( + e, + message: 'Failed to close cache', + type: 'cache_close_failed', + stackTrace: stackTrace, + ); + return []; + }, + ), + ); } @override diff --git a/lib/src/data/local_database.dart b/lib/src/data/local_database.dart index 6e59f193..290053eb 100644 --- a/lib/src/data/local_database.dart +++ b/lib/src/data/local_database.dart @@ -1,10 +1,11 @@ import 'dart:async'; -import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; import 'package:hive_ce_flutter/hive_flutter.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../logging/debug_logger.dart'; +import '../logging/error_logger.dart'; /// Allows access to local storage on the device. Implementations of this class /// should be able to store and retrieve data from the device's local storage. @@ -145,13 +146,21 @@ class HiveLocalDatabase extends LocalDatabase { } @override - void dispose() async { + void dispose() { DebugLogger.instance.printFunction('dispose()', name: LocalDatabase.name); // Cancel the subscription to the storage updates. _subscription?.cancel(); - // Close the box itself. - await _box.close(); + // Close the box itself. ChangeNotifier.dispose is synchronous, so report + // close failures instead of leaving them unhandled. + unawaited(_box.close().catchError((Object e, StackTrace stackTrace) { + ErrorLogger.instance.captureException( + e, + message: 'Failed to close local database', + type: 'local_database_close_failed', + stackTrace: stackTrace, + ); + })); super.dispose(); } diff --git a/lib/src/functions/functions_repository.dart b/lib/src/functions/functions_repository.dart index a246e9e0..492bde38 100644 --- a/lib/src/functions/functions_repository.dart +++ b/lib/src/functions/functions_repository.dart @@ -417,6 +417,12 @@ class FunctionsRepository { nullSubstitutionMode: NullSubstitutionMode.emptyString, scopedValues: ScopedValues.of(context), ).trim(); + openLink(url); + } + + /// Opens [url] with the platform, logging instead of throwing when it is + /// malformed or cannot be launched. + static void openLink(String url) { final Uri? uri = Uri.tryParse(url); if (uri == null || url.isEmpty) { DebugLogger.instance.printInfo( @@ -429,7 +435,18 @@ class FunctionsRepository { 'Launching URL: $uri', name: name, ); - launchUrl(uri); + unawaited( + launchUrl(uri).then( + (_) {}, + onError: (Object e, StackTrace stackTrace) => + ErrorLogger.instance.captureException( + e, + message: 'Failed to launch $url', + type: 'launch_url_failed', + stackTrace: stackTrace, + ), + ), + ); } static Future makeApiRequest({ diff --git a/lib/src/transformers/node_transformers/passive_divider_transformer.dart b/lib/src/transformers/node_transformers/passive_divider_transformer.dart index db27b3ef..d4b4b689 100644 --- a/lib/src/transformers/node_transformers/passive_divider_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_divider_transformer.dart @@ -1,6 +1,5 @@ import 'package:codelessly_api/codelessly_api.dart'; import 'package:flutter/material.dart'; -import 'package:url_launcher/url_launcher.dart'; import '../../../codelessly_sdk.dart'; @@ -76,7 +75,7 @@ class PassiveDividerTransformer extends NodeWidgetTransformer { void onAction(Reaction reaction) { switch (reaction.action.type) { case ActionType.link: - launchUrl(Uri.parse((reaction.action as LinkAction).url)); + FunctionsRepository.openLink((reaction.action as LinkAction).url); default: break; } diff --git a/lib/src/transformers/node_transformers/passive_loading_indicator_transformer.dart b/lib/src/transformers/node_transformers/passive_loading_indicator_transformer.dart index 0102809f..e43d86a3 100644 --- a/lib/src/transformers/node_transformers/passive_loading_indicator_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_loading_indicator_transformer.dart @@ -1,7 +1,6 @@ import 'package:codelessly_api/codelessly_api.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:url_launcher/url_launcher.dart'; import '../../../codelessly_sdk.dart'; @@ -83,7 +82,7 @@ class PassiveLoadingIndicatorTransformer void onAction(Reaction reaction) { switch (reaction.action.type) { case ActionType.link: - launchUrl(Uri.parse((reaction.action as LinkAction).url)); + FunctionsRepository.openLink((reaction.action as LinkAction).url); default: break; } diff --git a/lib/src/transformers/node_transformers/passive_text_transformer.dart b/lib/src/transformers/node_transformers/passive_text_transformer.dart index e9682571..2d8077e8 100644 --- a/lib/src/transformers/node_transformers/passive_text_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_text_transformer.dart @@ -1,7 +1,6 @@ import 'package:codelessly_api/codelessly_api.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; -import 'package:url_launcher/url_launcher.dart'; import '../../../codelessly_sdk.dart'; @@ -78,7 +77,7 @@ class _PassiveTextWidgetState extends State { if (link.isNotEmpty) { TapGestureRecognizer tapGestureRecognizer = TapGestureRecognizer(debugOwner: link) - ..onTap = () => launchUrl(Uri.parse(link)); + ..onTap = () => FunctionsRepository.openLink(link); tapGestureRecognizerRegistry[link] = tapGestureRecognizer; } }); diff --git a/lib/src/transformers/node_transformers/passive_web_view_transformer_io.dart b/lib/src/transformers/node_transformers/passive_web_view_transformer_io.dart index 33d0d4f5..0259c823 100644 --- a/lib/src/transformers/node_transformers/passive_web_view_transformer_io.dart +++ b/lib/src/transformers/node_transformers/passive_web_view_transformer_io.dart @@ -5,7 +5,6 @@ import 'package:codelessly_api/codelessly_api.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; -import 'package:url_launcher/url_launcher.dart'; import 'package:webview_flutter/webview_flutter.dart'; import 'package:webview_flutter_android/webview_flutter_android.dart'; import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart'; @@ -56,7 +55,7 @@ class PassiveWebViewTransformer extends NodeWidgetTransformer { void onAction(Reaction reaction) { switch (reaction.action.type) { case ActionType.link: - launchUrl(Uri.parse((reaction.action as LinkAction).url)); + FunctionsRepository.openLink((reaction.action as LinkAction).url); default: break; } diff --git a/lib/src/transformers/node_transformers/passive_web_view_transformer_web.dart b/lib/src/transformers/node_transformers/passive_web_view_transformer_web.dart index ab1e0f0d..6d1a6642 100644 --- a/lib/src/transformers/node_transformers/passive_web_view_transformer_web.dart +++ b/lib/src/transformers/node_transformers/passive_web_view_transformer_web.dart @@ -4,7 +4,6 @@ import 'dart:ui_web'; import 'package:codelessly_api/codelessly_api.dart'; import 'package:flutter/material.dart'; -import 'package:url_launcher/url_launcher.dart'; import 'package:web/web.dart' as web; import 'package:webview_flutter/webview_flutter.dart'; @@ -52,7 +51,7 @@ class PassiveWebViewTransformer extends NodeWidgetTransformer { void onAction(Reaction reaction) { switch (reaction.action.type) { case ActionType.link: - launchUrl(Uri.parse((reaction.action as LinkAction).url)); + FunctionsRepository.openLink((reaction.action as LinkAction).url); default: break; } diff --git a/pubspec.yaml b/pubspec.yaml index 91f6b85a..3d2a3f96 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -33,7 +33,6 @@ dependencies: # Core Flutter collection: '>=1.17.0 <2.0.0' http: '>=1.1.2 <2.0.0' - intl: '>=0.17.0 <1.0.0' logging: ^1.3.0 meta: ">=1.9.0 <2.0.0" shared_preferences: ^2.3.5 @@ -48,13 +47,11 @@ dependencies: equatable: ^3.0.0 vector_math: ^2.1.4 provider: ^6.1.2 - uuid: ^4.5.1 cupertino_icons: ^1.0.9 flutter_svg: ^2.0.17 # File and Image path: ^1.9.0 - path_provider: ^2.1.5 # Storage and Caching hive_ce: ^2.9.0 From 5240fd3721e07d0b691f15e7672d052563777c1e Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 13 Oct 2026 02:51:51 +0000 Subject: [PATCH 13/22] Degrade failed SVG loads and add transformer smoke tests SvgPicture.network had no errorBuilder in icons, SVG icon images and the image builder, so an offline or broken SVG reported an error instead of leaving its space empty (or showing the image error builder). Add widget tests that build thirteen node types through the real PassiveNodeTransformerManager, including an accordion with no children. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01A67XoQYsLVvoMtiKcXXUxY --- .../transformers/transformer_functions.dart | 3 + .../widget_node_transformer_manager.dart | 4 + lib/src/utils/image_builder.dart | 2 + test/transformers_test.dart | 77 +++++++++++++++++++ 4 files changed, 86 insertions(+) create mode 100644 test/transformers_test.dart diff --git a/lib/src/transformers/transformer_functions.dart b/lib/src/transformers/transformer_functions.dart index 5ed1fa3f..bf9c518a 100644 --- a/lib/src/transformers/transformer_functions.dart +++ b/lib/src/transformers/transformer_functions.dart @@ -334,6 +334,9 @@ Widget? retrieveIconWidget( icon.iconImage!, width: size ?? icon.size ?? kDefaultIconSize, height: size ?? icon.size ?? kDefaultIconSize, + errorBuilder: (context, error, stackTrace) => SizedBox.square( + dimension: size ?? icon.size ?? kDefaultIconSize, + ), ); } return SizedBox.square( diff --git a/lib/src/transformers/widget_node_transformer_manager.dart b/lib/src/transformers/widget_node_transformer_manager.dart index bb5a77b1..8ad06de2 100644 --- a/lib/src/transformers/widget_node_transformer_manager.dart +++ b/lib/src/transformers/widget_node_transformer_manager.dart @@ -408,6 +408,8 @@ class SvgIcon extends StatelessWidget { url, fit: fit, colorFilter: ColorFilter.mode(iconColor, BlendMode.srcIn), + // Offline or unavailable icons leave their space empty. + errorBuilder: (context, error, stackTrace) => const SizedBox.shrink(), ), ); } @@ -461,6 +463,8 @@ class SvgIconImage extends StatelessWidget { url!, fit: fit, colorFilter: ColorFilter.mode(iconColor, BlendMode.srcIn), + errorBuilder: (context, error, stackTrace) => + const SizedBox.shrink(), ), ); } diff --git a/lib/src/utils/image_builder.dart b/lib/src/utils/image_builder.dart index 28484d2d..34d3e990 100644 --- a/lib/src/utils/image_builder.dart +++ b/lib/src/utils/image_builder.dart @@ -222,6 +222,8 @@ class _UltimateImageBuilderState extends State { placeholderBuilder: widget.loadingBuilder != null ? (context) => widget.loadingBuilder!(context, null) : null, + errorBuilder: (context, error, stackTrace) => + (widget.errorBuilder ?? _defaultErrorBuilder)(context), ); } else if (url.isBase64Blob) { child = Image.memory( diff --git a/test/transformers_test.dart b/test/transformers_test.dart new file mode 100644 index 00000000..4bc27c1c --- /dev/null +++ b/test/transformers_test.dart @@ -0,0 +1,77 @@ +import 'package:codelessly_api/codelessly_api.dart'; +import 'package:codelessly_sdk/codelessly_sdk.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; + +/// Nodes that build without children or remote data. +List standaloneNodes() => [ + ButtonNode.empty(), + CheckboxNode.empty(), + DividerNode.empty(), + FloatingActionButtonNode.empty(), + // A Cupertino icon renders from the bundled font; Material icons load + // SVGs over the network, which widget tests cannot reach. + IconNode( + id: 'icon', + name: 'Icon', + basicBoxLocal: NodeBox(0, 0, 24, 24), + icon: CupertinoIcon( + codepoint: CupertinoIcons.heart.codePoint, + name: 'heart', + ), + ), + ProgressBarNode.empty(), + RadioNode.empty(), + SliderNode.empty(), + SwitchNode.empty(), + ListTileNode.empty(), + ExpansionTileNode.empty(), + TextNode( + id: 'text', + name: 'Text', + basicBoxLocal: NodeBox(0, 0, 100, 20), + characters: 'Hello', + ), + AccordionNode( + id: 'accordion', + name: 'Accordion', + basicBoxLocal: NodeBox(0, 0, 200, 100), + children: const [], + properties: AccordionProperties(isExpanded: true), + ), +]; + +void main() { + for (final node in standaloneNodes()) { + testWidgets('${node.type} builds without errors', (tester) async { + final registry = NodeRegistry()..setNodes({node.id: node}); + final manager = PassiveNodeTransformerManager( + registry.getNodeByID, + (context, bounds, pageID, layoutID, canvasID) => const SizedBox(), + ); + + await tester.pumpWidget( + ChangeNotifierProvider.value( + value: CodelesslyContext.empty(), + child: MaterialApp( + home: Scaffold( + body: Center( + child: Builder( + builder: (context) => manager.buildWidgetFromNode( + node, + context, + settings: const WidgetBuildSettings(debugLabel: 'test'), + ), + ), + ), + ), + ), + ), + ); + await tester.pump(); + expect(tester.takeException(), isNull); + }); + } +} From 423a6003ee21a9b83f8911f99cc03e5bfd6d991a Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 13 Oct 2026 02:52:51 +0000 Subject: [PATCH 14/22] Prepare 7.0.0: changelog, README fixes and pubspec metadata README: the crash-report option is automaticallySendCrashReports and defaults to false; CodelesslyWidget takes publishSource, not isPreview; fix the functions snippets; document useHostTheme and maxTextScaleFactor; drop an embedded token; use absolute image URLs so they render on pub.dev. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01A67XoQYsLVvoMtiKcXXUxY --- CHANGELOG.md | 40 +++++++++++++++++++++++++++++++++++++++- README.md | 52 +++++++++++++++++++++++++++++----------------------- pubspec.yaml | 10 +++++++++- 3 files changed, 77 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e7f9a088..527272a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,42 @@ -## Unreleased +## 7.0.0 + +### Breaking changes + +- Require Dart 3.13 and Flutter 3.47. +- Migrate to codelessly_api 5.0.0, json_annotation and json_serializable, + replacing the forked codegen that no longer builds on current Dart. +- Remove the `staggerDownloadQueue` argument of `CodelesslyConfig.copyWith`, + which had no effect, and the font parser's command-line `main()`. + +### New + +- Material Symbols render, including fill, weight, grade and optical size. + Cupertino icons render from the cupertino_icons font. Two-tone Material + icons load again. +- `CodelesslyConfig.useHostTheme` and `maxTextScaleFactor` let layouts + follow the app's theme and, within a bound, the system text size. +- Web views reload when their bound URL changes. +- Placeholder nodes deserialize (codelessly_api 5). + +### Fixes + +- Authentication no longer hangs when project claims never arrive or the + token refresh fails, and works on Windows (it decoded the wrong string). +- Initialization no longer hangs when the publish model stream errors. +- Cloud database variables keep one Firestore listener each instead of + accumulating them, so earlier queries can't overwrite newer results. +- Failed onLoad API calls and non-blocking actions are reported instead of + surfacing as uncaught errors. +- `CodelesslyWidget` handles removing an explicit context or controller, + and a missing breakpoint, without throwing. +- PageView, TabBar and Accordion edge cases that threw are fixed; + Accordion respects `isExpanded` and `enabled`, and TabBar `showDivider`. +- Malformed links and failed SVG downloads no longer throw. +- The web view works in WebAssembly builds. +- The auth token and auth response body are no longer logged, and library + code logs through `DebugLogger` instead of `print`. + +### Other changes - Require Dart 3.13 and Flutter 3.47. - Migrate to codelessly_api 5.0.0, json_annotation and json_serializable, diff --git a/README.md b/README.md index 8706efa8..4502679e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![Codelessly Logo](packages/Codelessly-Logo-Text.png) +![Codelessly Logo](https://raw.githubusercontent.com/Codelessly/CodelesslySDK/master/packages/Codelessly-Logo-Text.png) # Codelessly CloudUI™ SDK @@ -6,7 +6,7 @@ > ### Dynamic UI and real-time updates for Flutter apps -![Codelessly Publish UI](packages/Codelessly-Cover.png) +![Codelessly Publish UI](https://raw.githubusercontent.com/Codelessly/CodelesslySDK/master/packages/Codelessly-Cover.png) Supercharge your Flutter apps with dynamic UI and real-time updates. Build and publish UI without code! @@ -60,10 +60,10 @@ CodelesslyWidget( **How to obtain a Layout ID** 1. In the Codelessly Editor, select the **canvas** of your layout. -2. Press the **Preview Icon** in the toolbar. ![CloudUI Preview Icon](packages/preview_icon.png) +2. Press the **Preview Icon** in the toolbar. ![CloudUI Preview Icon](https://raw.githubusercontent.com/Codelessly/CodelesslySDK/master/packages/preview_icon.png) 3. Copy the **layoutID**. -![Codelessly Widget Code](packages/codelessly_widget_code.png) +![Codelessly Widget Code](https://raw.githubusercontent.com/Codelessly/CodelesslySDK/master/packages/codelessly_widget_code.png) ### Complete Example @@ -73,7 +73,7 @@ Here is a complete example: import 'package:codelessly_sdk/codelessly_sdk.dart'; class MyApp extends StatelessWidget { - const MyApp({Key? key}) : super(key: key); + const MyApp({super.key}); @override Widget build(BuildContext context) { @@ -114,7 +114,7 @@ CodelesslyWidget( Use the `${}` templating syntax in input fields to link data from the Codelessly editor to layouts as shown below. -![Data](packages/ui_with_data_linking.png) +![Data](https://raw.githubusercontent.com/Codelessly/CodelesslySDK/master/packages/ui_with_data_linking.png) To link the title of a text widget to the `title` variable in the Codelessly editor, put `${title}` in the text widget’s text field. @@ -144,7 +144,7 @@ the `data` is updated. This how it looks with populated data: -![Data](packages/ui_with_populated_data.png) +![Data](https://raw.githubusercontent.com/Codelessly/CodelesslySDK/master/packages/ui_with_populated_data.png) ## Functions @@ -156,7 +156,7 @@ CodelesslyWidget( functions: { 'functionName': (context, reference, params) { // TODO: Implement your function here - }), + }, }, ), ``` @@ -165,7 +165,7 @@ CodelesslyWidget( Here, we tell the button to call the native `onNextButtonClicked` function when pressed. -![Defining call function action](packages/defining_call_function_action.png) +![Defining call function action](https://raw.githubusercontent.com/Codelessly/CodelesslySDK/master/packages/defining_call_function_action.png) In the Codelessly Editor, you can easily add an Action to a widget. Use the `Call Function` action to invoke `onNextButtonClicked`. @@ -187,10 +187,10 @@ to all `CodelesslyWidget`s. ```dart CodelesslyWidget( - layoutID: '0R0Qe7wgeAJMnj3MGW4l', - isPreview: kDebugMode, + layoutID: LAYOUT_ID, + publishSource: kDebugMode ? PublishSource.preview : PublishSource.publish, config: CodelesslyConfig( - authToken: 'LCVyNTxyLCVxQXh3WDc5MFowLjApQXJfWyNdSnlAQjphLyN1', + authToken: AUTH_TOKEN, ), ) ``` @@ -198,11 +198,11 @@ CodelesslyWidget( - `layoutID`: The ID of the published canvas. The ID can be found in Quick Preview or under `Publish > Settings > Published Layouts`. -![Codelessly Published Layout ID](packages/codelessly_published_layout_id.png) +![Codelessly Published Layout ID](https://raw.githubusercontent.com/Codelessly/CodelesslySDK/master/packages/codelessly_published_layout_id.png) -- `isPreview`: Whether the layout is in preview or production mode. Preview mode is meant for debugging the layout and - syncs with the changes made in the editor. Widgets in production mode are only updated when published using the - Publish button. +- `publishSource`: Whether the layout loads from preview or production. Preview mode is meant for debugging the layout + and syncs with the changes made in the editor. Widgets in production mode are only updated when published using the + Publish button. Defaults to the global config's setting. - `config`: An optional `CodelesslyConfig` that holds the information required to authenticate your layout from the server. @@ -211,7 +211,8 @@ lets you embed layouts from other projects with different auth tokens. ## Environment Configuration -The CodelesslyWidget supports **Preview** and **Published** environments via the `isPreview` boolean. +The CodelesslyWidget supports **Preview** and **Published** environments, set globally with `isPreview` or per widget +with `publishSource`. ```dart // Global config. @@ -223,7 +224,8 @@ Codelessly.instance.initialize( // Widget level. Overrides global settings for this widget. CodelesslyWidget( - isPreview: true + layoutID: LAYOUT_ID, + publishSource: PublishSource.preview, ); ``` @@ -275,7 +277,7 @@ Codelessly.instance.initialize( authToken: AUTH_TOKEN, isPreview: kDebugMode, preload: true, - automaticallyCollectCrashReports: true, + automaticallySendCrashReports: true, ), ); ``` @@ -285,8 +287,12 @@ CodelesslyConfig supports the following configuration capabilities. * `isPreview`: Global enable or disable preview-mode setting. * `preload`: Preload layouts to improve performance. When layouts are preloaded, they load instantly and behave like local widgets. `true` by default. -* `automaticallyCollectCrashReports`: Report SDK crashes to Codelessly. `true` by default. You can optionally disable - this behavior. +* `automaticallySendCrashReports`: Report SDK crashes to Codelessly. `false` by default. Only the error and stack trace + are sent, no device data. +* `useHostTheme`: Render layouts with your app's `Theme` instead of a default `ThemeData`. Layouts are styled in the + editor, so they use a default theme by default to look exactly as designed. +* `maxTextScaleFactor`: The largest system text scale layouts follow. `1` by default, which keeps layouts at their + designed size; raise it to let text grow with the user's accessibility settings. ### Global Data and Functions @@ -296,8 +302,8 @@ Data and functions registered in the global `Codelessly.instance` are available Codelessly.instance.initialize( data: {'username': 'Sample User', 'paid': false}, functions: { - 'openPurchasePage': CodelesslyFunction( - (context) => Navigator.pushNamed(context, "PurchasePage")), + 'openPurchasePage': (context, reference, params) => + Navigator.pushNamed(context, 'PurchasePage'), }, ); ``` diff --git a/pubspec.yaml b/pubspec.yaml index 3d2a3f96..7fcbda14 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,8 +1,16 @@ name: codelessly_sdk description: Codelessly CloudUI™ - Server-driven UI for Flutter. Supercharge your Flutter apps with dynamic UI and real-time updates. Build and publish UI without code! -version: 6.2.0 +version: 7.0.0 homepage: https://codelessly.com/ repository: https://github.com/Codelessly/CodelesslySDK +issue_tracker: https://github.com/Codelessly/CodelesslySDK/issues +documentation: https://docs.codelessly.com/ + +topics: + - server-driven-ui + - ui + - no-code + - widget environment: sdk: ^3.13.0 From 793609a7f1e4f388dd2a32e553a0f716dc16f1b1 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 13 Oct 2026 02:53:15 +0000 Subject: [PATCH 15/22] Add CI: analyze, test and a WebAssembly build of the example Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01A67XoQYsLVvoMtiKcXXUxY --- .github/workflows/ci.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..8b420e8a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,23 @@ +name: CI + +on: + push: + pull_request: + +jobs: + check: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v7 + - uses: subosito/flutter-action@v2 + with: + channel: stable + flutter-version: 3.47.7 + cache: true + - run: flutter pub get + - run: flutter analyze + - run: flutter test + - name: Build the example for the web (WebAssembly) + working-directory: example + run: flutter build web --wasm From 445edc55464350cec40e9df5bbac305878723b8f Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 13 Oct 2026 03:01:32 +0000 Subject: [PATCH 16/22] Resolve codelessly_api from its master branch The API changes are merged (Codelessly/CodelesslyAPI#34). Keep the git override until codelessly_api 5.0.0 is published to pub.dev. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01A67XoQYsLVvoMtiKcXXUxY --- example/pubspec.yaml | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 500f7def..052a9792 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -29,4 +29,4 @@ dependency_overrides: codelessly_api: git: url: https://github.com/Codelessly/CodelesslyAPI.git - ref: claude/hopeful-brown-c8o8x7 + ref: master diff --git a/pubspec.yaml b/pubspec.yaml index 7fcbda14..75dff0c5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -96,4 +96,4 @@ dependency_overrides: codelessly_api: git: url: https://github.com/Codelessly/CodelesslyAPI.git - ref: claude/hopeful-brown-c8o8x7 + ref: master From d3801c40f2d172b7a118e463d24a0d57513f7368 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 13 Oct 2026 03:22:59 +0000 Subject: [PATCH 17/22] Depend on codelessly_api 5.0.0 from pub.dev 5.0.0 is published, so drop the git dependency_overrides from the package and the example. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01A67XoQYsLVvoMtiKcXXUxY --- example/pubspec.yaml | 7 ------- pubspec.yaml | 7 ------- 2 files changed, 14 deletions(-) diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 052a9792..ce8e8ac5 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -23,10 +23,3 @@ dev_dependencies: flutter: uses-material-design: true - -# codelessly_api 5.0.0 is not on pub.dev yet. Remove once it is published. -dependency_overrides: - codelessly_api: - git: - url: https://github.com/Codelessly/CodelesslyAPI.git - ref: master diff --git a/pubspec.yaml b/pubspec.yaml index 75dff0c5..7156fd09 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -90,10 +90,3 @@ false_secrets: - /lib/src/firebase_options_prod.dart scripts: scripts.yaml - -# codelessly_api 5.0.0 is not on pub.dev yet. Remove once it is published. -dependency_overrides: - codelessly_api: - git: - url: https://github.com/Codelessly/CodelesslyAPI.git - ref: master From 6089b31f8f9623aafa250f80fc46bc15f43536d8 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 13 Oct 2026 03:28:50 +0000 Subject: [PATCH 18/22] Add regression tests for the auth and data-layer fixes - AuthManager: Windows reads claims from the token, postAuthSuccess completes when the claim arrives, and times out or fails instead of hanging. The claims wait is now a constructor parameter so tests do not wait 30 seconds. - DataManager: a publish model stream error fails listenToPublishModel instead of hanging. - FirestoreCloudDatabase: a new query replaces the variable's previous listener and reset() cancels them all. Each test fails against the code before its fix. The Firebase fakes are hand-written because fake_cloud_firestore and firebase_auth_mocks still require equatable 2. Export DebugLogger and ErrorLogger: DataManager's public constructor takes an ErrorLogger that apps could not name. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01A67XoQYsLVvoMtiKcXXUxY --- CHANGELOG.md | 3 + lib/codelessly_sdk.dart | 2 + lib/src/auth/auth_manager.dart | 5 +- lib/src/cache/codelessly_cache_manager.dart | 2 - lib/src/codelessly.dart | 2 - lib/src/data/cloud_database.dart | 1 - lib/src/data/data_manager.dart | 2 - lib/src/data/firebase_data_repository.dart | 1 - lib/src/data/network_data_repository.dart | 2 - lib/src/data/web_data_repository.dart | 1 - lib/src/functions/functions_repository.dart | 2 - .../passive_canvas_transformer.dart | 1 - .../passive_embedded_video_transformer.dart | 1 - .../passive_grid_view_transformer.dart | 1 - .../passive_list_view_transformer.dart | 1 - .../passive_rectangle_transformer.dart | 1 - .../passive_web_view_transformer_io.dart | 1 - .../utils/condition_evaluator.dart | 1 - lib/src/ui/codelessly_widget.dart | 2 - lib/src/ui/codelessly_widget_controller.dart | 2 - lib/src/ui/layout_builder.dart | 2 - lib/src/utils/image_builder.dart | 1 - test/auth_manager_test.dart | 130 ++++++++++++++++++ test/cloud_database_test.dart | 92 +++++++++++++ test/data_manager_test.dart | 66 +++++++++ 25 files changed, 295 insertions(+), 30 deletions(-) create mode 100644 test/auth_manager_test.dart create mode 100644 test/cloud_database_test.dart create mode 100644 test/data_manager_test.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 527272a1..2a0d3a48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,9 @@ - Material Symbols render, including fill, weight, grade and optical size. Cupertino icons render from the cupertino_icons font. Two-tone Material icons load again. +- `DebugLogger` and `ErrorLogger` are exported; `DataManager`'s public + constructor already required an `ErrorLogger`. +- `AuthManager` takes a `claimsPropagationTimeout`. - `CodelesslyConfig.useHostTheme` and `maxTextScaleFactor` let layouts follow the app's theme and, within a bound, the system text size. - Web views reload when their bound URL changes. diff --git a/lib/codelessly_sdk.dart b/lib/codelessly_sdk.dart index 9e16e0f4..0fb7c904 100644 --- a/lib/codelessly_sdk.dart +++ b/lib/codelessly_sdk.dart @@ -15,6 +15,8 @@ export 'src/data/network_data_repository.dart'; export 'src/data/web_data_repository.dart'; export 'src/functions/functions.dart'; export 'src/functions/functions_repository.dart'; +export 'src/logging/debug_logger.dart'; +export 'src/logging/error_logger.dart'; export 'src/model/asset_model.dart'; export 'src/model/auth_data.dart'; export 'src/model/custom_component.dart'; diff --git a/lib/src/auth/auth_manager.dart b/lib/src/auth/auth_manager.dart index 7d1aa332..f4c15ba2 100644 --- a/lib/src/auth/auth_manager.dart +++ b/lib/src/auth/auth_manager.dart @@ -7,8 +7,6 @@ import 'package:http/http.dart' as http; import 'package:jwt_decoder/jwt_decoder.dart'; import '../../codelessly_sdk.dart'; -import '../logging/debug_logger.dart'; -import '../logging/error_logger.dart'; /// An implementation that uses Firebase auth to authenticate users and manage /// authentication state. Handles anonymous authentication, token verification, @@ -67,6 +65,7 @@ class AuthManager { required this.firebaseAuth, required this.client, this._authData, + this.claimsPropagationTimeout = const Duration(seconds: 30), }) { if (_authData != null) { _authStreamController.add(_authData); @@ -272,7 +271,7 @@ class AuthManager { /// How long [postAuthSuccess] waits for the project claim to appear on the /// user's token before failing authentication. - static const Duration claimsPropagationTimeout = Duration(seconds: 30); + final Duration claimsPropagationTimeout; /// This method is called after successful authentication of the token. /// It checks if the user has access to the project via token claims and if diff --git a/lib/src/cache/codelessly_cache_manager.dart b/lib/src/cache/codelessly_cache_manager.dart index f3764c84..4169cd69 100644 --- a/lib/src/cache/codelessly_cache_manager.dart +++ b/lib/src/cache/codelessly_cache_manager.dart @@ -5,8 +5,6 @@ import 'package:flutter/foundation.dart'; import 'package:hive_ce_flutter/hive_flutter.dart'; import '../../codelessly_sdk.dart'; -import '../logging/debug_logger.dart'; -import '../logging/error_logger.dart'; /// Handles caching of any data that needs to be cached via Hive. class CodelesslyCacheManager extends CacheManager { diff --git a/lib/src/codelessly.dart b/lib/src/codelessly.dart index c922c6e9..46bac096 100644 --- a/lib/src/codelessly.dart +++ b/lib/src/codelessly.dart @@ -9,8 +9,6 @@ import 'package:flutter/widgets.dart'; import 'package:http/http.dart'; import '../codelessly_sdk.dart'; -import 'logging/debug_logger.dart'; -import 'logging/error_logger.dart'; import 'logging/stat_tracker.dart'; import 'utils/codelessly_http_client.dart'; diff --git a/lib/src/data/cloud_database.dart b/lib/src/data/cloud_database.dart index 5de6dea9..f5e3a7fb 100644 --- a/lib/src/data/cloud_database.dart +++ b/lib/src/data/cloud_database.dart @@ -5,7 +5,6 @@ import 'package:codelessly_api/codelessly_api.dart'; import 'package:flutter/widgets.dart'; import '../../codelessly_sdk.dart'; -import '../logging/debug_logger.dart'; import '../logging/stat_tracker.dart'; import '../utils/constants.dart'; diff --git a/lib/src/data/data_manager.dart b/lib/src/data/data_manager.dart index f17bd576..5187551d 100644 --- a/lib/src/data/data_manager.dart +++ b/lib/src/data/data_manager.dart @@ -7,8 +7,6 @@ import 'package:hive_ce_flutter/hive_flutter.dart'; import 'package:logging/logging.dart'; import '../../codelessly_sdk.dart'; -import '../logging/debug_logger.dart'; -import '../logging/error_logger.dart'; import '../logging/stat_tracker.dart'; /// Orchestrates the data flow for the SDK. diff --git a/lib/src/data/firebase_data_repository.dart b/lib/src/data/firebase_data_repository.dart index 5af8079e..73584956 100644 --- a/lib/src/data/firebase_data_repository.dart +++ b/lib/src/data/firebase_data_repository.dart @@ -3,7 +3,6 @@ import 'dart:async'; import 'package:cloud_firestore/cloud_firestore.dart'; import '../../codelessly_sdk.dart'; -import '../logging/error_logger.dart'; import '../logging/stat_tracker.dart'; /// Handles the data flow of [SDKPublishModel] from the server. diff --git a/lib/src/data/network_data_repository.dart b/lib/src/data/network_data_repository.dart index 2abba2fa..e17ca3a5 100644 --- a/lib/src/data/network_data_repository.dart +++ b/lib/src/data/network_data_repository.dart @@ -6,8 +6,6 @@ import 'package:http/http.dart' as http; import 'package:logging/logging.dart'; import '../../codelessly_sdk.dart'; -import '../logging/debug_logger.dart'; -import '../logging/error_logger.dart'; import '../logging/stat_tracker.dart'; /// An abstract class that represents the operations that a [DataManager] will diff --git a/lib/src/data/web_data_repository.dart b/lib/src/data/web_data_repository.dart index 209e8f5d..43d11e52 100644 --- a/lib/src/data/web_data_repository.dart +++ b/lib/src/data/web_data_repository.dart @@ -4,7 +4,6 @@ import 'dart:convert'; import 'package:http/http.dart' as http; import '../../codelessly_sdk.dart'; -import '../logging/error_logger.dart'; /// A [NetworkDataRepository] implementation that utilizes the Firebase Cloud /// Functions to retrieve the relevant data. diff --git a/lib/src/functions/functions_repository.dart b/lib/src/functions/functions_repository.dart index 492bde38..28951c6d 100644 --- a/lib/src/functions/functions_repository.dart +++ b/lib/src/functions/functions_repository.dart @@ -13,8 +13,6 @@ import 'package:url_launcher/url_launcher.dart'; import '../../codelessly_sdk.dart'; import '../logging/stat_tracker.dart'; import '../ui/codelessly_dialog_widget.dart'; -import '../logging/debug_logger.dart'; -import '../logging/error_logger.dart'; /// Enum representing the types of API requests. enum ApiRequestType { diff --git a/lib/src/transformers/node_transformers/passive_canvas_transformer.dart b/lib/src/transformers/node_transformers/passive_canvas_transformer.dart index f5de87b8..7b5c2d8e 100644 --- a/lib/src/transformers/node_transformers/passive_canvas_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_canvas_transformer.dart @@ -7,7 +7,6 @@ import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; import '../../../codelessly_sdk.dart'; -import '../../logging/debug_logger.dart'; class PassiveCanvasTransformer extends NodeWidgetTransformer { static const String name = 'PassiveCanvasWidget'; diff --git a/lib/src/transformers/node_transformers/passive_embedded_video_transformer.dart b/lib/src/transformers/node_transformers/passive_embedded_video_transformer.dart index 689c01db..a2f5b27d 100644 --- a/lib/src/transformers/node_transformers/passive_embedded_video_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_embedded_video_transformer.dart @@ -9,7 +9,6 @@ import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart'; import '../../../codelessly_sdk.dart'; import '../web/web_webview_platform.dart'; -import '../../logging/debug_logger.dart'; class PassiveEmbeddedVideoTransformer extends NodeWidgetTransformer { diff --git a/lib/src/transformers/node_transformers/passive_grid_view_transformer.dart b/lib/src/transformers/node_transformers/passive_grid_view_transformer.dart index 026d8e33..0cff190c 100644 --- a/lib/src/transformers/node_transformers/passive_grid_view_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_grid_view_transformer.dart @@ -8,7 +8,6 @@ import 'package:flutter/rendering.dart'; import 'package:provider/provider.dart'; import '../../../codelessly_sdk.dart'; -import '../../logging/error_logger.dart'; import '../../ui/draggable_scroll_configuration.dart'; class PassiveGridViewTransformer extends NodeWidgetTransformer { diff --git a/lib/src/transformers/node_transformers/passive_list_view_transformer.dart b/lib/src/transformers/node_transformers/passive_list_view_transformer.dart index 4c787ca2..79a63c9a 100644 --- a/lib/src/transformers/node_transformers/passive_list_view_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_list_view_transformer.dart @@ -8,7 +8,6 @@ import 'package:flutter/rendering.dart'; import 'package:provider/provider.dart'; import '../../../codelessly_sdk.dart'; -import '../../logging/error_logger.dart'; import '../../ui/draggable_scroll_configuration.dart'; class PassiveListViewTransformer extends NodeWidgetTransformer { diff --git a/lib/src/transformers/node_transformers/passive_rectangle_transformer.dart b/lib/src/transformers/node_transformers/passive_rectangle_transformer.dart index 4294940d..39c6f5e2 100644 --- a/lib/src/transformers/node_transformers/passive_rectangle_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_rectangle_transformer.dart @@ -6,7 +6,6 @@ import 'package:flutter/material.dart'; import 'package:vector_math/vector_math_64.dart' as vec_math; import '../../../codelessly_sdk.dart'; -import '../../logging/debug_logger.dart'; class PassiveRectangleTransformer extends NodeWidgetTransformer { PassiveRectangleTransformer(super.getNode, super.manager); diff --git a/lib/src/transformers/node_transformers/passive_web_view_transformer_io.dart b/lib/src/transformers/node_transformers/passive_web_view_transformer_io.dart index 0259c823..6afadbaa 100644 --- a/lib/src/transformers/node_transformers/passive_web_view_transformer_io.dart +++ b/lib/src/transformers/node_transformers/passive_web_view_transformer_io.dart @@ -9,7 +9,6 @@ import 'package:webview_flutter/webview_flutter.dart'; import 'package:webview_flutter_android/webview_flutter_android.dart'; import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart'; -import '../../logging/debug_logger.dart'; import '../../../codelessly_sdk.dart'; diff --git a/lib/src/transformers/utils/condition_evaluator.dart b/lib/src/transformers/utils/condition_evaluator.dart index 23068d9d..348b4ff4 100644 --- a/lib/src/transformers/utils/condition_evaluator.dart +++ b/lib/src/transformers/utils/condition_evaluator.dart @@ -2,7 +2,6 @@ import 'package:codelessly_api/codelessly_api.dart'; import 'package:collection/collection.dart'; import '../../../codelessly_sdk.dart'; -import '../../logging/debug_logger.dart'; class ConditionEvaluator implements diff --git a/lib/src/ui/codelessly_widget.dart b/lib/src/ui/codelessly_widget.dart index f1c3f26f..fcac0c2f 100644 --- a/lib/src/ui/codelessly_widget.dart +++ b/lib/src/ui/codelessly_widget.dart @@ -6,8 +6,6 @@ import 'package:flutter/scheduler.dart'; import 'package:provider/provider.dart'; import '../../codelessly_sdk.dart'; -import '../logging/debug_logger.dart'; -import '../logging/error_logger.dart'; import '../logging/stat_tracker.dart'; import 'codelessly_error_screen.dart'; import 'codelessly_loading_screen.dart'; diff --git a/lib/src/ui/codelessly_widget_controller.dart b/lib/src/ui/codelessly_widget_controller.dart index fcf43381..65cffcbc 100644 --- a/lib/src/ui/codelessly_widget_controller.dart +++ b/lib/src/ui/codelessly_widget_controller.dart @@ -4,8 +4,6 @@ import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/material.dart'; import '../../codelessly_sdk.dart'; -import '../logging/debug_logger.dart'; -import '../logging/error_logger.dart'; Widget _defaultLayoutBuilder(BuildContext context, Widget layout) => layout; diff --git a/lib/src/ui/layout_builder.dart b/lib/src/ui/layout_builder.dart index 6521359c..1e416896 100644 --- a/lib/src/ui/layout_builder.dart +++ b/lib/src/ui/layout_builder.dart @@ -6,8 +6,6 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../../codelessly_sdk.dart'; -import '../logging/debug_logger.dart'; -import '../logging/error_logger.dart'; /// A widget that retrieves a layout from a [CodelesslyWidgetController]. class CodelesslyLayoutRetriever extends StatefulWidget { diff --git a/lib/src/utils/image_builder.dart b/lib/src/utils/image_builder.dart index 34d3e990..207b0cde 100644 --- a/lib/src/utils/image_builder.dart +++ b/lib/src/utils/image_builder.dart @@ -5,7 +5,6 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:google_fonts/google_fonts.dart'; import '../../codelessly_sdk.dart'; -import '../logging/debug_logger.dart'; /// Ultimate widget to build image from url, bytes or paint. Supports Gifs and /// SVGs too! diff --git a/test/auth_manager_test.dart b/test/auth_manager_test.dart new file mode 100644 index 00000000..757dfc55 --- /dev/null +++ b/test/auth_manager_test.dart @@ -0,0 +1,130 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:codelessly_sdk/codelessly_sdk.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:http/http.dart' as http; + +class _FakeIdTokenResult extends Fake implements IdTokenResult { + _FakeIdTokenResult(this.claims) : token = _jwt(claims); + + @override + final Map? claims; + + @override + final String? token; + + static String _jwt(Map? claims) { + String part(Object json) => + base64Url.encode(utf8.encode(jsonEncode(json))).replaceAll('=', ''); + return '${part({'alg': 'none'})}.${part(claims ?? {})}.signature'; + } +} + +class _FakeUser extends Fake implements User { + _FakeUser(this.result); + + final Future Function() result; + + @override + Future getIdTokenResult([bool forceRefresh = false]) => + result(); +} + +class _FakeAuth extends Fake implements FirebaseAuth { + _FakeAuth(this.currentUser, this.changes); + + @override + final User? currentUser; + + final Stream changes; + + @override + Stream userChanges() => changes; +} + +class _NoCache extends Fake implements CacheManager {} + +AuthManager authManager(FirebaseAuth auth) => AuthManager( + config: CodelesslyConfig(authToken: 'token'), + cacheManager: _NoCache(), + firebaseAuth: auth, + client: http.Client(), + claimsPropagationTimeout: const Duration(milliseconds: 100), +); + +AuthData authData() => AuthData( + authToken: 'token', + projectId: 'project', + timestamp: DateTime(2026), + owner: 'owner', + teams: const {}, + users: const {}, + roles: const {}, +); + +void main() { + group('checkClaimsForProject', () { + final result = _FakeIdTokenResult({ + 'project_ids': ['project'], + }); + + test('reads the claims', () { + final manager = authManager(_FakeAuth(null, const Stream.empty())); + expect(manager.checkClaimsForProject(result, 'project'), isTrue); + expect(manager.checkClaimsForProject(result, 'other'), isFalse); + }); + + test('decodes the token on Windows', () { + debugDefaultTargetPlatformOverride = TargetPlatform.windows; + addTearDown(() => debugDefaultTargetPlatformOverride = null); + final manager = authManager(_FakeAuth(null, const Stream.empty())); + expect(manager.checkClaimsForProject(result, 'project'), isTrue); + }); + }); + + group('postAuthSuccess', () { + test('completes once the project claim arrives', () async { + final changes = StreamController(); + addTearDown(changes.close); + var claims = {}; + final user = _FakeUser(() async => _FakeIdTokenResult(claims)); + final manager = authManager(_FakeAuth(user, changes.stream)); + + final done = manager.postAuthSuccess(authData()); + await Future.delayed(Duration.zero); + claims = { + 'project_ids': ['project'], + }; + changes.add(user); + await expectLater(done, completes); + }); + + test('times out when the claim never arrives', () async { + final user = _FakeUser(() async => _FakeIdTokenResult({})); + final manager = authManager(_FakeAuth(user, const Stream.empty())); + await expectLater( + manager.postAuthSuccess(authData()), + throwsA(isA()), + ); + }); + + test('fails instead of hanging when refreshing the token fails', () async { + final changes = StreamController(); + addTearDown(changes.close); + var calls = 0; + final user = _FakeUser(() async { + if (calls++ == 0) return _FakeIdTokenResult({}); + throw FirebaseAuthException(code: 'network-request-failed'); + }); + final manager = authManager(_FakeAuth(user, changes.stream)); + + final done = manager.postAuthSuccess(authData()); + await Future.delayed(Duration.zero); + changes.add(user); + await expectLater(done, throwsA(isA())); + }); + }); +} diff --git a/test/cloud_database_test.dart b/test/cloud_database_test.dart new file mode 100644 index 00000000..60f5d3b7 --- /dev/null +++ b/test/cloud_database_test.dart @@ -0,0 +1,92 @@ +// Firestore marks its references @sealed; these test fakes stand in for them. +// ignore_for_file: subtype_of_sealed_class + +import 'dart:async'; + +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:codelessly_api/codelessly_api.dart'; +import 'package:codelessly_sdk/codelessly_sdk.dart'; +import 'package:flutter_test/flutter_test.dart'; + +/// One snapshot stream per collection path, so tests can see who listens. +final Map>>> +snapshotStreams = {}; + +class _FakeCollection extends Fake + implements CollectionReference> { + _FakeCollection(this.path); + + @override + final String path; + + @override + DocumentReference> doc([String? id]) => + _FakeDoc('$path/$id'); + + @override + Stream>> snapshots({ + bool includeMetadataChanges = false, + ListenSource source = ListenSource.defaultSource, + }) => snapshotStreams.putIfAbsent(path, StreamController.new).stream; +} + +class _FakeDoc extends Fake implements DocumentReference> { + _FakeDoc(this.path); + + @override + final String path; + + @override + CollectionReference> collection(String id) => + _FakeCollection('$path/$id'); +} + +class _FakeFirestore extends Fake implements FirebaseFirestore { + @override + CollectionReference> collection(String path) => + _FakeCollection(path); +} + +void main() { + late FirestoreCloudDatabase database; + late Observable variable; + + void stream(String path) => database.streamCollectionToVariable( + path, + variable, + whereFilters: const [], + orderByOperations: const [], + scopedValues: ScopedValues.empty(), + nullSubstitutionMode: NullSubstitutionMode.emptyString, + ); + + bool listening(String path) => snapshotStreams.entries + .firstWhere((entry) => entry.key.endsWith('/$path')) + .value + .hasListener; + + setUp(() { + snapshotStreams.clear(); + database = FirestoreCloudDatabase( + 'project', + PublishSource.publish, + firestore: _FakeFirestore(), + ); + variable = Observable(VariableData(id: 'items', name: 'items')); + }); + + test('a new query for a variable replaces its previous listener', () { + stream('first'); + expect(listening('first'), isTrue); + + stream('second'); + expect(listening('first'), isFalse); + expect(listening('second'), isTrue); + }); + + test('reset cancels every listener', () { + stream('first'); + database.reset(); + expect(listening('first'), isFalse); + }); +} diff --git a/test/data_manager_test.dart b/test/data_manager_test.dart new file mode 100644 index 00000000..00e37ec5 --- /dev/null +++ b/test/data_manager_test.dart @@ -0,0 +1,66 @@ +import 'dart:async'; + +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:codelessly_sdk/codelessly_sdk.dart'; +import 'package:flutter_test/flutter_test.dart'; + +class _StreamingRepository extends Fake implements NetworkDataRepository { + _StreamingRepository(this.stream); + + final Stream stream; + + @override + Stream streamPublishModel({ + required String projectID, + required PublishSource source, + }) => stream; +} + +DataManager dataManager(Stream stream) => DataManager( + 'test', + config: CodelesslyConfig(authToken: 'token'), + cacheManager: _FakeCache(), + authManager: _FakeAuthManager(), + networkDataRepository: _StreamingRepository(stream), + localDataRepository: _FakeLocalRepository(), + firebaseFirestore: _FakeFirestore(), + errorLogger: ErrorLogger.instance, +); + +class _FakeCache extends Fake implements CacheManager {} + +class _FakeAuthManager extends Fake implements AuthManager {} + +class _FakeLocalRepository extends Fake implements LocalDataRepository {} + +class _FakeFirestore extends Fake implements FirebaseFirestore {} + +void main() { + test('the first publish model completes listenToPublishModel', () async { + final model = SDKPublishModel( + projectId: 'project', + owner: 'owner', + teams: const {}, + users: const {}, + roles: const {}, + public: false, + ); + final manager = dataManager(Stream.value(model)); + expect(await manager.listenToPublishModel('project'), model); + }); + + test( + 'a stream error fails listenToPublishModel instead of hanging', + () async { + final manager = dataManager( + Stream.error(FirebaseException(plugin: 'firestore', code: 'denied')), + ); + await expectLater( + manager + .listenToPublishModel('project') + .timeout(const Duration(seconds: 5)), + throwsA(isA()), + ); + }, + ); +} From 3707d680a62994cfcfd6de34b519d8f3727f223c Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 13 Oct 2026 03:30:32 +0000 Subject: [PATCH 19/22] Await app functions in Call Function actions callFunction discarded the app function's result, so a blocking Call Function action never waited for an async function before the next action ran, and its errors went uncaught. Return the result so the action chain awaits it and reports failures. triggerAction also catches synchronous throws from performAction now. Tests cover ordering, a failing function stopping the chain, and a failing non-blocking function letting it continue; all three fail on the previous code. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01A67XoQYsLVvoMtiKcXXUxY --- CHANGELOG.md | 3 + lib/src/functions/functions_repository.dart | 33 ++++-- test/actions_test.dart | 106 ++++++++++++++++++++ 3 files changed, 134 insertions(+), 8 deletions(-) create mode 100644 test/actions_test.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a0d3a48..344b8009 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,9 @@ accumulating them, so earlier queries can't overwrite newer results. - Failed onLoad API calls and non-blocking actions are reported instead of surfacing as uncaught errors. +- Call Function actions now wait for the app's function (unless marked + non-blocking) and report its errors; before, the next action started + immediately and async errors went uncaught. - `CodelesslyWidget` handles removing an explicit context or controller, and a missing breakpoint, without throwing. - PageView, TabBar and Accordion edge cases that threw are fixed; diff --git a/lib/src/functions/functions_repository.dart b/lib/src/functions/functions_repository.dart index 28951c6d..440c0c93 100644 --- a/lib/src/functions/functions_repository.dart +++ b/lib/src/functions/functions_repository.dart @@ -98,7 +98,9 @@ class FunctionsRepository { return setVariableFromAction(context, action as SetVariableAction, notify: notify); case ActionType.callFunction: - callFunction(context, action as CallFunctionAction); + final result = callFunction(context, action as CallFunctionAction); + // Blocking actions wait for the app's function to finish. + if (result is Future) return result.then((_) => true); return true; case ActionType.callApi: return makeApiRequestFromAction(action as ApiCallAction, context); @@ -1021,11 +1023,17 @@ ${response.body.contains('{') ? const JsonEncoder.withIndent(' ').convert(json. for (final reaction in filteredReactions) { if (!context.mounted) continue; - final future = FunctionsRepository.performAction( - context, - reaction.action, - internalValue: value, - ); + final FutureOr future; + try { + future = FunctionsRepository.performAction( + context, + reaction.action, + internalValue: value, + ); + } catch (e, stackTrace) { + _reportActionError(reaction, e, stackTrace); + return true; + } if (!reaction.action.nonBlocking) { // Await only if this action is not a non-blocking. It must not be // awaited if it is non-blocking. A failed action stops the chain, @@ -1069,7 +1077,12 @@ ${response.body.contains('{') ? const JsonEncoder.withIndent(' ').convert(json. ); } - static void callFunction(BuildContext context, CallFunctionAction action) { + /// Calls the app's function named by [action], returning its result so + /// callers can wait for it and see its errors. + static FutureOr callFunction( + BuildContext context, + CallFunctionAction action, + ) { final ScopedValues scopedValues = ScopedValues.of(context); final CodelesslyContext codelesslyContext = context.read(); @@ -1085,7 +1098,11 @@ ${response.body.contains('{') ? const JsonEncoder.withIndent(' ').convert(json. name: name, ); - function?.call(context, codelesslyContext, Map.unmodifiable(parsedParams)); + return function?.call( + context, + codelesslyContext, + Map.unmodifiable(parsedParams), + ); } static Future setStorageFromAction( diff --git a/test/actions_test.dart b/test/actions_test.dart new file mode 100644 index 00000000..0b1ea830 --- /dev/null +++ b/test/actions_test.dart @@ -0,0 +1,106 @@ +import 'dart:async'; + +import 'package:codelessly_api/codelessly_api.dart'; +import 'package:codelessly_sdk/codelessly_sdk.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; + +Reaction callFunction(String name, {bool nonBlocking = false}) => Reaction( + action: CallFunctionAction(name: name, nonBlocking: nonBlocking), + trigger: TriggerModel(type: TriggerType.click), +); + +/// Pumps a tree with [functions] and returns a context inside it. +Future pumpContext( + WidgetTester tester, + Map functions, +) async { + late BuildContext context; + await tester.pumpWidget( + ChangeNotifierProvider.value( + value: CodelesslyContext.empty()..functions = functions, + child: Builder( + builder: (c) { + context = c; + return const SizedBox(); + }, + ), + ), + ); + return context; +} + +void main() { + testWidgets('blocking functions run in order and are awaited', ( + tester, + ) async { + final calls = []; + final first = Completer(); + final context = await pumpContext(tester, { + 'first': (context, reference, params) { + calls.add('first started'); + return first.future.then((_) => calls.add('first done')); + }, + 'second': (context, reference, params) => calls.add('second'), + }); + + final done = FunctionsRepository.triggerAction( + context, + TriggerType.click, + reactions: [callFunction('first'), callFunction('second')], + ); + await tester.pump(); + expect(calls, ['first started']); + + first.complete(); + await tester.runAsync(() => done); + expect(calls, ['first started', 'first done', 'second']); + }); + + testWidgets('a failing function stops the chain without escaping', ( + tester, + ) async { + final calls = []; + final context = await pumpContext(tester, { + 'fails': (context, reference, params) async => throw StateError('x'), + 'throwsSync': (context, reference, params) => throw StateError('y'), + 'next': (context, reference, params) => calls.add('next'), + }); + + for (final failing in ['fails', 'throwsSync']) { + expect( + await FunctionsRepository.triggerAction( + context, + TriggerType.click, + reactions: [callFunction(failing), callFunction('next')], + ), + isTrue, + ); + } + expect(calls, isEmpty); + expect(tester.takeException(), isNull); + }); + + testWidgets('a failing non-blocking function does not stop the chain', ( + tester, + ) async { + final calls = []; + final context = await pumpContext(tester, { + 'fails': (context, reference, params) async => throw StateError('x'), + 'next': (context, reference, params) => calls.add('next'), + }); + + await FunctionsRepository.triggerAction( + context, + TriggerType.click, + reactions: [ + callFunction('fails', nonBlocking: true), + callFunction('next'), + ], + ); + await tester.pump(); + expect(calls, ['next']); + expect(tester.takeException(), isNull); + }); +} From 2ddb3d540319eab740e069e3b546034d1536dfc3 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 13 Oct 2026 03:40:10 +0000 Subject: [PATCH 20/22] Keep native web view plugins out of web builds of embedded video The embedded video transformer imported webview_flutter_wkwebview and webview_flutter_android directly, pulling dart:io into web builds and making the package incompatible with WebAssembly by pub.dev's analysis. Move the platform-specific controller setup behind a conditional import, as the web view transformer already does. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01A67XoQYsLVvoMtiKcXXUxY --- .../embedded_video_controller.dart | 7 +++ .../embedded_video_controller_io.dart | 40 +++++++++++++++ .../embedded_video_controller_web.dart | 14 +++++ .../passive_embedded_video_transformer.dart | 51 ++----------------- 4 files changed, 66 insertions(+), 46 deletions(-) create mode 100644 lib/src/transformers/node_transformers/embedded_video_controller.dart create mode 100644 lib/src/transformers/node_transformers/embedded_video_controller_io.dart create mode 100644 lib/src/transformers/node_transformers/embedded_video_controller_web.dart diff --git a/lib/src/transformers/node_transformers/embedded_video_controller.dart b/lib/src/transformers/node_transformers/embedded_video_controller.dart new file mode 100644 index 00000000..37d4525b --- /dev/null +++ b/lib/src/transformers/node_transformers/embedded_video_controller.dart @@ -0,0 +1,7 @@ +/// Creates the web view controller for an embedded video. The native +/// implementation configures the iOS and Android web views; the web one keeps +/// their plugins (and dart:io) out of web and WebAssembly builds. +library; + +export 'embedded_video_controller_io.dart' + if (dart.library.js_interop) 'embedded_video_controller_web.dart'; diff --git a/lib/src/transformers/node_transformers/embedded_video_controller_io.dart b/lib/src/transformers/node_transformers/embedded_video_controller_io.dart new file mode 100644 index 00000000..02a2ad12 --- /dev/null +++ b/lib/src/transformers/node_transformers/embedded_video_controller_io.dart @@ -0,0 +1,40 @@ +import 'package:webview_flutter/webview_flutter.dart'; +import 'package:webview_flutter_android/webview_flutter_android.dart'; +import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart'; + +/// A user agent that makes YouTube and Vimeo play inline in mobile web views. +const String _desktopUserAgent = + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 ' + '(KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36'; + +/// Creates a web view controller set up to play embedded videos. +WebViewController createEmbeddedVideoController({ + required bool autoPlay, + required bool isYoutube, +}) { + final PlatformWebViewControllerCreationParams params; + if (WebViewPlatform.instance is WebKitWebViewPlatform) { + params = WebKitWebViewControllerCreationParams( + allowsInlineMediaPlayback: true, + mediaTypesRequiringUserAction: { + if (!autoPlay) ...{PlaybackMediaTypes.audio, PlaybackMediaTypes.video}, + }, + ); + } else if (WebViewPlatform.instance is AndroidWebViewPlatform) { + params = AndroidWebViewControllerCreationParams(); + } else { + params = const PlatformWebViewControllerCreationParams(); + } + + final controller = WebViewController.fromPlatformCreationParams(params) + ..setJavaScriptMode(JavaScriptMode.unrestricted); + + final platform = controller.platform; + if (platform is AndroidWebViewController) { + platform.setMediaPlaybackRequiresUserGesture(!autoPlay); + platform.setUserAgent(_desktopUserAgent); + } else if (platform is WebKitWebViewController && isYoutube) { + platform.setUserAgent(_desktopUserAgent); + } + return controller; +} diff --git a/lib/src/transformers/node_transformers/embedded_video_controller_web.dart b/lib/src/transformers/node_transformers/embedded_video_controller_web.dart new file mode 100644 index 00000000..201cbd48 --- /dev/null +++ b/lib/src/transformers/node_transformers/embedded_video_controller_web.dart @@ -0,0 +1,14 @@ +import 'package:webview_flutter/webview_flutter.dart'; +import 'package:webview_flutter_web/webview_flutter_web.dart'; + +/// Creates a web view controller set up to play embedded videos. +/// +/// The web implementation only supports loadRequest; any other controller +/// call throws. +WebViewController createEmbeddedVideoController({ + required bool autoPlay, + required bool isYoutube, +}) { + WebViewPlatform.instance = WebWebViewPlatform(); + return WebViewController(); +} diff --git a/lib/src/transformers/node_transformers/passive_embedded_video_transformer.dart b/lib/src/transformers/node_transformers/passive_embedded_video_transformer.dart index a2f5b27d..d201c56d 100644 --- a/lib/src/transformers/node_transformers/passive_embedded_video_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_embedded_video_transformer.dart @@ -4,11 +4,9 @@ import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:provider/provider.dart'; import 'package:webview_flutter/webview_flutter.dart'; -import 'package:webview_flutter_android/webview_flutter_android.dart'; -import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart'; import '../../../codelessly_sdk.dart'; -import '../web/web_webview_platform.dart'; +import 'embedded_video_controller.dart'; class PassiveEmbeddedVideoTransformer extends NodeWidgetTransformer { @@ -100,49 +98,10 @@ class _PassiveEmbeddedVideoWidgetState ); return; } - final PlatformWebViewControllerCreationParams params; - if (kIsWeb) { - // WebView on web only supports loadRequest. Any other method invocation - // on the controller will result in an exception. Be aware!! - WebViewPlatform.instance = WebWebViewPlatform(); - _controller = WebViewController(); - } else { - if (WebViewPlatform.instance is WebKitWebViewPlatform) { - params = WebKitWebViewControllerCreationParams( - allowsInlineMediaPlayback: true, - mediaTypesRequiringUserAction: { - if (!widget.node.properties.autoPlay) ...{ - PlaybackMediaTypes.audio, - PlaybackMediaTypes.video, - }, - }, - ); - } else if (WebViewPlatform.instance is AndroidWebViewPlatform) { - params = AndroidWebViewControllerCreationParams(); - } else { - params = const PlatformWebViewControllerCreationParams(); - } - - _controller = WebViewController.fromPlatformCreationParams(params); - _controller.setJavaScriptMode(JavaScriptMode.unrestricted); - } - // _controller.setUserAgent('chrome'); - if (_controller.platform is AndroidWebViewController) { - final controller = _controller.platform as AndroidWebViewController; - controller.setMediaPlaybackRequiresUserGesture( - !widget.node.properties.autoPlay); - // Using this user-agent string to force the video to play in the webview - // on Android. This is a hack, but it works. - controller.setUserAgent( - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36'); - } else if (_controller.platform is WebKitWebViewController) { - final controller = _controller.platform as WebKitWebViewController; - if (widget.node.properties.source == EmbeddedVideoSource.youtube) { - controller.setUserAgent( - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36'); - // controller.setUserAgent('chrome'); - } - } + _controller = createEmbeddedVideoController( + autoPlay: widget.node.properties.autoPlay, + isYoutube: widget.node.properties.source == EmbeddedVideoSource.youtube, + ); final config = context.read().config!; From 3ed8bc4ec360415e58ead2b79fcef39efe14eddf Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 13 Oct 2026 03:41:56 +0000 Subject: [PATCH 21/22] Apply the current Dart formatter and check formatting in CI pub.dev deducts static analysis points for unformatted files; 90 files predated the Dart 3.7 formatter style. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01A67XoQYsLVvoMtiKcXXUxY --- .github/workflows/ci.yml | 1 + lib/src/auth/auth_manager.dart | 171 +++-- lib/src/cache/codelessly_cache_manager.dart | 43 +- lib/src/codelessly.dart | 177 +++-- lib/src/codelessly_config.dart | 82 +- lib/src/data/cloud_database.dart | 74 +- lib/src/data/data_manager.dart | 709 ++++++++++-------- lib/src/data/firebase_data_repository.dart | 42 +- lib/src/data/local_data_repository.dart | 40 +- lib/src/data/local_database.dart | 30 +- lib/src/data/network_data_repository.dart | 62 +- lib/src/data/web_data_repository.dart | 40 +- lib/src/firebase_options_prod.dart | 6 +- lib/src/functions/functions_repository.dart | 495 ++++++------ lib/src/logging/debug_logger.dart | 62 +- lib/src/logging/error_logger.dart | 1 + lib/src/logging/stat_tracker.dart | 54 +- lib/src/model/asset_model.dart | 16 +- lib/src/model/auth_data.dart | 12 +- lib/src/model/custom_component.dart | 92 ++- lib/src/model/font_family.dart | 17 +- lib/src/model/font_file_data.dart | 9 +- lib/src/model/model_http_request.dart | 64 +- lib/src/model/privacy_base.dart | 87 ++- lib/src/model/publish_source.dart | 64 +- lib/src/model/sdk_publish_model.dart | 242 +++--- .../defaults/default_properties.dart | 382 +++++----- .../transformers/defaults/theme_defaults.dart | 52 +- lib/src/transformers/node_transformer.dart | 54 +- .../node_transformer_manager.dart | 25 +- .../passive_accordion_transformer.dart | 12 +- .../passive_app_bar_transformer.dart | 57 +- .../passive_button_transformer.dart | 45 +- .../passive_canvas_transformer.dart | 116 +-- .../passive_checkbox_transformer.dart | 33 +- .../passive_divider_transformer.dart | 15 +- .../passive_dropdown_transformer.dart | 257 ++++--- .../passive_embedded_video_transformer.dart | 149 ++-- .../passive_expansion_tile_transformer.dart | 80 +- ...assive_external_component_transformer.dart | 4 +- ...ve_floating_action_button_transformer.dart | 78 +- .../passive_grid_view_transformer.dart | 33 +- .../passive_icon_transformer.dart | 4 +- .../passive_list_tile_transformer.dart | 72 +- .../passive_list_view_transformer.dart | 103 +-- ...passive_loading_indicator_transformer.dart | 23 +- .../passive_navigation_bar_transformer.dart | 110 +-- .../passive_page_view_transformer.dart | 45 +- .../passive_progressbar_transformer.dart | 48 +- .../passive_rectangle_transformer.dart | 225 +++--- .../passive_row_column_transformer.dart | 75 +- .../passive_slider_transformer.dart | 74 +- .../passive_spacer_transformer.dart | 9 +- .../passive_stack_transformer.dart | 66 +- .../passive_switch_transformer.dart | 40 +- .../passive_tab_bar_transformer.dart | 93 ++- .../passive_text_field_transformer.dart | 140 ++-- .../passive_text_transformer.dart | 9 +- .../passive_variance_transformer.dart | 13 +- .../passive_web_view_transformer_io.dart | 102 +-- .../passive_web_view_transformer_web.dart | 79 +- .../passive_transformer_manager.dart | 84 ++- .../transformers/transformer_functions.dart | 184 +++-- lib/src/transformers/utils/blend_mask.dart | 14 +- .../utils/condition_evaluator.dart | 65 +- .../utils/condition_visitors.dart | 31 +- lib/src/transformers/utils/dash_path.dart | 3 +- .../utils/node_state_provider.dart | 4 +- .../utils/placeholder_painter.dart | 41 +- .../utils/property_value_delegate.dart | 398 +++++----- lib/src/transformers/utils/scoped_values.dart | 37 +- .../transformers/utils/stroke_painter.dart | 108 +-- lib/src/transformers/utils/text_utils.dart | 82 +- lib/src/transformers/utils/variable_span.dart | 6 +- .../widget_node_transformer_manager.dart | 92 ++- lib/src/ui/codelessly_context.dart | 38 +- lib/src/ui/codelessly_dialog_widget.dart | 4 +- lib/src/ui/codelessly_error_screen.dart | 15 +- lib/src/ui/codelessly_widget.dart | 132 ++-- lib/src/ui/codelessly_widget_controller.dart | 189 +++-- .../ui/draggable_scroll_configuration.dart | 5 +- lib/src/ui/layout_builder.dart | 114 +-- lib/src/utils/debouncer.dart | 15 +- lib/src/utils/extensions.dart | 455 ++++++----- lib/src/utils/flutter_icons_data_map.dart | 2 +- lib/src/utils/flutter_icons_names_map.dart | 2 +- lib/src/utils/font_parser.dart | 22 +- lib/src/utils/image_builder.dart | 72 +- lib/src/utils/rgb_to_color.dart | 21 +- lib/src/utils/typed_image_bytes.dart | 14 +- lib/src/utils/utils.dart | 282 ++++--- 91 files changed, 4251 insertions(+), 3694 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8b420e8a..cdb1e774 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,6 +16,7 @@ jobs: flutter-version: 3.47.7 cache: true - run: flutter pub get + - run: dart format --set-exit-if-changed --output=none lib test tool - run: flutter analyze - run: flutter test - name: Build the example for the web (WebAssembly) diff --git a/lib/src/auth/auth_manager.dart b/lib/src/auth/auth_manager.dart index f4c15ba2..38e5076d 100644 --- a/lib/src/auth/auth_manager.dart +++ b/lib/src/auth/auth_manager.dart @@ -78,15 +78,20 @@ class AuthManager { final Stopwatch stopwatch = Stopwatch()..start(); if (firebaseAuth.currentUser == null) { - DebugLogger.instance - .printInfo('Authenticating anonymously...', name: name); + DebugLogger.instance.printInfo( + 'Authenticating anonymously...', + name: name, + ); await firebaseAuth.signInAnonymously(); - DebugLogger.instance - .printInfo('Anonymous authentication successful!', name: name); + DebugLogger.instance.printInfo( + 'Anonymous authentication successful!', + name: name, + ); } else { DebugLogger.instance.printInfo( - 'Already authenticated ${firebaseAuth.currentUser!.isAnonymous ? 'anonymously' : 'with email ${firebaseAuth.currentUser!.email}'}', - name: name); + 'Already authenticated ${firebaseAuth.currentUser!.isAnonymous ? 'anonymously' : 'with email ${firebaseAuth.currentUser!.email}'}', + name: name, + ); } // This step is mandatory to compare claims for auth. If re-auth is @@ -117,12 +122,14 @@ class AuthManager { } else { if (cachedAuthData.authToken != config.authToken) { DebugLogger.instance.printInfo( - 'Auth token mismatch. Cache invalidated.', - name: name); + 'Auth token mismatch. Cache invalidated.', + name: name, + ); } else { DebugLogger.instance.printInfo( - 'Project ID is not in user claims. Cache invalidated.', - name: name); + 'Project ID is not in user claims. Cache invalidated.', + name: name, + ); } await cacheManager.clearAll(); await cacheManager.deleteAllByteData(); @@ -135,8 +142,10 @@ class AuthManager { await cacheManager.deleteAllByteData(); } } else { - DebugLogger.instance - .printInfo('Auth data is not cached in cache manager.', name: name); + DebugLogger.instance.printInfo( + 'Auth data is not cached in cache manager.', + name: name, + ); } // We verify the auth token. If it the user is NOT already authenticated @@ -152,13 +161,15 @@ class AuthManager { if (!isAuthenticated()) { DebugLogger.instance.printInfo( - 'Token is not authenticated. Authenticating...', - name: name); + 'Token is not authenticated. Authenticating...', + name: name, + ); await authenticate(); } else { DebugLogger.instance.printInfo( - 'Token already authenticated! Verifying in the background...', - name: name); + 'Token already authenticated! Verifying in the background...', + name: name, + ); authenticate().catchError((error) { // Error handling needs to be done here because this will not be caught // by SDK initialization. @@ -168,8 +179,9 @@ class AuthManager { stopwatch.stop(); DebugLogger.instance.printInfo( - 'Auth manager initialized took ${stopwatch.elapsedMilliseconds}ms or ${stopwatch.elapsed.inSeconds}s', - name: name); + 'Auth manager initialized took ${stopwatch.elapsedMilliseconds}ms or ${stopwatch.elapsed.inSeconds}s', + name: name, + ); } /// Disposes this instance of the [AuthManager]. @@ -207,8 +219,8 @@ class AuthManager { /// configured in the [CodelesslyConfig] is returned. PublishSource getBestPublishSource(CodelesslyConfig config) => authData?.isTemplate == true - ? PublishSource.template - : config.publishSource; + ? PublishSource.template + : config.publishSource; /// A helper method to check if the user has access to a project given their /// [result] from [getIdTokenResult] and the [projectId] they're trying to @@ -225,8 +237,9 @@ class AuthManager { if (result == null) { DebugLogger.instance.printInfo( - 'Id token result is null. Cannot check claims for project id [$projectId]', - name: name); + 'Id token result is null. Cannot check claims for project id [$projectId]', + name: name, + ); return false; } @@ -244,27 +257,32 @@ class AuthManager { DebugLogger.instance.printInfo('User claims: $claims', name: name); if (claims.isEmpty) { DebugLogger.instance.printInfo( - 'User claims is null. Cannot check claims for project id [$projectId]', - name: name); + 'User claims is null. Cannot check claims for project id [$projectId]', + name: name, + ); return false; } if (!claims.containsKey('project_ids')) { DebugLogger.instance.printInfo( - 'User claims does not contain project_ids. Cannot check claims for project id [$projectId]', - name: name); + 'User claims does not contain project_ids. Cannot check claims for project id [$projectId]', + name: name, + ); return false; } if (claims['project_ids'] is! List) { DebugLogger.instance.printInfo( - 'User claims project_ids is not a list. Cannot check claims for project id [$projectId]', - name: name); + 'User claims project_ids is not a list. Cannot check claims for project id [$projectId]', + name: name, + ); return false; } final projectIds = claims['project_ids'] as List; - DebugLogger.instance - .printInfo('User project id claims: $projectIds', name: name); + DebugLogger.instance.printInfo( + 'User project id claims: $projectIds', + name: name, + ); return projectIds.contains(projectId); } @@ -295,8 +313,9 @@ class AuthManager { // Check if the user already has access to the project. if (checkClaimsForProject(_idTokenResult, authData.projectId)) { DebugLogger.instance.printInfo( - 'User has access to project since the claim exists already. Completing Firebase Auth process.', - name: name); + 'User has access to project since the claim exists already. Completing Firebase Auth process.', + name: name, + ); return; } @@ -304,14 +323,16 @@ class AuthManager { _idTokenResult = await firebaseAuth.currentUser?.getIdTokenResult(true); if (checkClaimsForProject(_idTokenResult, authData.projectId)) { DebugLogger.instance.printInfo( - 'User has access to project since the claim exists already after force-refreshing. Completing Firebase Auth process.', - name: name); + 'User has access to project since the claim exists already after force-refreshing. Completing Firebase Auth process.', + name: name, + ); return; } DebugLogger.instance.printInfo( - 'Listening & waiting for Firebase Auth state changes to proceed since user token does not have desired claims for project access...', - name: name); + 'Listening & waiting for Firebase Auth state changes to proceed since user token does not have desired claims for project access...', + name: name, + ); // Create a completer to handle the completion of the Firebase Auth process. final Completer completer = Completer(); @@ -325,8 +346,9 @@ class AuthManager { if (completer.isCompleted) return; DebugLogger.instance.printInfo( - 'Firebase Auth state changed. Checking claims...', - name: name); + 'Firebase Auth state changed. Checking claims...', + name: name, + ); try { _idTokenResult = await event?.getIdTokenResult(true); } catch (e, stackTrace) { @@ -342,8 +364,9 @@ class AuthManager { // Check if the user has access to the project. if (checkClaimsForProject(_idTokenResult, authData.projectId)) { DebugLogger.instance.printInfo( - 'User has access to project since the claim exists. Completing Firebase Auth process.', - name: name); + 'User has access to project since the claim exists. Completing Firebase Auth process.', + name: name, + ); completer.complete(event); } }, @@ -363,8 +386,9 @@ class AuthManager { } DebugLogger.instance.printInfo( - 'Auth state changed successfully as expected. Firebase Auth complete.', - name: name); + 'Auth state changed successfully as expected. Firebase Auth complete.', + name: name, + ); } /// Performs a handshake with the server to authenticate the token. @@ -387,8 +411,9 @@ class AuthManager { if (_disposed) { DebugLogger.instance.printInfo( - 'Auth manager was disposed. Aborting authentication.', - name: name); + 'Auth manager was disposed. Aborting authentication.', + name: name, + ); return; } @@ -398,30 +423,41 @@ class AuthManager { await cacheManager.store('auth', _authData!.toJson()); DebugLogger.instance.printInfo('Stored auth data in cache', name: name); - DebugLogger.instance - .printInfo('Authentication successfully!', name: name); + DebugLogger.instance.printInfo( + 'Authentication successfully!', + name: name, + ); } else { _authData = null; _authStreamController.add(_authData); await cacheManager.delete('auth'); - DebugLogger.instance - .printInfo('Failed to authenticate token.', name: name); + DebugLogger.instance.printInfo( + 'Failed to authenticate token.', + name: name, + ); - ErrorLogger.instance.captureException('Authentication failed', - message: 'Failed to authenticate token', type: 'auth_failed'); + ErrorLogger.instance.captureException( + 'Authentication failed', + message: 'Failed to authenticate token', + type: 'auth_failed', + ); return; } } on http.ClientException catch (e, str) { - ErrorLogger.instance.captureException(e, - message: 'Network error during authentication', - type: 'network_error', - stackTrace: str); + ErrorLogger.instance.captureException( + e, + message: 'Network error during authentication', + type: 'network_error', + stackTrace: str, + ); rethrow; } catch (e, str) { - ErrorLogger.instance.captureException(e, - message: 'Failed to authenticate token', - type: 'auth_error', - stackTrace: str); + ErrorLogger.instance.captureException( + e, + message: 'Failed to authenticate token', + type: 'auth_error', + stackTrace: str, + ); rethrow; } } @@ -455,7 +491,8 @@ class AuthManager { // Make a POST request to the server to verify the token. final http.Response result = await client.post( Uri.parse( - '${config.firebaseCloudFunctionsBaseURL}/api/verifyProjectAuthToken'), + '${config.firebaseCloudFunctionsBaseURL}/api/verifyProjectAuthToken', + ), headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer $userToken', @@ -471,8 +508,9 @@ class AuthManager { // successful. if (result.statusCode == 200) { DebugLogger.instance.printInfo( - 'Successful auth token verification response received.', - name: name); + 'Successful auth token verification response received.', + name: name, + ); final jsonBody = jsonDecode(result.body); @@ -483,19 +521,20 @@ class AuthManager { // success. await postSuccess(authData); - return authData; } else { // If the status code of the response is not 200, the authentication failed. // Log the status code, reason, and body of the response. DebugLogger.instance.printInfo( - 'Failed to authenticate token.\nStatus Code: ${result.statusCode}.\nReason: ${result.reasonPhrase}', - name: name); + 'Failed to authenticate token.\nStatus Code: ${result.statusCode}.\nReason: ${result.reasonPhrase}', + name: name, + ); } } catch (e, stacktrace) { DebugLogger.instance.printInfo( - 'Error trying to authenticate token.\nError: $e', - name: name); + 'Error trying to authenticate token.\nError: $e', + name: name, + ); DebugLogger.instance.printInfo('$stacktrace', name: name); } diff --git a/lib/src/cache/codelessly_cache_manager.dart b/lib/src/cache/codelessly_cache_manager.dart index 4169cd69..cf2c4363 100644 --- a/lib/src/cache/codelessly_cache_manager.dart +++ b/lib/src/cache/codelessly_cache_manager.dart @@ -30,9 +30,7 @@ class CodelesslyCacheManager extends CacheManager { final Stopwatch stopwatch = Stopwatch()..start(); try { await Hive.initFlutter('codelessly_sdk'); - box = await Hive.openBox( - 'cache_${config.uniqueID.replaceAll('/', '')}', - ); + box = await Hive.openBox('cache_${config.uniqueID.replaceAll('/', '')}'); filesBox = await Hive.openBox( 'cache_files_${config.uniqueID.replaceAll('/', '')}', ); @@ -80,17 +78,16 @@ class CodelesslyCacheManager extends CacheManager { void dispose() { DebugLogger.instance.printFunction('dispose()', name: name); unawaited( - Future.wait([box.close(), filesBox.close()]).catchError( - (Object e, StackTrace stackTrace) { - ErrorLogger.instance.captureException( - e, - message: 'Failed to close cache', - type: 'cache_close_failed', - stackTrace: stackTrace, - ); - return []; - }, - ), + Future.wait([box.close(), filesBox.close()]) + .catchError((Object e, StackTrace stackTrace) { + ErrorLogger.instance.captureException( + e, + message: 'Failed to close cache', + type: 'cache_close_failed', + stackTrace: stackTrace, + ); + return []; + }), ); } @@ -153,8 +150,10 @@ class CodelesslyCacheManager extends CacheManager { DebugLogger.instance.printInfo('Deleting all byte data...', name: name); try { filesBox.clear(); - DebugLogger.instance - .printInfo('Cache bytes deleted successfully!', name: name); + DebugLogger.instance.printInfo( + 'Cache bytes deleted successfully!', + name: name, + ); } catch (e, str) { ErrorLogger.instance.captureException( e, @@ -245,13 +244,17 @@ class CodelesslyCacheManager extends CacheManager { } final String fileName = path.split('/').last; if (!excludedFileNames.contains(fileName)) { - DebugLogger.instance - .printInfo('\t\tDeleting file: $path', name: name); + DebugLogger.instance.printInfo( + '\t\tDeleting file: $path', + name: name, + ); await filesBox.delete(path); - DebugLogger.instance - .printInfo('\t\tSuccessfully deleted.', name: name); + DebugLogger.instance.printInfo( + '\t\tSuccessfully deleted.', + name: name, + ); purgedFiles++; } diff --git a/lib/src/codelessly.dart b/lib/src/codelessly.dart index 46bac096..6cfccad4 100644 --- a/lib/src/codelessly.dart +++ b/lib/src/codelessly.dart @@ -19,7 +19,9 @@ typedef NavigationListener = void Function( ); typedef BreakpointsListener = void Function( - BuildContext context, Breakpoint breakpoint); + BuildContext context, + Breakpoint breakpoint, +); /// The entry point for accessing the Codelessly SDK. /// @@ -104,10 +106,10 @@ class Codelessly { /// A helper getter to retrieve the active [DataManager] based on the /// [PublishSource] provided by the [CodelesslyConfig]. DataManager get dataManager => switch (config!.publishSource) { - PublishSource.publish => publishDataManager, - PublishSource.preview => previewDataManager, - PublishSource.template => templateDataManager, - }; + PublishSource.publish => publishDataManager, + PublishSource.preview => previewDataManager, + PublishSource.template => templateDataManager, + }; /// Returns the local instance of the Firebase SDK that this instance of the /// Codelessly SDK is using. @@ -227,8 +229,9 @@ class Codelessly { void dispose({bool sealCache = true}) { DebugLogger.instance.printFunction('dispose()', name: name); DebugLogger.instance.printInfo( - 'Disposing SDK. ${sealCache ? 'Sealing cache.' : 'Keeping cache open.'}', - name: name); + 'Disposing SDK. ${sealCache ? 'Sealing cache.' : 'Keeping cache open.'}', + name: name, + ); status = CStatus.empty(); _statusStreamController.close(); @@ -260,8 +263,9 @@ class Codelessly { FutureOr reset({bool clearCache = false}) async { DebugLogger.instance.printFunction('reset()', name: name); DebugLogger.instance.printInfo( - 'Resetting SDK. ${clearCache ? 'Clearing cache.' : 'Keeping cache.'}', - name: name); + 'Resetting SDK. ${clearCache ? 'Clearing cache.' : 'Keeping cache.'}', + name: name, + ); _cacheManager?.reset(); _publishDataManager?.reset(); _previewDataManager?.reset(); @@ -391,8 +395,9 @@ class Codelessly { final FirebaseOptions firebaseOptions = config.firebaseOptions; DebugLogger.instance.printInfo( - 'Initializing Firebase instance with project ID: [${firebaseOptions.projectId}] and Firebase instance name [$firebaseInstanceName]', - name: name); + 'Initializing Firebase instance with project ID: [${firebaseOptions.projectId}] and Firebase instance name [$firebaseInstanceName]', + name: name, + ); final Stopwatch stopwatch = Stopwatch()..start(); try { @@ -411,8 +416,9 @@ class Codelessly { } finally { if (_firebaseApp != null) { DebugLogger.instance.printInfo( - 'Firebase default app not initialized. Call Firebase.initializeApp() before initializing the CodelesslySDK.', - name: name); + 'Firebase default app not initialized. Call Firebase.initializeApp() before initializing the CodelesslySDK.', + name: name, + ); } } @@ -423,33 +429,40 @@ class Codelessly { _didInitializeFirebase = true; DebugLogger.instance.printInfo( - 'Codelessly successfully initialized the default Firebase app.', - name: name); + 'Codelessly successfully initialized the default Firebase app.', + name: name, + ); return; } DebugLogger.instance.printInfo( - 'A default Firebase app was found already registered. Checking for an existing [$firebaseInstanceName] Firebase app.', - name: name); + 'A default Firebase app was found already registered. Checking for an existing [$firebaseInstanceName] Firebase app.', + name: name, + ); FirebaseApp? existingApp; // Check if an existing Firebase app instance can be reused. - existingApp = Firebase.apps - .firstWhereOrNull((app) => app.name == firebaseInstanceName); + existingApp = Firebase.apps.firstWhereOrNull( + (app) => app.name == firebaseInstanceName, + ); if (existingApp != null) { DebugLogger.instance.printInfo( - 'Found an existing Firebase app instance with name: [$firebaseInstanceName]. Using it.', - name: name); + 'Found an existing Firebase app instance with name: [$firebaseInstanceName]. Using it.', + name: name, + ); _firebaseApp = existingApp; } else { // Create a new Firebase app instance if none exists DebugLogger.instance.printInfo( - 'No existing Firebase app instance found with name: [$firebaseInstanceName]. Registering a new one.', - name: name); + 'No existing Firebase app instance found with name: [$firebaseInstanceName]. Registering a new one.', + name: name, + ); _firebaseApp = await Firebase.initializeApp( - name: firebaseInstanceName, options: firebaseOptions); + name: firebaseInstanceName, + options: firebaseOptions, + ); } // Initialize Firestore and FirebaseAuth instances. @@ -459,8 +472,9 @@ class Codelessly { _didInitializeFirebase = true; DebugLogger.instance.printInfo( - 'Firebase instance initialized successfully [${_firebaseApp?.name}].', - name: name); + 'Firebase instance initialized successfully [${_firebaseApp?.name}].', + name: name, + ); } catch (e, str) { ErrorLogger.instance.captureException( e, @@ -472,8 +486,9 @@ class Codelessly { stopwatch.stop(); DebugLogger.instance.printInfo( - 'Firebase initialized in ${stopwatch.elapsed.inMilliseconds}ms or ${stopwatch.elapsed.inSeconds}s', - name: name); + 'Firebase initialized in ${stopwatch.elapsed.inMilliseconds}ms or ${stopwatch.elapsed.inSeconds}s', + name: name, + ); } /// Initializes the internal Firestore instance used by this SDK and @@ -516,9 +531,7 @@ class Codelessly { }) async { DebugLogger.instance.initialize( name: name, - config: DebugLoggerConfig( - debugLog: config?.debugLog ?? false, - ), + config: DebugLoggerConfig(debugLog: config?.debugLog ?? false), ); DebugLogger.instance.printFunction('initialize()', name: name); @@ -569,7 +582,8 @@ class Codelessly { _cacheManager = cacheManager ?? CodelesslyCacheManager(config: _config!); // Create the auth manager. - _authManager = authManager ?? + _authManager = + authManager ?? AuthManager( config: _config!, cacheManager: _cacheManager!, @@ -578,7 +592,8 @@ class Codelessly { ); // Create the publish data manager. - _publishDataManager = publishDataManager ?? + _publishDataManager = + publishDataManager ?? DataManager( 'Publish', config: _config!.copyWith(isPreview: false), @@ -596,7 +611,8 @@ class Codelessly { ); // Create the preview data manager. - _previewDataManager = previewDataManager ?? + _previewDataManager = + previewDataManager ?? DataManager( 'Preview', config: _config!.copyWith(isPreview: true), @@ -615,7 +631,8 @@ class Codelessly { // Create the template data manager. This is always automatically // created. - _templateDataManager = templateDataManager ?? + _templateDataManager = + templateDataManager ?? DataManager( 'Template', config: _config!.copyWith( @@ -637,10 +654,7 @@ class Codelessly { status = CStatus.loading(CLoadingState.createdManagers); - DebugLogger.instance.printInfo( - 'Initializing cache manager', - name: name, - ); + DebugLogger.instance.printInfo('Initializing cache manager', name: name); await _cacheManager!.init(); status = CStatus.loading(CLoadingState.initializedCache); @@ -657,7 +671,8 @@ class Codelessly { StatTracker.instance.init( projectId: projectId, serverUrl: Uri.parse( - '${_config!.firebaseCloudFunctionsBaseURL}/api/trackStatsRequest'), + '${_config!.firebaseCloudFunctionsBaseURL}/api/trackStatsRequest', + ), ); } @@ -680,10 +695,7 @@ class Codelessly { await dataManager.init(layoutID: null); } - DebugLogger.instance.printInfo( - 'Data manager initialized.', - name: name, - ); + DebugLogger.instance.printInfo('Data manager initialized.', name: name); status = CStatus.loading(CLoadingState.initializedDataManagers); } else { if (!initializeDataManagers) { @@ -704,41 +716,48 @@ class Codelessly { 'Since a slug was provided & data manager finished, authenticating in the background...', name: name, ); - _authManager!.init().then((_) async { - if (_authManager!.authData == null) return; - - if (_authManager?.authData?.projectId case String projectId) { - StatTracker.instance.init( - projectId: projectId, - serverUrl: Uri.parse( - '${_config!.firebaseCloudFunctionsBaseURL}/api/trackStatsRequest'), - ); - } - - DebugLogger.instance.printInfo( - '[POST-INIT] Background authentication succeeded. Initializing layout storage.', - name: name, - ); - await dataManager - .onPublishModelLoaded(_authManager!.authData!.projectId); - - DebugLogger.instance.printInfo( - '[POST-INIT] Layout storage initialized. Listening to publish model.', - name: name, - ); - dataManager.listenToPublishModel(_authManager!.authData!.projectId); - }).catchError((e, str) { - DebugLogger.instance.printInfo( - '[POST-INIT] Background authentication failed.', - name: name, - ); - ErrorLogger.instance.captureException( - e, - message: 'Failed to authenticate', - type: 'authentication_failed', - stackTrace: str, - ); - }); + _authManager! + .init() + .then((_) async { + if (_authManager!.authData == null) return; + + if (_authManager?.authData?.projectId case String projectId) { + StatTracker.instance.init( + projectId: projectId, + serverUrl: Uri.parse( + '${_config!.firebaseCloudFunctionsBaseURL}/api/trackStatsRequest', + ), + ); + } + + DebugLogger.instance.printInfo( + '[POST-INIT] Background authentication succeeded. Initializing layout storage.', + name: name, + ); + await dataManager.onPublishModelLoaded( + _authManager!.authData!.projectId, + ); + + DebugLogger.instance.printInfo( + '[POST-INIT] Layout storage initialized. Listening to publish model.', + name: name, + ); + dataManager.listenToPublishModel( + _authManager!.authData!.projectId, + ); + }) + .catchError((e, str) { + DebugLogger.instance.printInfo( + '[POST-INIT] Background authentication failed.', + name: name, + ); + ErrorLogger.instance.captureException( + e, + message: 'Failed to authenticate', + type: 'authentication_failed', + stackTrace: str, + ); + }); status = CStatus.loading(CLoadingState.initializedSlug); } diff --git a/lib/src/codelessly_config.dart b/lib/src/codelessly_config.dart index 0b65f6b2..6328051b 100644 --- a/lib/src/codelessly_config.dart +++ b/lib/src/codelessly_config.dart @@ -127,11 +127,12 @@ class CodelesslyConfig with Equatable { this.debugLog = false, this.useHostTheme = false, this.maxTextScaleFactor = 1, - }) : assert(maxTextScaleFactor >= 1), - firebaseOptions = - firebaseOptions ?? DefaultFirebaseOptionsProd.currentPlatform, - publishSource = publishSource ?? - (isPreview ? PublishSource.preview : PublishSource.publish); + }) : assert(maxTextScaleFactor >= 1), + firebaseOptions = + firebaseOptions ?? DefaultFirebaseOptionsProd.currentPlatform, + publishSource = + publishSource ?? + (isPreview ? PublishSource.preview : PublishSource.publish); /// Creates a new instance of [CodelesslyConfig] with the provided optional /// parameters. @@ -151,45 +152,44 @@ class CodelesslyConfig with Equatable { bool? debugLog, bool? useHostTheme, double? maxTextScaleFactor, - }) => - CodelesslyConfig( - authToken: authToken ?? this.authToken, - slug: slug ?? this.slug, - automaticallySendCrashReports: - automaticallySendCrashReports ?? this.automaticallySendCrashReports, - isPreview: isPreview ?? this.isPreview, - preload: preload ?? this.preload, - firebaseOptions: firebaseOptions ?? this.firebaseOptions, - firebaseCloudFunctionsBaseURL: - firebaseCloudFunctionsBaseURL ?? this.firebaseCloudFunctionsBaseURL, - baseURL: baseURL ?? this.baseURL, - firebaseInstanceName: firebaseInstanceName ?? this.firebaseInstanceName, - clientType: clientType ?? this.clientType, - firebaseProjectId: firebaseProjectId ?? this.firebaseProjectId, - publishSource: publishSource ?? this.publishSource, - debugLog: debugLog ?? this.debugLog, - useHostTheme: useHostTheme ?? this.useHostTheme, - maxTextScaleFactor: maxTextScaleFactor ?? this.maxTextScaleFactor, - ); + }) => CodelesslyConfig( + authToken: authToken ?? this.authToken, + slug: slug ?? this.slug, + automaticallySendCrashReports: + automaticallySendCrashReports ?? this.automaticallySendCrashReports, + isPreview: isPreview ?? this.isPreview, + preload: preload ?? this.preload, + firebaseOptions: firebaseOptions ?? this.firebaseOptions, + firebaseCloudFunctionsBaseURL: + firebaseCloudFunctionsBaseURL ?? this.firebaseCloudFunctionsBaseURL, + baseURL: baseURL ?? this.baseURL, + firebaseInstanceName: firebaseInstanceName ?? this.firebaseInstanceName, + clientType: clientType ?? this.clientType, + firebaseProjectId: firebaseProjectId ?? this.firebaseProjectId, + publishSource: publishSource ?? this.publishSource, + debugLog: debugLog ?? this.debugLog, + useHostTheme: useHostTheme ?? this.useHostTheme, + maxTextScaleFactor: maxTextScaleFactor ?? this.maxTextScaleFactor, + ); @override List get props => [ - authToken, - slug, - automaticallySendCrashReports, - isPreview, - preload, - firebaseOptions, - firebaseCloudFunctionsBaseURL, - baseURL, - firebaseInstanceName, - clientType, - firebaseProjectId, - publishSource, - debugLog, - useHostTheme, - maxTextScaleFactor, - ]; + authToken, + slug, + automaticallySendCrashReports, + isPreview, + preload, + firebaseOptions, + firebaseCloudFunctionsBaseURL, + baseURL, + firebaseInstanceName, + clientType, + firebaseProjectId, + publishSource, + debugLog, + useHostTheme, + maxTextScaleFactor, + ]; } enum CLoadingState { diff --git a/lib/src/data/cloud_database.dart b/lib/src/data/cloud_database.dart index f5e3a7fb..8416ffc6 100644 --- a/lib/src/data/cloud_database.dart +++ b/lib/src/data/cloud_database.dart @@ -58,7 +58,7 @@ abstract class CloudDatabase extends ChangeNotifier { /// /// It will throw an [AssertionError] if the [identifier] is empty. CloudDatabase(this.identifier, this.publishSource) - : assert(identifier.isNotEmpty, 'identifier cannot be empty'); + : assert(identifier.isNotEmpty, 'identifier cannot be empty'); /// Adds a document to the cloud storage at the given [path]. /// @@ -194,14 +194,15 @@ class FirestoreCloudDatabase extends CloudDatabase { static const String name = 'CloudDatabase'; /// Reference to the root document for this session. - late final DocumentReference> rootRef = - firestore.collection(_rootCollection).doc(identifier); + late final DocumentReference> rootRef = firestore + .collection(_rootCollection) + .doc(identifier); String get _rootCollection => switch (publishSource) { - PublishSource.publish => 'data', - PublishSource.preview => 'preview_data', - PublishSource.template => 'template_data', - }; + PublishSource.publish => 'data', + PublishSource.preview => 'preview_data', + PublishSource.template => 'template_data', + }; /// Reference to the Firestore instance. final FirebaseFirestore firestore; @@ -258,8 +259,10 @@ class FirestoreCloudDatabase extends CloudDatabase { await rootRef.set({'project': identifier}); StatTracker.instance.track(StatType.write, 'cloudDatabase/init'); - DebugLogger.instance - .printInfo('Done initializing for $identifier', name: name); + DebugLogger.instance.printInfo( + 'Done initializing for $identifier', + name: name, + ); } /// Gives a collection reference for the given [path]. @@ -292,8 +295,9 @@ class FirestoreCloudDatabase extends CloudDatabase { } else { // this means the ref represents a collection, so we add a collection // with the first part of the path as the id. - final String id = - pathParts.isNotEmpty ? pathParts.removeAt(0) : 'default'; + final String id = pathParts.isNotEmpty + ? pathParts.removeAt(0) + : 'default'; ref = ref.doc(part).collection(id); } } @@ -313,7 +317,9 @@ class FirestoreCloudDatabase extends CloudDatabase { /// [documentId] is the id of the document. If [documentId] is null or empty, /// then "default" is used as the document id. DocumentReference> getDocPath( - String path, String? documentId) { + String path, + String? documentId, + ) { // Use default if null. documentId ??= 'default'; @@ -432,7 +438,9 @@ class FirestoreCloudDatabase extends CloudDatabase { @override Future> getDocumentData( - String path, String documentId) async { + String path, + String documentId, + ) async { final docRef = getDocPath(path, documentId); final snapshot = await docRef.get(); @@ -449,7 +457,8 @@ class FirestoreCloudDatabase extends CloudDatabase { StatTracker.instance.track(StatType.read, 'cloudDatabase/streamDocument'); return snapshot.data()?.let( - (value) => sanitizeCloudDataForUse(value, docId: snapshot.id)) ?? + (value) => sanitizeCloudDataForUse(value, docId: snapshot.id), + ) ?? {}; }); } @@ -492,8 +501,9 @@ class FirestoreCloudDatabase extends CloudDatabase { // Set the variable with success state. variable.set( - variable.value - .copyWith(value: CloudDatabaseVariableUtils.success(data)), + variable.value.copyWith( + value: CloudDatabaseVariableUtils.success(data), + ), ); }, onError: (error) { @@ -575,8 +585,9 @@ class FirestoreCloudDatabase extends CloudDatabase { ); // Set the variable with success state. variable.set( - variable.value - .copyWith(value: CloudDatabaseVariableUtils.success(docs)), + variable.value.copyWith( + value: CloudDatabaseVariableUtils.success(docs), + ), ); }, onError: (error) { @@ -609,10 +620,12 @@ Map sanitizeCloudDataForUse( // breaks reference and allows to modify the data. data = {...data}; - data[SDKConstants.createdAt] = - deserializeCosmicValue(data[SDKConstants.createdAt]); - data[SDKConstants.updatedAt] = - deserializeCosmicValue(data[SDKConstants.updatedAt]); + data[SDKConstants.createdAt] = deserializeCosmicValue( + data[SDKConstants.createdAt], + ); + data[SDKConstants.updatedAt] = deserializeCosmicValue( + data[SDKConstants.updatedAt], + ); data[SDKConstants.id] = docId; // Sort private fields to the bottom. @@ -651,8 +664,9 @@ Map sanitizeCloudDataToSend( late final DateTime now = DateTime.now().toUtc(); if (autoChangeUpdatedAt) { - data[SDKConstants.createdAt] = - serializedCosmicValue(data[SDKConstants.createdAt] ?? now); + data[SDKConstants.createdAt] = serializedCosmicValue( + data[SDKConstants.createdAt] ?? now, + ); data[SDKConstants.updatedAt] = serializedCosmicValue(now); } @@ -682,10 +696,9 @@ String? serializedCosmicValue(Object? value) { Timestamp timestamp => timestamp.toDate().toUtc().toIso8601String(), DateTime dateTime => dateTime.toUtc().toIso8601String(), String string => DateTime.tryParse(string)?.toUtc().toIso8601String(), - int millisecondsSinceEpoch => - DateTime.fromMillisecondsSinceEpoch(millisecondsSinceEpoch) - .toUtc() - .toIso8601String(), + int millisecondsSinceEpoch => DateTime.fromMillisecondsSinceEpoch( + millisecondsSinceEpoch, + ).toUtc().toIso8601String(), _ => null, }; } @@ -695,8 +708,9 @@ DateTime? deserializeCosmicValue(Object? value) { Timestamp timestamp => timestamp.toDate().toUtc(), DateTime dateTime => dateTime.toUtc(), String string => DateTime.tryParse(string)?.toUtc(), - int millisecondsSinceEpoch => - DateTime.fromMillisecondsSinceEpoch(millisecondsSinceEpoch).toUtc(), + int millisecondsSinceEpoch => DateTime.fromMillisecondsSinceEpoch( + millisecondsSinceEpoch, + ).toUtc(), _ => null, }; } diff --git a/lib/src/data/data_manager.dart b/lib/src/data/data_manager.dart index 5187551d..3c6b99ff 100644 --- a/lib/src/data/data_manager.dart +++ b/lib/src/data/data_manager.dart @@ -150,8 +150,10 @@ class DataManager { _publishModel ??= cachedModel; if (_publishModel != null) { - DebugLogger.instance - .printInfo('Publish model is cached locally. Emitting.', name: name); + DebugLogger.instance.printInfo( + 'Publish model is cached locally. Emitting.', + name: name, + ); await emitPublishModel(); loadFontsFromPublishModel(); @@ -183,41 +185,44 @@ class DataManager { ); } - final publishBundleFuture = fetchCompletePublishBundle( - slug: slug, - source: config.publishSource, - ).then((success) { - if (success) { - DebugLogger.instance.printInfo( - '[slug] Complete publish model from slug is downloaded in background. Emitting.', - name: name, - ); - - loadFontsFromPublishModel(); - } else { - errorLogger.captureException( - 'Failed to download complete publish bundle', - message: - 'Failed to download complete publish bundle for slug $slug.', - type: 'bundle_download_failed', - ); - } - }); + final publishBundleFuture = + fetchCompletePublishBundle( + slug: slug, + source: config.publishSource, + ).then((success) { + if (success) { + DebugLogger.instance.printInfo( + '[slug] Complete publish model from slug is downloaded in background. Emitting.', + name: name, + ); + + loadFontsFromPublishModel(); + } else { + errorLogger.captureException( + 'Failed to download complete publish bundle', + message: + 'Failed to download complete publish bundle for slug $slug.', + type: 'bundle_download_failed', + ); + } + }); if (_publishModel == null) { await publishBundleFuture; } else { // Background refresh: report failures instead of leaving them as // unhandled async errors. - unawaited(publishBundleFuture.catchError((Object e, StackTrace st) { - errorLogger.captureException( - e, - message: - 'Failed to refresh publish bundle for slug $slug in background.', - type: 'bundle_download_failed', - stackTrace: st, - ); - })); + unawaited( + publishBundleFuture.catchError((Object e, StackTrace st) { + errorLogger.captureException( + e, + message: + 'Failed to refresh publish bundle for slug $slug in background.', + type: 'bundle_download_failed', + stackTrace: st, + ); + }), + ); } _logTime(stopwatch); @@ -352,8 +357,9 @@ class DataManager { // Listen the publish model document. // It's either going to be fetched for the first time if it doesn't exist // in cache, or it's going to be updated with new data. - final Future firstPublishEvent = - listenToPublishModel(authData.projectId); + final Future firstPublishEvent = listenToPublishModel( + authData.projectId, + ); if (_publishModel != null) { // The cached model is used and the first event is not awaited. Its // errors are already reported by the stream listener. @@ -563,10 +569,7 @@ class DataManager { _localDatabase?.reset(); _localDatabase = await initializeLocalStorage(projectId: projectId); didChange = true; - DebugLogger.instance.printInfo( - 'Local storage initialized.', - name: name, - ); + DebugLogger.instance.printInfo('Local storage initialized.', name: name); } else { DebugLogger.instance.printInfo( 'Local storage already initialized correctly. Skipping.', @@ -574,12 +577,14 @@ class DataManager { ); } - final bool shouldInitCloudStorage = _cloudDatabase == null || + final bool shouldInitCloudStorage = + _cloudDatabase == null || (cloudDatabase!.publishSource != config.publishSource || cloudDatabase!.identifier != projectId); bool isAuthenticated = false; if (shouldInitCloudStorage) { - isAuthenticated = authManager.isAuthenticated() && + isAuthenticated = + authManager.isAuthenticated() && authManager.hasCloudStorageAccess(projectId); } if (shouldInitCloudStorage && isAuthenticated) { @@ -591,10 +596,7 @@ class DataManager { _cloudDatabase = await initializeCloudStorage(projectId: projectId); _publishModelStreamController.add(_publishModel); didChange = true; - DebugLogger.instance.printInfo( - 'Cloud storage initialized.', - name: name, - ); + DebugLogger.instance.printInfo('Cloud storage initialized.', name: name); } else { if (shouldInitCloudStorage) { DebugLogger.instance.printInfo( @@ -612,8 +614,9 @@ class DataManager { return didChange; } - Future initializeLocalStorage( - {required String projectId}) async { + Future initializeLocalStorage({ + required String projectId, + }) async { final Box box = await Hive.openBox(projectId); return HiveLocalDatabase(box, identifier: projectId); } @@ -622,8 +625,9 @@ class DataManager { // [DataManager] by delegating this initialization to the creator of // [DataManager], maybe through a callback so that DataManager can decide // exactly when to initialize this. - Future initializeCloudStorage( - {required String projectId}) async { + Future initializeCloudStorage({ + required String projectId, + }) async { final instance = FirestoreCloudDatabase( projectId, config.publishSource, @@ -660,10 +664,7 @@ class DataManager { name: name, ); } else { - DebugLogger.instance.printInfo( - 'No fonts to load.', - name: name, - ); + DebugLogger.instance.printInfo('No fonts to load.', name: name); } for (final SDKPublishFont font in _publishModel!.fonts.values) { @@ -685,90 +686,91 @@ class DataManager { final Completer completer = Completer(); _publishModelDocumentListener?.cancel(); - _publishModelDocumentListener = networkDataRepository - .streamPublishModel( - projectID: projectId, - source: config.publishSource, - ) - .listen((SDKPublishModel? serverModel) { - if (serverModel == null) return; - - DebugLogger.instance.printInfo( - 'Publish model stream event received.', - name: name, - ); - - final bool isFirstEvent = !completer.isCompleted; - - // If the completer has not completed yet, it needs to be - // completed with the first available publish model form the server. - if (isFirstEvent) { - DebugLogger.instance.printInfo( - 'Completing publish model stream completer since this is the first event.', - name: name, - ); - completer.complete(serverModel); - } - - // If the publish model is null, meaning no publish model was previously - // cached, then this is the first available publish model we have that - // just arrived from the server. - // - // We can skip publish model comparison as there is nothing to compare - // to yet. - // - // Since it is null, it will be hydrated with a value outside of this - // listener when the completer completes. The publish model is emitted - // once that happens, therefore we don't need to emit it here, nor - // compare. - if (_publishModel == null) { - DebugLogger.instance.printInfo( - 'Publish model is null during init and received the first publish model from the server. Skipping comparison in stream.', - name: name, - ); - return; - } - - if (config.slug != null) { - DebugLogger.instance.printInfo( - 'Initialized using the slug, this event is not essential for initial loading.', - name: name, - ); - } else if (isFirstEvent) { - DebugLogger.instance.printInfo( - 'Publish model during init was not null, and we received a new publish model from the server. Comparing...', - name: name, - ); - } else { - DebugLogger.instance.printInfo( - 'Received a new publish model from the server. Comparing...', - name: name, - ); - } - final SDKPublishModel localModel = _publishModel!; - - // Comparison should always be a background process. - processPublishDifference( - serverModel: serverModel, - localModel: localModel, - ); - - DebugLogger.instance.printInfo( - 'Publish model comparison complete.', - name: name, - ); - }) - ..onError((error, str) { - ErrorLogger.instance.captureException( - error, - message: 'Failed to listen to publish model', - type: 'publish_model_stream_failed', - stackTrace: str, - ); - // Without this, initialization would wait forever for a first event - // that never comes, e.g. on a Firestore permission error. - if (!completer.isCompleted) completer.completeError(error, str); - }); + _publishModelDocumentListener = + networkDataRepository + .streamPublishModel( + projectID: projectId, + source: config.publishSource, + ) + .listen((SDKPublishModel? serverModel) { + if (serverModel == null) return; + + DebugLogger.instance.printInfo( + 'Publish model stream event received.', + name: name, + ); + + final bool isFirstEvent = !completer.isCompleted; + + // If the completer has not completed yet, it needs to be + // completed with the first available publish model form the server. + if (isFirstEvent) { + DebugLogger.instance.printInfo( + 'Completing publish model stream completer since this is the first event.', + name: name, + ); + completer.complete(serverModel); + } + + // If the publish model is null, meaning no publish model was previously + // cached, then this is the first available publish model we have that + // just arrived from the server. + // + // We can skip publish model comparison as there is nothing to compare + // to yet. + // + // Since it is null, it will be hydrated with a value outside of this + // listener when the completer completes. The publish model is emitted + // once that happens, therefore we don't need to emit it here, nor + // compare. + if (_publishModel == null) { + DebugLogger.instance.printInfo( + 'Publish model is null during init and received the first publish model from the server. Skipping comparison in stream.', + name: name, + ); + return; + } + + if (config.slug != null) { + DebugLogger.instance.printInfo( + 'Initialized using the slug, this event is not essential for initial loading.', + name: name, + ); + } else if (isFirstEvent) { + DebugLogger.instance.printInfo( + 'Publish model during init was not null, and we received a new publish model from the server. Comparing...', + name: name, + ); + } else { + DebugLogger.instance.printInfo( + 'Received a new publish model from the server. Comparing...', + name: name, + ); + } + final SDKPublishModel localModel = _publishModel!; + + // Comparison should always be a background process. + processPublishDifference( + serverModel: serverModel, + localModel: localModel, + ); + + DebugLogger.instance.printInfo( + 'Publish model comparison complete.', + name: name, + ); + }) + ..onError((error, str) { + ErrorLogger.instance.captureException( + error, + message: 'Failed to listen to publish model', + type: 'publish_model_stream_failed', + stackTrace: str, + ); + // Without this, initialization would wait forever for a first event + // that never comes, e.g. on a Firestore permission error. + if (!completer.isCompleted) completer.completeError(error, str); + }); return completer.future; } @@ -875,8 +877,8 @@ class DataManager { final bool entryChanged = localModel.entryLayoutId != serverModel.entryLayoutId || - localModel.entryCanvasId != serverModel.entryCanvasId || - localModel.entryPageId != serverModel.entryPageId; + localModel.entryCanvasId != serverModel.entryCanvasId || + localModel.entryPageId != serverModel.entryPageId; final bool didLayoutIDMapChange = const MapEquality().equals( localModel.layoutIDMap, @@ -884,17 +886,24 @@ class DataManager { ); if (didLayoutIDMapChange) { localModel = localModel.copyWith(layoutIDMap: serverModel.layoutIDMap); - DebugLogger.instance - .printInfo('DIFF: Layout ID map changed. Updating...', name: name); + DebugLogger.instance.printInfo( + 'DIFF: Layout ID map changed. Updating...', + name: name, + ); } - final bool disabledLayoutsChanged = !(const ListEquality() - .equals(localModel.disabledLayouts, serverModel.disabledLayouts)); + final bool disabledLayoutsChanged = !(const ListEquality().equals( + localModel.disabledLayouts, + serverModel.disabledLayouts, + )); if (disabledLayoutsChanged) { - localModel = - localModel.copyWith(disabledLayouts: serverModel.disabledLayouts); - DebugLogger.instance - .printInfo('DIFF: Disabled layouts changed. Updating...', name: name); + localModel = localModel.copyWith( + disabledLayouts: serverModel.disabledLayouts, + ); + DebugLogger.instance.printInfo( + 'DIFF: Disabled layouts changed. Updating...', + name: name, + ); } if (layoutUpdates.isEmpty && @@ -906,11 +915,14 @@ class DataManager { !entryChanged && !didLayoutIDMapChange && !disabledLayoutsChanged) { - DebugLogger.instance - .printInfo('DIFF: No updates to process.', name: name); + DebugLogger.instance.printInfo( + 'DIFF: No updates to process.', + name: name, + ); return; } else { - DebugLogger.instance.printInfo('''DIFF: Processing updates: + DebugLogger.instance.printInfo( + '''DIFF: Processing updates: | ${layoutUpdates.length} layout updates. | ${fontUpdates.length} font updates. | ${apiUpdates.length} api updates. @@ -920,7 +932,8 @@ class DataManager { | ${entryChanged ? 1 : 0} entry id update${entryChanged ? '' : 's'}. | ${didLayoutIDMapChange ? 'Layout ID map changed.' : 'No layout ID map changes.'} | ${disabledLayoutsChanged ? 'Disabled layout IDs changed.' : 'No disabled layout IDs changed.'}''', - name: name); + name: name, + ); } for (final String layoutID in layoutUpdates.keys) { @@ -928,8 +941,10 @@ class DataManager { switch (updateType) { case UpdateType.delete: - DebugLogger.instance - .printInfo('DIFF: Deleting layout [$layoutID]...', name: name); + DebugLogger.instance.printInfo( + 'DIFF: Deleting layout [$layoutID]...', + name: name, + ); localModel.layouts.remove(layoutID); localDataRepository.deletePublishLayout( layoutID: layoutID, @@ -937,14 +952,16 @@ class DataManager { ); case UpdateType.add: case UpdateType.update: - DebugLogger.instance - .printInfo('DIFF: Downloading layout [$layoutID]...', name: name); - final SDKPublishLayout? layout = - await networkDataRepository.downloadLayoutModel( - projectID: authManager.authData!.projectId, - layoutID: layoutID, - source: config.publishSource, + DebugLogger.instance.printInfo( + 'DIFF: Downloading layout [$layoutID]...', + name: name, ); + final SDKPublishLayout? layout = await networkDataRepository + .downloadLayoutModel( + projectID: authManager.authData!.projectId, + layoutID: layoutID, + source: config.publishSource, + ); if (layout != null) { localModel.layouts[layoutID] = layout; } @@ -956,8 +973,10 @@ class DataManager { switch (updateType) { case UpdateType.delete: - DebugLogger.instance - .printInfo('DIFF: Deleting font [$fontID]...', name: name); + DebugLogger.instance.printInfo( + 'DIFF: Deleting font [$fontID]...', + name: name, + ); localModel.fonts.remove(fontID); localDataRepository.deleteFontBytes( fontID: fontID, @@ -965,14 +984,16 @@ class DataManager { ); case UpdateType.add: case UpdateType.update: - DebugLogger.instance - .printInfo('DIFF: Downloading font [$fontID]...', name: name); - final SDKPublishFont? font = - await networkDataRepository.downloadFontModel( - projectID: authManager.authData!.projectId, - fontID: fontID, - source: config.publishSource, + DebugLogger.instance.printInfo( + 'DIFF: Downloading font [$fontID]...', + name: name, ); + final SDKPublishFont? font = await networkDataRepository + .downloadFontModel( + projectID: authManager.authData!.projectId, + fontID: fontID, + source: config.publishSource, + ); if (font != null) { localModel.fonts[fontID] = font; @@ -986,8 +1007,10 @@ class DataManager { switch (updateType) { case UpdateType.delete: - DebugLogger.instance - .printInfo('DIFF: Deleting api [$apiId]...', name: name); + DebugLogger.instance.printInfo( + 'DIFF: Deleting api [$apiId]...', + name: name, + ); localModel.apis.remove(apiId); localDataRepository.deletePublishApi( apiId: apiId, @@ -995,8 +1018,10 @@ class DataManager { ); case UpdateType.add: case UpdateType.update: - DebugLogger.instance - .printInfo('DIFF: Downloading api [$apiId]...', name: name); + DebugLogger.instance.printInfo( + 'DIFF: Downloading api [$apiId]...', + name: name, + ); final HttpApiData? api = await networkDataRepository.downloadApi( projectID: authManager.authData!.projectId, apiId: apiId, @@ -1014,8 +1039,9 @@ class DataManager { switch (updateType) { case UpdateType.delete: DebugLogger.instance.printInfo( - 'DIFF: Deleting variables for layout [$layoutId]...', - name: name); + 'DIFF: Deleting variables for layout [$layoutId]...', + name: name, + ); localModel.variables.remove(layoutId); localDataRepository.deletePublishVariables( layoutID: layoutId, @@ -1024,14 +1050,15 @@ class DataManager { case UpdateType.add: case UpdateType.update: DebugLogger.instance.printInfo( - 'DIFF: Downloading variables for layout [$layoutId]...', - name: name); + 'DIFF: Downloading variables for layout [$layoutId]...', + name: name, + ); final SDKLayoutVariables? layoutVariables = await networkDataRepository.downloadLayoutVariables( - projectID: authManager.authData!.projectId, - layoutID: layoutId, - source: config.publishSource, - ); + projectID: authManager.authData!.projectId, + layoutID: layoutId, + source: config.publishSource, + ); if (layoutVariables != null) { localModel.variables[layoutId] = layoutVariables; } @@ -1044,8 +1071,9 @@ class DataManager { switch (updateType) { case UpdateType.delete: DebugLogger.instance.printInfo( - 'DIFF: Deleting conditions for layout [$layoutID]...', - name: name); + 'DIFF: Deleting conditions for layout [$layoutID]...', + name: name, + ); localModel.variables.remove(layoutID); localDataRepository.deletePublishConditions( layoutID: layoutID, @@ -1054,22 +1082,25 @@ class DataManager { case UpdateType.add: case UpdateType.update: DebugLogger.instance.printInfo( - 'DIFF: Downloading conditions for layout [$layoutID]...', - name: name); + 'DIFF: Downloading conditions for layout [$layoutID]...', + name: name, + ); final SDKLayoutConditions? layoutConditions = await networkDataRepository.downloadLayoutConditions( - projectID: authManager.authData!.projectId, - layoutID: layoutID, - source: config.publishSource, - ); + projectID: authManager.authData!.projectId, + layoutID: layoutID, + source: config.publishSource, + ); if (layoutConditions != null) { localModel.conditions[layoutID] = layoutConditions; } } } - DebugLogger.instance - .printInfo('DIFF: Done processing updates.', name: name); + DebugLogger.instance.printInfo( + 'DIFF: Done processing updates.', + name: name, + ); _publishModel = localModel.copyWith( updates: serverModel.updates, entryPageId: serverModel.entryPageId, @@ -1280,13 +1311,15 @@ class DataManager { }) async { if (_publishModel != null && queuingDone) { DebugLogger.instance.printInfo( - '[queueLayout] No longer queuing. Downloading layout [$layoutID] immediately...', - name: name); + '[queueLayout] No longer queuing. Downloading layout [$layoutID] immediately...', + name: name, + ); try { await getOrFetchPopulatedLayout(layoutID: layoutID); DebugLogger.instance.printInfo( - '[queueLayout] Layout [$layoutID] download complete.', - name: name); + '[queueLayout] Layout [$layoutID] download complete.', + name: name, + ); } catch (e, str) { DebugLogger.instance.log( '[queueLayout] Layout [$layoutID] failed to download immediately.\nError: $e', @@ -1307,32 +1340,37 @@ class DataManager { if (prioritize) { if (_downloadQueue.first == layoutID) { DebugLogger.instance.printInfo( - '[queueLayout] Layout [$layoutID] is already at the front of the queue. Skipping.', - name: name); + '[queueLayout] Layout [$layoutID] is already at the front of the queue. Skipping.', + name: name, + ); return; } else { DebugLogger.instance.printInfo( - '[queueLayout] Layout [$layoutID] is already in the queue. Moving it to the front to prioritize it.', - name: name); + '[queueLayout] Layout [$layoutID] is already in the queue. Moving it to the front to prioritize it.', + name: name, + ); _downloadQueue.remove(layoutID); } } else { DebugLogger.instance.printInfo( - '[queueLayout] Layout [$layoutID] is already in the queue. Skipping.', - name: name); + '[queueLayout] Layout [$layoutID] is already in the queue. Skipping.', + name: name, + ); return; } } if (prioritize) { DebugLogger.instance.printInfo( - '[queueLayout] Prioritizing this layout. Inserting [$layoutID] to the front of the queue.', - name: name); + '[queueLayout] Prioritizing this layout. Inserting [$layoutID] to the front of the queue.', + name: name, + ); _downloadQueue.insert(0, layoutID); } else { DebugLogger.instance.printInfo( - '[queueLayout] Adding [$layoutID] to the back of the queue.', - name: name); + '[queueLayout] Adding [$layoutID] to the back of the queue.', + name: name, + ); _downloadQueue.add(layoutID); } } @@ -1360,9 +1398,7 @@ class DataManager { /// /// Will return `true` if the layout and its associated fonts were fetched /// successfully, `false` otherwise. - Future getOrFetchPopulatedLayout({ - required String layoutID, - }) async { + Future getOrFetchPopulatedLayout({required String layoutID}) async { DebugLogger.instance.printFunction( 'getOrFetchPopulatedLayout(layoutID: $layoutID)', name: name, @@ -1370,10 +1406,7 @@ class DataManager { final SDKPublishModel? model = _publishModel; final AuthData? auth = authManager.authData; - assert( - model != null, - 'Data manager has not been initialized yet.', - ); + assert(model != null, 'Data manager has not been initialized yet.'); // If the user is not authenticated, then they get whatever is cached and // nothing else. @@ -1387,13 +1420,15 @@ class DataManager { SDKPublishLayout? layout; if (model!.layouts.containsKey(layoutID)) { DebugLogger.instance.printInfo( - '\tLayout [$layoutID] is already cached. Skipping download.', - name: name); + '\tLayout [$layoutID] is already cached. Skipping download.', + name: name, + ); layout = model.layouts[layoutID]; } else { DebugLogger.instance.printInfo( - '\tLayout [$layoutID] is not cached. Downloading...', - name: name); + '\tLayout [$layoutID] is not cached. Downloading...', + name: name, + ); layout = await networkDataRepository.downloadLayoutModel( layoutID: layoutID, projectID: auth.projectId, @@ -1401,13 +1436,15 @@ class DataManager { ); if (layout == null) { DebugLogger.instance.printInfo( - '\tLayout [$layoutID] could not be downloaded.', - name: name); + '\tLayout [$layoutID] could not be downloaded.', + name: name, + ); return false; } else { DebugLogger.instance.printInfo( - '\tLayout [$layoutID] downloaded successfully.', - name: name); + '\tLayout [$layoutID] downloaded successfully.', + name: name, + ); } model.layouts[layoutID] = layout; @@ -1418,43 +1455,50 @@ class DataManager { // Process APIs if (model.updates.layoutApis.containsKey(layoutID)) { DebugLogger.instance.printInfo( - '\tLayout [$layoutID] has ${model.updates.layoutApis[layoutID]!.length} apis.', - name: name); + '\tLayout [$layoutID] has ${model.updates.layoutApis[layoutID]!.length} apis.', + name: name, + ); final Set> apiModels = getOrFetchApis( apiIds: model.updates.layoutApis[layoutID]!, ); - final List results = - await Future.wait(apiModels.map((future) async { - try { - return await future; - } catch (e) { - DebugLogger.instance.log( - 'Error while fetching apis.\nError: $e', - category: DebugCategory.error, - name: name, - level: Level.WARNING, - ); - return null; - } - })); + final List results = await Future.wait( + apiModels.map((future) async { + try { + return await future; + } catch (e) { + DebugLogger.instance.log( + 'Error while fetching apis.\nError: $e', + category: DebugCategory.error, + name: name, + level: Level.WARNING, + ); + return null; + } + }), + ); for (final api in results) { if (api == null) continue; model.apis[api.id] = api; } } else { - DebugLogger.instance - .printInfo('\tLayout [$layoutID] has no apis.', name: name); + DebugLogger.instance.printInfo( + '\tLayout [$layoutID] has no apis.', + name: name, + ); } // Process Variables if (model.updates.variables.containsKey(layoutID)) { - DebugLogger.instance - .printInfo('\tLayout [$layoutID] has variables.', name: name); + DebugLogger.instance.printInfo( + '\tLayout [$layoutID] has variables.', + name: name, + ); try { - final SDKLayoutVariables? variables = - await getOrFetchVariables(layoutID); + final SDKLayoutVariables? variables = await getOrFetchVariables( + layoutID, + ); if (variables != null) { model.variables[layoutID] = variables; } @@ -1467,17 +1511,22 @@ class DataManager { ); } } else { - DebugLogger.instance - .printInfo('Layout [$layoutID] has no variables.', name: name); + DebugLogger.instance.printInfo( + 'Layout [$layoutID] has no variables.', + name: name, + ); } // Process Conditions if (model.updates.conditions.containsKey(layoutID)) { - DebugLogger.instance - .printInfo('\tLayout [$layoutID] has conditions.', name: name); + DebugLogger.instance.printInfo( + '\tLayout [$layoutID] has conditions.', + name: name, + ); try { - final SDKLayoutConditions? conditions = - await getOrFetchConditions(layoutID); + final SDKLayoutConditions? conditions = await getOrFetchConditions( + layoutID, + ); if (conditions != null) { model.conditions[layoutID] = conditions; } @@ -1490,54 +1539,64 @@ class DataManager { ); } } else { - DebugLogger.instance - .printInfo('\tLayout [$layoutID] has no conditions.', name: name); + DebugLogger.instance.printInfo( + '\tLayout [$layoutID] has no conditions.', + name: name, + ); } await emitPublishModel(); savePublishModel(); - DebugLogger.instance - .printInfo('\tLayout [$layoutID] ready, time for fonts...', name: name); + DebugLogger.instance.printInfo( + '\tLayout [$layoutID] ready, time for fonts...', + name: name, + ); // Process Fonts if (model.updates.layoutFonts.containsKey(layoutID)) { DebugLogger.instance.printInfo( - '\tLayout [$layoutID] has ${model.updates.layoutFonts[layoutID]!.length} fonts.', - name: name); + '\tLayout [$layoutID] has ${model.updates.layoutFonts[layoutID]!.length} fonts.', + name: name, + ); // Download or load fonts in the background. - getOrFetchFontModels( - fontIDs: model.updates.layoutFonts[layoutID]!, - ).then((Set fontModels) async { - DebugLogger.instance.printInfo( - '\tFound ${fontModels.length} fonts to fetch for layout [$layoutID].', - name: name); - - for (final SDKPublishFont fontModel in fontModels) { - DebugLogger.instance.printInfo( - '\t\tFontModel [${fontModel.id}] ready. Fetching bytes & loading...', - name: name); - - model.fonts[fontModel.id] = fontModel; - - await getOrFetchFontBytesAndSaveAndLoad(fontModel).then((_) { + getOrFetchFontModels(fontIDs: model.updates.layoutFonts[layoutID]!) + .then((Set fontModels) async { DebugLogger.instance.printInfo( - '\t\tFontModel [${fontModel.id}] loaded. Done!\n', - name: name); + '\tFound ${fontModels.length} fonts to fetch for layout [$layoutID].', + name: name, + ); + + for (final SDKPublishFont fontModel in fontModels) { + DebugLogger.instance.printInfo( + '\t\tFontModel [${fontModel.id}] ready. Fetching bytes & loading...', + name: name, + ); + + model.fonts[fontModel.id] = fontModel; + + await getOrFetchFontBytesAndSaveAndLoad(fontModel).then((_) { + DebugLogger.instance.printInfo( + '\t\tFontModel [${fontModel.id}] loaded. Done!\n', + name: name, + ); + }); + } + }) + .catchError((error, stack) { + DebugLogger.instance.log( + 'Error while fetching fonts.\nError: $error', + category: DebugCategory.error, + name: name, + level: Level.WARNING, + ); }); - } - }).catchError((error, stack) { - DebugLogger.instance.log( - 'Error while fetching fonts.\nError: $error', - category: DebugCategory.error, - name: name, - level: Level.WARNING, - ); - }); } else { - DebugLogger.instance - .printInfo('\tLayout [$layoutID] has no fonts.', name: name); + DebugLogger.instance.printInfo( + '\tLayout [$layoutID] has no fonts.', + name: name, + ); } StatTracker.instance.track(StatType.populatedLayoutDownload, layoutID); @@ -1572,14 +1631,16 @@ class DataManager { } finally { stopwatch.stop(); DebugLogger.instance.printInfo( - 'Publish bundle download stopwatch done in ${stopwatch.elapsedMilliseconds}ms or ${stopwatch.elapsed.inSeconds}s.', - name: name); + 'Publish bundle download stopwatch done in ${stopwatch.elapsedMilliseconds}ms or ${stopwatch.elapsed.inSeconds}s.', + name: name, + ); } if (model != null) { DebugLogger.instance.printInfo( - 'Successfully downloaded complete publish bundle. Emitting it.', - name: name); + 'Successfully downloaded complete publish bundle. Emitting it.', + name: name, + ); _publishModel = model; await emitPublishModel(); savePublishModel(); @@ -1587,8 +1648,9 @@ class DataManager { } DebugLogger.instance.printInfo( - 'Failed to download complete publish bundle in ${stopwatch.elapsedMilliseconds}ms or ${stopwatch.elapsed.inSeconds}s.', - name: name); + 'Failed to download complete publish bundle in ${stopwatch.elapsedMilliseconds}ms or ${stopwatch.elapsed.inSeconds}s.', + name: name, + ); return false; } @@ -1604,16 +1666,18 @@ class DataManager { /// Loads a [font] with its associated [fontBytes] into the Flutter engine. Future loadFont(SDKPublishFont font, Uint8List fontBytes) async { DebugLogger.instance.printInfo( - '[FLUTTER] Loading font [${font.id}](${font.fullFontName}) into Flutter framework.', - name: name); + '[FLUTTER] Loading font [${font.id}](${font.fullFontName}) into Flutter framework.', + name: name, + ); final FontLoader fontLoader = FontLoader(font.fullFontName); fontLoader.addFont(Future.value(ByteData.sublistView(fontBytes))); return fontLoader.load().then((_) { DebugLogger.instance.printInfo( - '[FLUTTER] Successfully loaded font [${font.id}](${font.fullFontName}) into Flutter framework.', - name: name); + '[FLUTTER] Successfully loaded font [${font.id}](${font.fullFontName}) into Flutter framework.', + name: name, + ); }); } @@ -1621,34 +1685,38 @@ class DataManager { /// them. Future getOrFetchFontBytesAndSave(SDKPublishFont font) async { DebugLogger.instance.printInfo( - '\t\tChecking bytes for [${font.id}](${font.fullFontName}).', - name: name); + '\t\tChecking bytes for [${font.id}](${font.fullFontName}).', + name: name, + ); final Uint8List? fontBytes = localDataRepository.fetchFontBytes( fontID: font.id, source: config.publishSource, ); if (fontBytes != null) { - DebugLogger.instance - .printInfo('\t\tFont [${font.id}] bytes already cached.', name: name); + DebugLogger.instance.printInfo( + '\t\tFont [${font.id}] bytes already cached.', + name: name, + ); return Future.value(fontBytes); } else { DebugLogger.instance.printInfo( - '\t\tFont [${font.id}] bytes not cached. Downloading...', - name: name); + '\t\tFont [${font.id}] bytes not cached. Downloading...', + name: name, + ); return downloadFontBytesAndSave(font); } } /// Downloads the bytes of a [font] and saves them to cache. Future downloadFontBytesAndSave(SDKPublishFont font) async => - networkDataRepository.downloadFontBytes(url: font.url).then( - (Uint8List? fontBytes) { - // Save the font bytes to cache. - if (fontBytes == null) return null; - saveFontBytes(font, fontBytes); - return fontBytes; - }, - ); + networkDataRepository.downloadFontBytes(url: font.url).then(( + Uint8List? fontBytes, + ) { + // Save the font bytes to cache. + if (fontBytes == null) return null; + saveFontBytes(font, fontBytes); + return fontBytes; + }); /// Gets all [SDKPublishFont] models of a given set of [fontIDs] either from /// local cache or downloads them from the network. @@ -1666,19 +1734,20 @@ class DataManager { if (auth == null) continue; try { - final SDKPublishFont? downloadedFont = - await networkDataRepository.downloadFontModel( - projectID: auth.projectId, - fontID: fontID, - source: config.publishSource, - ); + final SDKPublishFont? downloadedFont = await networkDataRepository + .downloadFontModel( + projectID: auth.projectId, + fontID: fontID, + source: config.publishSource, + ); if (downloadedFont != null) { fonts.add(downloadedFont); } } catch (e) { DebugLogger.instance.printInfo( - '\t\tFont [$fontID] could not be downloaded.', - name: name); + '\t\tFont [$fontID] could not be downloaded.', + name: name, + ); } } } @@ -1687,9 +1756,7 @@ class DataManager { /// Gets all [SDKPublishFont] models of a given set of [fontIDs] either from /// local cache or downloads them from the network. - Set> getOrFetchApis({ - required Set apiIds, - }) { + Set> getOrFetchApis({required Set apiIds}) { final AuthData? auth = authManager.authData; final Set> apis = {}; @@ -1700,12 +1767,12 @@ class DataManager { } else { if (auth == null) continue; - final Future apiFuture = - networkDataRepository.downloadApi( - projectID: auth.projectId, - apiId: apiId, - source: config.publishSource, - ); + final Future apiFuture = networkDataRepository + .downloadApi( + projectID: auth.projectId, + apiId: apiId, + source: config.publishSource, + ); apis.add(apiFuture); } } diff --git a/lib/src/data/firebase_data_repository.dart b/lib/src/data/firebase_data_repository.dart index 73584956..6c46ceef 100644 --- a/lib/src/data/firebase_data_repository.dart +++ b/lib/src/data/firebase_data_repository.dart @@ -12,18 +12,16 @@ class FirebaseDataRepository extends NetworkDataRepository { /// Creates a new [FirebaseDataRepository] instance with the given [firestore] /// instance. - FirebaseDataRepository({ - required this.firestore, - required super.config, - }); + FirebaseDataRepository({required this.firestore, required super.config}); @override Stream streamPublishModel({ required String projectID, required PublishSource source, }) { - final publishModelDoc = - firestore.collection(source.serverPath).doc(projectID); + final publishModelDoc = firestore + .collection(source.serverPath) + .doc(projectID); return publishModelDoc.snapshots().map((event) { StatTracker.instance.track( @@ -42,9 +40,10 @@ class FirebaseDataRepository extends NetworkDataRepository { ); return null; } - final SDKPublishModel model = SDKPublishModel.fromJson( - {...data, 'id': event.id}, - ); + final SDKPublishModel model = SDKPublishModel.fromJson({ + ...data, + 'id': event.id, + }); return model; }); } @@ -89,9 +88,10 @@ class FirebaseDataRepository extends NetworkDataRepository { return null; } - final SDKPublishLayout layout = SDKPublishLayout.fromJson( - {...data, 'id': value.id}, - ); + final SDKPublishLayout layout = SDKPublishLayout.fromJson({ + ...data, + 'id': value.id, + }); return layout; }); } @@ -126,9 +126,10 @@ class FirebaseDataRepository extends NetworkDataRepository { return null; } - final SDKPublishFont font = SDKPublishFont.fromJson( - {...data, 'id': value.id}, - ); + final SDKPublishFont font = SDKPublishFont.fromJson({ + ...data, + 'id': value.id, + }); return font; }); } @@ -163,9 +164,7 @@ class FirebaseDataRepository extends NetworkDataRepository { return null; } - final HttpApiData api = HttpApiData.fromJson( - {...data, 'id': value.id}, - ); + final HttpApiData api = HttpApiData.fromJson({...data, 'id': value.id}); return api; }); } @@ -201,9 +200,10 @@ class FirebaseDataRepository extends NetworkDataRepository { return null; } - final SDKLayoutVariables layoutVariables = SDKLayoutVariables.fromJson( - {...data, 'id': value.id}, - ); + final SDKLayoutVariables layoutVariables = SDKLayoutVariables.fromJson({ + ...data, + 'id': value.id, + }); return layoutVariables; }); } diff --git a/lib/src/data/local_data_repository.dart b/lib/src/data/local_data_repository.dart index 43e7320f..9d042f55 100644 --- a/lib/src/data/local_data_repository.dart +++ b/lib/src/data/local_data_repository.dart @@ -17,9 +17,7 @@ class LocalDataRepository { LocalDataRepository({required this.cacheManager}); /// Returns the [SDKPublishModel] from the cache. - SDKPublishModel? fetchPublishModel({ - required PublishSource source, - }) { + SDKPublishModel? fetchPublishModel({required PublishSource source}) { try { return cacheManager.get( source.modelCacheKey, @@ -46,60 +44,46 @@ class LocalDataRepository { Future savePublishModel({ required SDKPublishModel model, required PublishSource source, - }) => - cacheManager.store( - source.modelCacheKey, - model.toFullJson(), - ); + }) => cacheManager.store(source.modelCacheKey, model.toFullJson()); /// Stores the given [bytes] of a [fontID] in the cache. Future saveFontBytes({ required String fontID, required Uint8List bytes, required PublishSource source, - }) => - cacheManager - .storeBytes( - source.fontsCacheKey, - fontID, - bytes, - ) - .catchError((e, str) { - debugPrintStack(label: '$e', stackTrace: str); - }); + }) => cacheManager.storeBytes(source.fontsCacheKey, fontID, bytes).catchError( + (e, str) { + debugPrintStack(label: '$e', stackTrace: str); + }, + ); /// Deletes a given layout associated with a [layoutID] from the cache. void deletePublishLayout({ required String layoutID, required PublishSource source, - }) => - cacheManager.delete(source.modelCacheKey); + }) => cacheManager.delete(source.modelCacheKey); /// Deletes a given api associated with a [apiId] from the cache. void deletePublishApi({ required String apiId, required PublishSource source, - }) => - cacheManager.delete(source.apisCacheKey); + }) => cacheManager.delete(source.apisCacheKey); /// Deletes a given api associated with a [apiId] from the cache. void deletePublishVariables({ required String layoutID, required PublishSource source, - }) => - cacheManager.delete(source.variablesCacheKey); + }) => cacheManager.delete(source.variablesCacheKey); /// Deletes a given api associated with a [apiId] from the cache. void deletePublishConditions({ required String layoutID, required PublishSource source, - }) => - cacheManager.delete(source.conditionsCacheKey); + }) => cacheManager.delete(source.conditionsCacheKey); /// Deletes the stored bytes of a given [fontID] from the cache. void deleteFontBytes({ required String fontID, required PublishSource source, - }) => - cacheManager.deleteBytes(source.fontsCacheKey, fontID); + }) => cacheManager.deleteBytes(source.fontsCacheKey, fontID); } diff --git a/lib/src/data/local_database.dart b/lib/src/data/local_database.dart index 290053eb..8a184158 100644 --- a/lib/src/data/local_database.dart +++ b/lib/src/data/local_database.dart @@ -153,14 +153,16 @@ class HiveLocalDatabase extends LocalDatabase { // Close the box itself. ChangeNotifier.dispose is synchronous, so report // close failures instead of leaving them unhandled. - unawaited(_box.close().catchError((Object e, StackTrace stackTrace) { - ErrorLogger.instance.captureException( - e, - message: 'Failed to close local database', - type: 'local_database_close_failed', - stackTrace: stackTrace, - ); - })); + unawaited( + _box.close().catchError((Object e, StackTrace stackTrace) { + ErrorLogger.instance.captureException( + e, + message: 'Failed to close local database', + type: 'local_database_close_failed', + stackTrace: stackTrace, + ); + }), + ); super.dispose(); } @@ -214,9 +216,9 @@ class SharedPreferencesLocalDatabase extends LocalDatabase { .getKeys() .where((key) => key.startsWith(_prefix)) .fold>({}, (map, key) { - map[key.substring(_prefix.length)] = _prefsInstance!.get(key); - return map; - }); + map[key.substring(_prefix.length)] = _prefsInstance!.get(key); + return map; + }); } @override @@ -240,7 +242,7 @@ class SharedPreferencesLocalDatabase extends LocalDatabase { } else { throw ArgumentError('Unsupported value type: ${value.runtimeType}'); } - + // Notify listeners after value changes notifyListeners(); _notifiers[key]?.notify(); @@ -273,9 +275,9 @@ class SharedPreferencesLocalDatabase extends LocalDatabase { @override Listenable getNotifier(String? key) { if (key == null) return this; - + if (_notifiers.containsKey(key)) return _notifiers[key]!; - + final notifier = _StorageListenable._(key); _notifiers[key] = notifier; return notifier; diff --git a/lib/src/data/network_data_repository.dart b/lib/src/data/network_data_repository.dart index e17ca3a5..c2ea86bf 100644 --- a/lib/src/data/network_data_repository.dart +++ b/lib/src/data/network_data_repository.dart @@ -18,9 +18,7 @@ abstract class NetworkDataRepository { final CodelesslyConfig config; /// Creates a new instance of [NetworkDataRepository]. - NetworkDataRepository({ - required this.config, - }); + NetworkDataRepository({required this.config}); /// Calls a cloud function that searches for the project associated with the /// given unique slug and returns a completely populated [SDKPublishModel] @@ -42,10 +40,14 @@ abstract class NetworkDataRepository { final http.Response result = await http.get(Uri.parse(url)); if (result.statusCode != 200) { - DebugLogger.instance - .printInfo('Error downloading publish bundle.', name: name); - DebugLogger.instance - .printInfo('Status code: ${result.statusCode}', name: name); + DebugLogger.instance.printInfo( + 'Error downloading publish bundle.', + name: name, + ); + DebugLogger.instance.printInfo( + 'Status code: ${result.statusCode}', + name: name, + ); DebugLogger.instance.printInfo('Message: ${result.body}', name: name); ErrorLogger.instance.captureException( @@ -59,14 +61,16 @@ abstract class NetworkDataRepository { StatTracker.instance.track(StatType.bundleDownload); - final Map modelDoc = - jsonDecode(utf8.decode(result.bodyBytes)); + final Map modelDoc = jsonDecode( + utf8.decode(result.bodyBytes), + ); final SDKPublishModel model = SDKPublishModel.fromJson(modelDoc); DebugLogger.instance.printInfo( - 'Finished downloading publish bundle with slug: $slug and source: $source.', - name: name); + 'Finished downloading publish bundle with slug: $slug and source: $source.', + name: name, + ); return model; } catch (e, str) { DebugLogger.instance.log( @@ -139,15 +143,10 @@ abstract class NetworkDataRepository { required String projectID, required Set fontIDs, required PublishSource source, - }) => - { - for (final fontID in fontIDs) - downloadFontModel( - projectID: projectID, - fontID: fontID, - source: source, - ) - }; + }) => { + for (final fontID in fontIDs) + downloadFontModel(projectID: projectID, fontID: fontID, source: source), + }; /// Fetches the relevant [SDKPublishLayout]s from the server based on the /// configurations of the implementation. @@ -155,20 +154,21 @@ abstract class NetworkDataRepository { required String projectID, required Set layoutIDs, required PublishSource source, - }) => - { - for (final layoutID in layoutIDs) - downloadLayoutModel( - projectID: projectID, - layoutID: layoutID, - source: source, - ) - }; + }) => { + for (final layoutID in layoutIDs) + downloadLayoutModel( + projectID: projectID, + layoutID: layoutID, + source: source, + ), + }; /// Downloads the bytes of a font from the server given a [url]. Future downloadFontBytes({required String url}) async { - DebugLogger.instance - .printFunction('downloadFontBytes(url: $url)', name: name); + DebugLogger.instance.printFunction( + 'downloadFontBytes(url: $url)', + name: name, + ); try { final http.Response response = await http.get(Uri.parse(url)); if (response.statusCode != 200) return null; diff --git a/lib/src/data/web_data_repository.dart b/lib/src/data/web_data_repository.dart index 43d11e52..4610db7d 100644 --- a/lib/src/data/web_data_repository.dart +++ b/lib/src/data/web_data_repository.dart @@ -12,10 +12,7 @@ class WebDataRepository extends NetworkDataRepository { final http.Client client; /// Creates a [WebDataRepository] instance. - WebDataRepository({ - required super.config, - required this.client, - }); + WebDataRepository({required super.config, required this.client}); @override Stream streamPublishModel({ @@ -25,12 +22,10 @@ class WebDataRepository extends NetworkDataRepository { try { final http.Response result = await client.post( Uri.parse( - '${config.firebaseCloudFunctionsBaseURL}/api/getPublishModelRequest'), + '${config.firebaseCloudFunctionsBaseURL}/api/getPublishModelRequest', + ), headers: {'Content-Type': 'application/json'}, - body: jsonEncode({ - 'projectID': projectID, - 'source': source.serverPath, - }), + body: jsonEncode({'projectID': projectID, 'source': source.serverPath}), ); if (result.statusCode != 200) { @@ -67,7 +62,8 @@ class WebDataRepository extends NetworkDataRepository { try { final http.Response result = await client.post( Uri.parse( - '${config.firebaseCloudFunctionsBaseURL}/api/getLayoutModelRequest'), + '${config.firebaseCloudFunctionsBaseURL}/api/getLayoutModelRequest', + ), headers: {'Content-Type': 'application/json'}, body: jsonEncode({ 'projectID': projectID, @@ -113,7 +109,8 @@ class WebDataRepository extends NetworkDataRepository { try { final http.Response result = await client.post( Uri.parse( - '${config.firebaseCloudFunctionsBaseURL}/api/getFontModelRequest'), + '${config.firebaseCloudFunctionsBaseURL}/api/getFontModelRequest', + ), headers: {'Content-Type': 'application/json'}, body: jsonEncode({ 'projectID': projectID, @@ -156,7 +153,8 @@ class WebDataRepository extends NetworkDataRepository { try { final http.Response result = await client.post( Uri.parse( - '${config.firebaseCloudFunctionsBaseURL}/api/getPublishedApiRequest'), + '${config.firebaseCloudFunctionsBaseURL}/api/getPublishedApiRequest', + ), headers: {'Content-Type': 'application/json'}, body: jsonEncode({ 'projectID': projectID, @@ -199,7 +197,8 @@ class WebDataRepository extends NetworkDataRepository { try { final http.Response result = await client.post( Uri.parse( - '${config.firebaseCloudFunctionsBaseURL}/api/getPublishedLayoutVariablesRequest'), + '${config.firebaseCloudFunctionsBaseURL}/api/getPublishedLayoutVariablesRequest', + ), headers: {'Content-Type': 'application/json'}, body: jsonEncode({ 'projectID': projectID, @@ -220,8 +219,10 @@ class WebDataRepository extends NetworkDataRepository { } final Map modelDoc = jsonDecode(result.body); - final SDKLayoutVariables variables = - SDKLayoutVariables.fromJson({...modelDoc, 'id': layoutID}); + final SDKLayoutVariables variables = SDKLayoutVariables.fromJson({ + ...modelDoc, + 'id': layoutID, + }); return variables; } catch (e, str) { @@ -245,7 +246,8 @@ class WebDataRepository extends NetworkDataRepository { try { final http.Response result = await client.post( Uri.parse( - '${config.firebaseCloudFunctionsBaseURL}/api/getPublishedLayoutConditionsRequest'), + '${config.firebaseCloudFunctionsBaseURL}/api/getPublishedLayoutConditionsRequest', + ), headers: {'Content-Type': 'application/json'}, body: jsonEncode({ 'projectID': projectID, @@ -266,8 +268,10 @@ class WebDataRepository extends NetworkDataRepository { } final Map modelDoc = jsonDecode(result.body); - final SDKLayoutConditions conditions = - SDKLayoutConditions.fromJson({...modelDoc, 'id': layoutID}); + final SDKLayoutConditions conditions = SDKLayoutConditions.fromJson({ + ...modelDoc, + 'id': layoutID, + }); return conditions; } catch (e, str) { diff --git a/lib/src/firebase_options_prod.dart b/lib/src/firebase_options_prod.dart index 3bd53948..e65dbc55 100644 --- a/lib/src/firebase_options_prod.dart +++ b/lib/src/firebase_options_prod.dart @@ -56,8 +56,7 @@ class DefaultFirebaseOptionsProd { projectId: 'codeless-app', databaseURL: 'https://codeless-app-default-rtdb.firebaseio.com', storageBucket: 'codeless-app.appspot.com', - iosClientId: - '1008464357540-quq84tmv8e0b7qo3jo2r3148pd9snfni.apps.googleusercontent.com', + iosClientId: '1008464357540-quq84tmv8e0b7qo3jo2r3148pd9snfni.apps.googleusercontent.com', iosBundleId: 'com.codelessly', ); @@ -68,8 +67,7 @@ class DefaultFirebaseOptionsProd { projectId: 'codeless-app', databaseURL: 'https://codeless-app-default-rtdb.firebaseio.com', storageBucket: 'codeless-app.appspot.com', - iosClientId: - '1008464357540-quq84tmv8e0b7qo3jo2r3148pd9snfni.apps.googleusercontent.com', + iosClientId: '1008464357540-quq84tmv8e0b7qo3jo2r3148pd9snfni.apps.googleusercontent.com', iosBundleId: 'com.codelessly', ); diff --git a/lib/src/functions/functions_repository.dart b/lib/src/functions/functions_repository.dart index 440c0c93..98d99936 100644 --- a/lib/src/functions/functions_repository.dart +++ b/lib/src/functions/functions_repository.dart @@ -95,8 +95,11 @@ class FunctionsRepository { setVariant(context, action as SetVariantAction, notify: notify); return true; case ActionType.setVariable: - return setVariableFromAction(context, action as SetVariableAction, - notify: notify); + return setVariableFromAction( + context, + action as SetVariableAction, + notify: notify, + ); case ActionType.callFunction: final result = callFunction(context, action as CallFunctionAction); // Blocking actions wait for the app's function to finish. @@ -108,10 +111,14 @@ class FunctionsRepository { return setStorageFromAction(context, action as SetStorageAction); case ActionType.setCloudStorage: return setCloudDatabaseFromAction( - context, action as SetCloudStorageAction); + context, + action as SetCloudStorageAction, + ); case ActionType.loadFromCloudStorage: return loadFromStorageAction( - context, action as LoadFromCloudStorageAction); + context, + action as LoadFromCloudStorageAction, + ); } } @@ -120,8 +127,11 @@ class FunctionsRepository { BuildContext context, [ Observable? variable, ]) { - final Map apis = - context.read().dataManager.publishModel!.apis; + final Map apis = context + .read() + .dataManager + .publishModel! + .apis; final HttpApiData? apiData = apis[action.apiId]; @@ -155,8 +165,9 @@ class FunctionsRepository { } // fetch default value from api data. final param = apiData.variables.findByNameOrNull(name); - final paramValue = - param != null && param.value.isNotEmpty ? param.value : ''; + final paramValue = param != null && param.value.isNotEmpty + ? param.value + : ''; params[name] = paramValue; } @@ -174,15 +185,21 @@ class FunctionsRepository { ); } - static Map _generateMapFromPairs(List pairs, - Map parameters, ScopedValues scopedValues) { + static Map _generateMapFromPairs( + List pairs, + Map parameters, + ScopedValues scopedValues, + ) { return pairs .where((pair) => pair.isUsed && pair.key.isNotEmpty) .toList() .asMap() - .map((key, pair) => MapEntry( + .map( + (key, pair) => MapEntry( _applyApiInputs(pair.key, parameters, scopedValues), - _applyApiInputs(pair.value, parameters, scopedValues))); + _applyApiInputs(pair.value, parameters, scopedValues), + ), + ); } static String _applyApiInputs( @@ -245,8 +262,10 @@ class FunctionsRepository { }) async { final ScopedValues effectiveScopedValues = scopedValues ?? ScopedValues.of(context); - final parsedParams = - substituteVariablesInMap(action.params, effectiveScopedValues); + final parsedParams = substituteVariablesInMap( + action.params, + effectiveScopedValues, + ); DebugLogger.instance.printInfo( 'Performing navigation action with params: $parsedParams', @@ -262,11 +281,16 @@ class FunctionsRepository { await Navigator.maybePop(context, parsedParams); } else { // Check if a layout exists for the action's [destinationId]. - final String? layoutId = effectiveCodelessly - .dataManager.publishModel?.layouts[action.destinationId]?.id ?? + final String? layoutId = + effectiveCodelessly + .dataManager + .publishModel + ?.layouts[action.destinationId] + ?.id ?? effectiveCodelessly.dataManager.publishModel?.layouts.values .firstWhereOrNull( - (layout) => layout.canvasIds.contains(action.destinationId)) + (layout) => layout.canvasIds.contains(action.destinationId), + ) ?.id; DebugLogger.instance.printInfo( @@ -304,9 +328,8 @@ class FunctionsRepository { context, MaterialPageRoute( settings: RouteSettings(arguments: parsedParams), - builder: (context) => CodelesslyWidget( - controller: effectiveController, - ), + builder: (context) => + CodelesslyWidget(controller: effectiveController), ), ); // Notify navigation listeners that this screen has been navigated back @@ -324,9 +347,8 @@ class FunctionsRepository { context, MaterialPageRoute( settings: RouteSettings(arguments: parsedParams), - builder: (context) => CodelesslyWidget( - controller: effectiveController, - ), + builder: (context) => + CodelesslyWidget(controller: effectiveController), ), ); @@ -363,7 +385,8 @@ class FunctionsRepository { // Check if a layout exists for the action's [destinationId]. final String? layoutId = codelessly.dataManager.publishModel?.layouts.values .firstWhereOrNull( - (layout) => layout.canvasIds.contains(action.destinationId)) + (layout) => layout.canvasIds.contains(action.destinationId), + ) ?.id; DebugLogger.instance.printInfo( @@ -396,10 +419,8 @@ class FunctionsRepository { routeSettings: RouteSettings(arguments: parsedParams), builder: (context) => CodelesslyDialogWidget( showCloseButton: action.showCloseButton, - builder: (context) => CodelesslyWidget( - codelessly: codelessly, - layoutID: layoutId, - ), + builder: (context) => + CodelesslyWidget(codelessly: codelessly, layoutID: layoutId), ), ); if (context.mounted) { @@ -431,20 +452,17 @@ class FunctionsRepository { ); return; } - DebugLogger.instance.printInfo( - 'Launching URL: $uri', - name: name, - ); + DebugLogger.instance.printInfo('Launching URL: $uri', name: name); unawaited( launchUrl(uri).then( (_) {}, onError: (Object e, StackTrace stackTrace) => ErrorLogger.instance.captureException( - e, - message: 'Failed to launch $url', - type: 'launch_url_failed', - stackTrace: stackTrace, - ), + e, + message: 'Failed to launch $url', + type: 'launch_url_failed', + stackTrace: stackTrace, + ), ), ); } @@ -458,8 +476,10 @@ class FunctionsRepository { bool useCloudFunctionForWeb = false, Observable? variable, }) async { - assert(variable == null || variable.value.type.isMap, - 'Provided variable for api call must be of type map. Found ${variable.value.type}'); + assert( + variable == null || variable.value.type.isMap, + 'Provided variable for api call must be of type map. Found ${variable.value.type}', + ); printApiDetails(method: method, url: url, headers: headers, body: body); @@ -511,11 +531,17 @@ class FunctionsRepository { final Uri uri = Uri.parse(url); response = switch (method) { HttpMethod.get => await client.get(uri, headers: headers), - HttpMethod.post => - await client.post(uri, headers: headers, body: body), - HttpMethod.delete => - await client.delete(uri, headers: headers, body: body), - HttpMethod.put => await client.put(uri, headers: headers, body: body) + HttpMethod.post => await client.post( + uri, + headers: headers, + body: body, + ), + HttpMethod.delete => await client.delete( + uri, + headers: headers, + body: body, + ), + HttpMethod.put => await client.put(uri, headers: headers, body: body), }; } @@ -538,21 +564,11 @@ class FunctionsRepository { return response; } catch (error, stackTrace) { - DebugLogger.instance.printInfo( - error.toString(), - name: name, - ); - DebugLogger.instance.printInfo( - stackTrace.toString(), - name: name, - ); + DebugLogger.instance.printInfo(error.toString(), name: name); + DebugLogger.instance.printInfo(stackTrace.toString(), name: name); if (variable != null) { variable.value = variable.value.copyWith( - value: ApiResponseVariableUtils.error( - url, - error, - data: existingData, - ), + value: ApiResponseVariableUtils.error(url, error, data: existingData), ); DebugLogger.instance.printInfo( '${variable.value.name} updated with error state.', @@ -579,18 +595,12 @@ class FunctionsRepository { '--------------------------------------------------------------------', name: name, ); - DebugLogger.instance.printInfo( - 'API Request:', - name: name, - ); + DebugLogger.instance.printInfo('API Request:', name: name); DebugLogger.instance.printInfo( '--------------------------------------------------------------------', name: name, ); - DebugLogger.instance.printInfo( - '${method.shortName} $url', - name: name, - ); + DebugLogger.instance.printInfo('${method.shortName} $url', name: name); DebugLogger.instance.printInfo( 'Headers: ${headers.isEmpty ? 'None' : ''}', name: name, @@ -600,10 +610,7 @@ class FunctionsRepository { const JsonEncoder.withIndent(' ').convert(headers), name: name, ); - DebugLogger.instance.printInfo( - '', - name: name, - ); + DebugLogger.instance.printInfo('', name: name); } DebugLogger.instance.printInfo( 'Body: ${body == null || body.toString().trim().isEmpty ? 'None' : ''}', @@ -617,10 +624,7 @@ class FunctionsRepository { name: name, ); } catch (e) { - DebugLogger.instance.printInfo( - body.toString(), - name: name, - ); + DebugLogger.instance.printInfo(body.toString(), name: name); } } DebugLogger.instance.printInfo( @@ -667,7 +671,9 @@ ${response.body.contains('{') ? const JsonEncoder.withIndent(' ').convert(json. } static Future submitToNewsletter( - BuildContext context, SubmitAction action) async { + BuildContext context, + SubmitAction action, + ) async { final CodelesslyContext payload = context.read(); // Get primary linked text field ID. final String primaryTextField = action.primaryTextField; @@ -675,7 +681,8 @@ ${response.body.contains('{') ? const JsonEncoder.withIndent(' ').convert(json. final List primaryFieldValues = payload.nodeValues[primaryTextField]?.value ?? []; // Email ID would be primary field's input value. - final String emailID = primaryFieldValues + final String emailID = + primaryFieldValues .firstWhereOrNull((value) => value.name == 'inputValue') ?.value .typedValue() ?? @@ -704,12 +711,14 @@ ${response.body.contains('{') ? const JsonEncoder.withIndent(' ').convert(json. final List lastNameFieldValues = payload.nodeValues[lastNameField]?.value ?? []; // Get first and last names. - final String firstName = firstNameFieldValues + final String firstName = + firstNameFieldValues .firstWhereOrNull((value) => value.name == 'inputValue') ?.value .typedValue() ?? ''; - final String lastName = lastNameFieldValues + final String lastName = + lastNameFieldValues .firstWhereOrNull((value) => value.name == 'inputValue') ?.value .typedValue() ?? @@ -727,15 +736,15 @@ ${response.body.contains('{') ? const JsonEncoder.withIndent(' ').convert(json. 'email_address': emailID, 'status': 'subscribed', 'merge_fields': mergeFields, - } - ] + }, + ], }; // Mailchimp's endpoint to submit data. final String url = 'https://${action.dataCenter}.api.mailchimp.com/3.0/lists/${action.listID}'; // Headers to authenticate request. final Map headers = { - 'Authorization': 'auth ${action.apiKey}' + 'Authorization': 'auth ${action.apiKey}', }; // Submit data to Mailchimp. return makeApiRequest( @@ -788,8 +797,8 @@ ${response.body.contains('{') ? const JsonEncoder.withIndent(' ').convert(json. V Function()? syncValue, bool notify = true, }) { - final CodelesslyContext codelesslyContext = - context.read(); + final CodelesslyContext codelesslyContext = context + .read(); final List values = codelesslyContext.nodeValues[action.nodeID]!.value; // Get new value. @@ -807,15 +816,18 @@ ${response.body.contains('{') ? const JsonEncoder.withIndent(' ').convert(json. } if (newValue != null) { // Get old value by name. - final ValueModel oldValue = - values.firstWhere((value) => value.name == newValue!.name); + final ValueModel oldValue = values.firstWhere( + (value) => value.name == newValue!.name, + ); // Replace old value with new. final List updateValues = [...values] ..remove(oldValue) ..add(newValue); // Update values list. - codelesslyContext.nodeValues[action.nodeID]! - .set(updateValues, notify: notify); + codelesslyContext.nodeValues[action.nodeID]!.set( + updateValues, + notify: notify, + ); } } @@ -828,11 +840,14 @@ ${response.body.contains('{') ? const JsonEncoder.withIndent(' ').convert(json. // Get node's values. final List values = payload.nodeValues[action.nodeID]!.value; // Get new variant value. - final ValueModel newValue = - StringValue(name: 'currentVariantId', value: action.variantID); + final ValueModel newValue = StringValue( + name: 'currentVariantId', + value: action.variantID, + ); // Get old value by name. - final ValueModel oldValue = - values.firstWhere((value) => value.name == newValue.name); + final ValueModel oldValue = values.firstWhere( + (value) => value.name == newValue.name, + ); // Replace old value with new. final List updateValues = [...values] ..remove(oldValue) @@ -850,10 +865,11 @@ ${response.body.contains('{') ? const JsonEncoder.withIndent(' ').convert(json. bool notify = true, }) { final ScopedValues scopedValues = ScopedValues.of(context); - final CodelesslyContext codelesslyContext = - context.read(); - final variableNotifier = - codelesslyContext.findVariableByName(action.variable.name); + final CodelesslyContext codelesslyContext = context + .read(); + final variableNotifier = codelesslyContext.findVariableByName( + action.variable.name, + ); if (variableNotifier == null) return false; String newValue = PropertyValueDelegate.substituteVariables( @@ -868,29 +884,39 @@ ${response.body.contains('{') ? const JsonEncoder.withIndent(' ').convert(json. VariableType.text => newValue, VariableType.image => newValue, VariableType.color => ColorRGBA.fromHex(newValue), - VariableType.integer => _performIntOperation(action.numberOperation, - currentValue?.toInt(), newValue, scopedValues), - VariableType.decimal => _performDecimalOperation(action.numberOperation, - currentValue?.toDouble(), newValue, scopedValues), - VariableType.boolean => action.toggled - ? !(bool.tryParse(currentValue.toString()) ?? false) - : bool.tryParse(newValue), + VariableType.integer => _performIntOperation( + action.numberOperation, + currentValue?.toInt(), + newValue, + scopedValues, + ), + VariableType.decimal => _performDecimalOperation( + action.numberOperation, + currentValue?.toDouble(), + newValue, + scopedValues, + ), + VariableType.boolean => + action.toggled + ? !(bool.tryParse(currentValue.toString()) ?? false) + : bool.tryParse(newValue), VariableType.list => _performListOperation( - action, - currentValue?.toList(), - newValue, - scopedValues, - ), + action, + currentValue?.toList(), + newValue, + scopedValues, + ), VariableType.map => _performMapOperation( - action, - currentValue?.toMap(), - newValue, - scopedValues, - ), + action, + currentValue?.toMap(), + newValue, + scopedValues, + ), }; - final VariableData updatedVariable = - variableNotifier.value.copyWith(value: updatedValue); + final VariableData updatedVariable = variableNotifier.value.copyWith( + value: updatedValue, + ); variableNotifier.set(updatedVariable, notify: notify); return true; @@ -933,8 +959,8 @@ ${response.body.contains('{') ? const JsonEncoder.withIndent(' ').convert(json. if (match == null) return false; - final ValueNotifier? propertyNotifier = - payload.findVariableByName(match.name); + final ValueNotifier? propertyNotifier = payload + .findVariableByName(match.name); if (propertyNotifier == null) return false; @@ -966,8 +992,9 @@ ${response.body.contains('{') ? const JsonEncoder.withIndent(' ').convert(json. if (payload.nodeValues[node.id] == null) return false; final List values = payload.nodeValues[node.id]?.value ?? []; - final ValueModel? valueModel = - values.firstWhereOrNull((val) => val.name == property); + final ValueModel? valueModel = values.firstWhereOrNull( + (val) => val.name == property, + ); if (valueModel == null) return false; @@ -997,12 +1024,7 @@ ${response.body.contains('{') ? const JsonEncoder.withIndent(' ').convert(json. property: property, value: value.toString(), ) || - setNodeValue( - context, - node: node, - property: property, - value: value, - ); + setNodeValue(context, node: node, property: property, value: value); } /// Triggers actions on given [node] or [reactions] with given [type]. @@ -1016,8 +1038,9 @@ ${response.body.contains('{') ? const JsonEncoder.withIndent(' ').convert(json. dynamic value, List? reactions, }) async { - final filteredReactions = (reactions ?? node?.reactions ?? []) - .where((reaction) => reaction.trigger.type == type); + final filteredReactions = (reactions ?? node?.reactions ?? []).where( + (reaction) => reaction.trigger.type == type, + ); if (filteredReactions.isEmpty) return false; @@ -1084,14 +1107,16 @@ ${response.body.contains('{') ? const JsonEncoder.withIndent(' ').convert(json. CallFunctionAction action, ) { final ScopedValues scopedValues = ScopedValues.of(context); - final CodelesslyContext codelesslyContext = - context.read(); + final CodelesslyContext codelesslyContext = context + .read(); final CodelesslyFunction? function = codelesslyContext.functions[action.name]; // Substitute variables in params. - final Map parsedParams = - substituteVariablesInMap(action.params, scopedValues); + final Map parsedParams = substituteVariablesInMap( + action.params, + scopedValues, + ); DebugLogger.instance.printInfo( 'Calling function ${action.name}(${parsedParams.entries.map((e) => '${e.key}: ${e.value}').join(', ')}).', @@ -1124,23 +1149,14 @@ ${response.body.contains('{') ? const JsonEncoder.withIndent(' ').convert(json. try { final LocalDatabase? storage = scopedValues.localStorage; if (storage == null) { - DebugLogger.instance.printInfo( - 'Storage is null.', - name: name, - ); + DebugLogger.instance.printInfo('Storage is null.', name: name); return false; } await storage.clear(); return true; } catch (error, stackTrace) { - DebugLogger.instance.printInfo( - error.toString(), - name: name, - ); - DebugLogger.instance.printInfo( - stackTrace.toString(), - name: name, - ); + DebugLogger.instance.printInfo(error.toString(), name: name); + DebugLogger.instance.printInfo(stackTrace.toString(), name: name); return false; } } @@ -1153,10 +1169,7 @@ ${response.body.contains('{') ? const JsonEncoder.withIndent(' ').convert(json. final LocalDatabase? storage = scopedValues.localStorage; if (storage == null) { - DebugLogger.instance.printInfo( - 'Storage is null.', - name: name, - ); + DebugLogger.instance.printInfo('Storage is null.', name: name); return false; } @@ -1202,25 +1215,34 @@ ${response.body.contains('{') ? const JsonEncoder.withIndent(' ').convert(json. VariableType.text => newValue, VariableType.image => newValue, VariableType.color => ColorRGBA.fromHex(newValue), - VariableType.integer => _performIntOperation(action.numberOperation, - currentValue?.toInt(), newValue, scopedValues), - VariableType.decimal => _performDecimalOperation(action.numberOperation, - currentValue?.toDouble(), newValue, scopedValues), - VariableType.boolean => action.toggled - ? !(bool.tryParse(currentValue.toString()) ?? false) - : bool.tryParse(newValue), + VariableType.integer => _performIntOperation( + action.numberOperation, + currentValue?.toInt(), + newValue, + scopedValues, + ), + VariableType.decimal => _performDecimalOperation( + action.numberOperation, + currentValue?.toDouble(), + newValue, + scopedValues, + ), + VariableType.boolean => + action.toggled + ? !(bool.tryParse(currentValue.toString()) ?? false) + : bool.tryParse(newValue), VariableType.list => _performListOperation( - action, - currentValue?.toList(), - newValue, - scopedValues, - ), + action, + currentValue?.toList(), + newValue, + scopedValues, + ), VariableType.map => _performMapOperation( - action, - currentValue?.toMap(), - newValue, - scopedValues, - ), + action, + currentValue?.toMap(), + newValue, + scopedValues, + ), }; if (match == null || pointer == null) { @@ -1238,10 +1260,10 @@ ${response.body.contains('{') ? const JsonEncoder.withIndent(' ').convert(json. final defaultValue = action.variableType.isList ? [] : action.variableType.isMap - ? {} - : null; + ? {} + : null; Map storageData = { - match.name: storage.get(match.name, defaultValue: defaultValue) + match.name: storage.get(match.name, defaultValue: defaultValue), }; final result = pointer.write(storageData, value); @@ -1257,14 +1279,8 @@ ${response.body.contains('{') ? const JsonEncoder.withIndent(' ').convert(json. return true; } catch (error, stackTrace) { - DebugLogger.instance.printInfo( - error.toString(), - name: name, - ); - DebugLogger.instance.printInfo( - stackTrace.toString(), - name: name, - ); + DebugLogger.instance.printInfo(error.toString(), name: name); + DebugLogger.instance.printInfo(stackTrace.toString(), name: name); return false; } } @@ -1272,14 +1288,13 @@ ${response.body.contains('{') ? const JsonEncoder.withIndent(' ').convert(json. /// Performs remove operation on given storage [action] and returns `true` if /// the operation was successful, `false` otherwise. static Future _removeFromStorage( - SetStorageAction action, ScopedValues scopedValues) async { + SetStorageAction action, + ScopedValues scopedValues, + ) async { try { final LocalDatabase? storage = scopedValues.localStorage; if (storage == null) { - DebugLogger.instance.printInfo( - 'Storage is null.', - name: name, - ); + DebugLogger.instance.printInfo('Storage is null.', name: name); return false; } @@ -1321,14 +1336,8 @@ ${response.body.contains('{') ? const JsonEncoder.withIndent(' ').convert(json. return true; } catch (error, stackTrace) { - DebugLogger.instance.printInfo( - error.toString(), - name: name, - ); - DebugLogger.instance.printInfo( - stackTrace.toString(), - name: name, - ); + DebugLogger.instance.printInfo(error.toString(), name: name); + DebugLogger.instance.printInfo(stackTrace.toString(), name: name); return false; } } @@ -1425,10 +1434,14 @@ ${response.body.contains('{') ? const JsonEncoder.withIndent(' ').convert(json. ) async { final ScopedValues scopedValues = ScopedValues.of(context); return await switch (action.subAction) { - AddDocumentSubAction addDocumentSubAction => - addDocumentToCloud(addDocumentSubAction, scopedValues), - UpdateDocumentSubAction updateDocumentSubAction => - updateDocumentOnCloud(updateDocumentSubAction, scopedValues), + AddDocumentSubAction addDocumentSubAction => addDocumentToCloud( + addDocumentSubAction, + scopedValues, + ), + UpdateDocumentSubAction updateDocumentSubAction => updateDocumentOnCloud( + updateDocumentSubAction, + scopedValues, + ), RemoveDocumentSubAction removeDocumentSubAction => removeDocumentFromCloud(removeDocumentSubAction, scopedValues), }; @@ -1442,10 +1455,7 @@ ${response.body.contains('{') ? const JsonEncoder.withIndent(' ').convert(json. final CloudDatabase? cloudDatabase = scopedValues.cloudDatabase; if (cloudDatabase == null) { - DebugLogger.instance.printInfo( - 'Cloud storage is null.', - name: name, - ); + DebugLogger.instance.printInfo('Cloud storage is null.', name: name); return false; } @@ -1499,14 +1509,8 @@ ${response.body.contains('{') ? const JsonEncoder.withIndent(' ').convert(json. skipCreationIfDocumentExists: subAction.skipCreationIfDocumentExists, ); } catch (error, stackTrace) { - DebugLogger.instance.printInfo( - error.toString(), - name: name, - ); - DebugLogger.instance.printInfo( - stackTrace.toString(), - name: name, - ); + DebugLogger.instance.printInfo(error.toString(), name: name); + DebugLogger.instance.printInfo(stackTrace.toString(), name: name); return false; } } @@ -1518,10 +1522,7 @@ ${response.body.contains('{') ? const JsonEncoder.withIndent(' ').convert(json. final CloudDatabase? cloudDatabase = scopedValues.cloudDatabase; if (cloudDatabase == null) { - DebugLogger.instance.printInfo( - 'Cloud storage is null.', - name: name, - ); + DebugLogger.instance.printInfo('Cloud storage is null.', name: name); return false; } @@ -1568,7 +1569,9 @@ ${response.body.contains('{') ? const JsonEncoder.withIndent(' ').convert(json. final match = VariableMatch.parse(storageKey.wrapWithVariableSyntax()); final docData = await cloudDatabase.getDocumentData( - subAction.path, evaluatedDocumentId); + subAction.path, + evaluatedDocumentId, + ); final Object? currentValue; JsonPointer? pointer; @@ -1592,28 +1595,34 @@ ${response.body.contains('{') ? const JsonEncoder.withIndent(' ').convert(json. VariableType.text => newValue, VariableType.image => newValue, VariableType.color => ColorRGBA.fromHex(newValue), - VariableType.integer => _performIntOperation(subAction.numberOperation, - currentValue?.toInt(), newValue, scopedValues), + VariableType.integer => _performIntOperation( + subAction.numberOperation, + currentValue?.toInt(), + newValue, + scopedValues, + ), VariableType.decimal => _performDecimalOperation( - subAction.numberOperation, - currentValue?.toDouble(), - newValue, - scopedValues), - VariableType.boolean => subAction.toggled - ? !(bool.tryParse(currentValue.toString()) ?? false) - : bool.tryParse(newValue), + subAction.numberOperation, + currentValue?.toDouble(), + newValue, + scopedValues, + ), + VariableType.boolean => + subAction.toggled + ? !(bool.tryParse(currentValue.toString()) ?? false) + : bool.tryParse(newValue), VariableType.list => _performListOperation( - subAction, - currentValue?.toList(), - newValue, - scopedValues, - ), + subAction, + currentValue?.toList(), + newValue, + scopedValues, + ), VariableType.map => _performMapOperation( - subAction, - currentValue?.toMap(), - newValue, - scopedValues, - ), + subAction, + currentValue?.toMap(), + newValue, + scopedValues, + ), }; if (match == null || pointer == null) { @@ -1636,8 +1645,8 @@ ${response.body.contains('{') ? const JsonEncoder.withIndent(' ').convert(json. final defaultValue = subAction.variableType.isList ? [] : subAction.variableType.isMap - ? {} - : null; + ? {} + : null; Map storageData = { match.name: docData[match.name] ?? defaultValue, }; @@ -1660,14 +1669,8 @@ ${response.body.contains('{') ? const JsonEncoder.withIndent(' ').convert(json. value: {match.name: docData[match.name]}, ); } catch (error, stackTrace) { - DebugLogger.instance.printInfo( - error.toString(), - name: name, - ); - DebugLogger.instance.printInfo( - stackTrace.toString(), - name: name, - ); + DebugLogger.instance.printInfo(error.toString(), name: name); + DebugLogger.instance.printInfo(stackTrace.toString(), name: name); return false; } } @@ -1680,10 +1683,7 @@ ${response.body.contains('{') ? const JsonEncoder.withIndent(' ').convert(json. final CloudDatabase? cloudDatabase = scopedValues.cloudDatabase; if (cloudDatabase == null) { - DebugLogger.instance.printInfo( - 'Cloud storage is null.', - name: name, - ); + DebugLogger.instance.printInfo('Cloud storage is null.', name: name); return false; } @@ -1703,14 +1703,8 @@ ${response.body.contains('{') ? const JsonEncoder.withIndent(' ').convert(json. evaluatedDocumentId, ); } catch (error, stackTrace) { - DebugLogger.instance.printInfo( - error.toString(), - name: name, - ); - DebugLogger.instance.printInfo( - stackTrace.toString(), - name: name, - ); + DebugLogger.instance.printInfo(error.toString(), name: name); + DebugLogger.instance.printInfo(stackTrace.toString(), name: name); return false; } } @@ -1764,7 +1758,10 @@ ${response.body.contains('{') ? const JsonEncoder.withIndent(' ').convert(json. name: name, ); cloudDatabase.streamDocumentToVariable( - evaluatedPath, evaluatedDocumentId, variable); + evaluatedPath, + evaluatedDocumentId, + variable, + ); } else { DebugLogger.instance.printInfo( 'Streaming collection from cloud storage: $evaluatedPath', diff --git a/lib/src/logging/debug_logger.dart b/lib/src/logging/debug_logger.dart index 81b2b8ce..88432fee 100644 --- a/lib/src/logging/debug_logger.dart +++ b/lib/src/logging/debug_logger.dart @@ -74,8 +74,12 @@ class DebugLogger { /// /// Filters messages based on the configuration settings such as enabled and /// disabled categories, and highlights. - void log(Object? message, - {String? category, String? name, Level level = Level.INFO}) { + void log( + Object? message, { + String? category, + String? name, + Level level = Level.INFO, + }) { try { // Check if debug logging is enabled if (!config.debugLog) { @@ -121,8 +125,12 @@ class DebugLogger { } /// Prints a log message to the console using the [Logger] library. - void _log(Object? message, - {String? category, String? name, Level level = Level.INFO}) { + void _log( + Object? message, { + String? category, + String? name, + Level level = Level.INFO, + }) { StringBuffer logMessage = StringBuffer(); if (name?.isNotEmpty ?? false) { logMessage.write('[$name] '); @@ -149,8 +157,12 @@ class DebugLogger { /// } /// ``` void printRebuild(String name) { - log('Rebuild', - category: DebugCategory.rebuild, name: name, level: Level.FINEST); + log( + 'Rebuild', + category: DebugCategory.rebuild, + name: name, + level: Level.FINEST, + ); } /// [Level.FINEST] Specialized logging function used for tracing user actions within an application. @@ -168,8 +180,12 @@ class DebugLogger { /// ) /// ``` void printAction(Object? message, {String? name}) { - log(message, - category: DebugCategory.action, name: name, level: Level.FINEST); + log( + message, + category: DebugCategory.action, + name: name, + level: Level.FINEST, + ); } /// [Level.FINEST] Logs detailed information about function executions, including function name and passed parameters. @@ -191,8 +207,12 @@ class DebugLogger { /// } /// ``` void printFunction(Object? message, {required String name}) { - log(message, - category: DebugCategory.function, name: name, level: Level.FINEST); + log( + message, + category: DebugCategory.function, + name: name, + level: Level.FINEST, + ); } /// [Level.INFO] Default log method. @@ -262,19 +282,23 @@ class DebugLoggerConfig { bool isHighlighted({String? category, String? name}) { if (highlights.isEmpty) return false; - return highlights.any((String highlight) => highlight.contains('-') - ? (highlight == - [category, name] - .where((String? item) => item != null) - .join('-')) - : (highlight == category || highlight == name)); + return highlights.any( + (String highlight) => highlight.contains('-') + ? (highlight == + [ + category, + name, + ].where((String? item) => item != null).join('-')) + : (highlight == category || highlight == name), + ); } /// Sets a highlight for a specific category and name. DebugLoggerConfig setHighlight({String? category, String? name}) { - String highlight = [category, name] - .where((String? item) => item != null) - .join('-'); + String highlight = [ + category, + name, + ].where((String? item) => item != null).join('-'); if (highlight.isNotEmpty) { highlights.add(highlight); } diff --git a/lib/src/logging/error_logger.dart b/lib/src/logging/error_logger.dart index 243a174c..5b3678d5 100644 --- a/lib/src/logging/error_logger.dart +++ b/lib/src/logging/error_logger.dart @@ -1,4 +1,5 @@ import 'dart:async'; + import 'package:logging/logging.dart'; import '../logging/debug_logger.dart'; diff --git a/lib/src/logging/stat_tracker.dart b/lib/src/logging/stat_tracker.dart index 306c30e2..7778c04b 100644 --- a/lib/src/logging/stat_tracker.dart +++ b/lib/src/logging/stat_tracker.dart @@ -60,36 +60,30 @@ class StatTracker { } /// Sends the batch of stats to the server with retry logic. - Future sendBatch() => debouncer.run( - () async { - int maxRetries = 3; - int baseDelaySeconds = 2; - - for (int attempt = 0; attempt < maxRetries; attempt++) { - try { - final response = await client.post( - serverUrl!, - headers: {'Content-Type': 'application/json'}, - body: jsonEncode({ - 'projectId': projectId, - 'stats': statBatch, - }), - ); - - if (response.statusCode == 200) { - statBatch.clear(); - return; // Exit the function if successful - } - } catch (e) { - // Handle exception silently - } - await Future.delayed(Duration( - seconds: - baseDelaySeconds * (1 << attempt))); // Exponential backoff - } - }, - forceRunAfter: 20, - ); + Future sendBatch() => debouncer.run(() async { + int maxRetries = 3; + int baseDelaySeconds = 2; + + for (int attempt = 0; attempt < maxRetries; attempt++) { + try { + final response = await client.post( + serverUrl!, + headers: {'Content-Type': 'application/json'}, + body: jsonEncode({'projectId': projectId, 'stats': statBatch}), + ); + + if (response.statusCode == 200) { + statBatch.clear(); + return; // Exit the function if successful + } + } catch (e) { + // Handle exception silently + } + await Future.delayed( + Duration(seconds: baseDelaySeconds * (1 << attempt)), + ); // Exponential backoff + } + }, forceRunAfter: 20); /// Tracks a stat with optional sublabel and count /// diff --git a/lib/src/model/asset_model.dart b/lib/src/model/asset_model.dart index 5a3cb14f..4ae94cb6 100644 --- a/lib/src/model/asset_model.dart +++ b/lib/src/model/asset_model.dart @@ -73,12 +73,12 @@ class AssetModel with Equatable { @override List get props => [ - id, - name, - url, - blurHash, - sourceWidth, - sourceHeight, - createdAt, - ]; + id, + name, + url, + blurHash, + sourceWidth, + sourceHeight, + createdAt, + ]; } diff --git a/lib/src/model/auth_data.dart b/lib/src/model/auth_data.dart index d2399c80..bfff75e1 100644 --- a/lib/src/model/auth_data.dart +++ b/lib/src/model/auth_data.dart @@ -59,10 +59,10 @@ class AuthData extends PrivacyBase { @override List get props => [ - ...super.props, - authToken, - projectId, - timestamp, - isTemplate, - ]; + ...super.props, + authToken, + projectId, + timestamp, + isTemplate, + ]; } diff --git a/lib/src/model/custom_component.dart b/lib/src/model/custom_component.dart index 13168f94..d7b01d0e 100644 --- a/lib/src/model/custom_component.dart +++ b/lib/src/model/custom_component.dart @@ -53,9 +53,9 @@ class CustomComponent with Equatable, SerializableMixin { required this.projectId, required this.pageId, required int? version, - }) : createdAt = createdAt ?? DateTime.now(), - // parentId = parentId ?? getTopMostParentIDs(data.nodes).first, - version = version ?? 1; + }) : createdAt = createdAt ?? DateTime.now(), + // parentId = parentId ?? getTopMostParentIDs(data.nodes).first, + version = version ?? 1; /// Duplicate this [CustomComponent] instance with the given data overrides. CustomComponent copyWith({ @@ -68,18 +68,17 @@ class CustomComponent with Equatable, SerializableMixin { String? pageId, String? projectId, int? version, - }) => - CustomComponent( - id: id ?? this.id, - name: name ?? this.name, - data: data ?? this.data, - createdAt: DateTime.now(), - previewUrl: previewUrl ?? this.previewUrl, - blurhash: blurhash ?? this.blurhash, - projectId: projectId ?? this.projectId, - pageId: pageId ?? this.pageId, - version: version ?? this.version, - ); + }) => CustomComponent( + id: id ?? this.id, + name: name ?? this.name, + data: data ?? this.data, + createdAt: DateTime.now(), + previewUrl: previewUrl ?? this.previewUrl, + blurhash: blurhash ?? this.blurhash, + projectId: projectId ?? this.projectId, + pageId: pageId ?? this.pageId, + version: version ?? this.version, + ); /// Factory constructor for creating a new instance of this class from /// a JSON object. @@ -91,22 +90,22 @@ class CustomComponent with Equatable, SerializableMixin { @override List get props => [ - id, - name, - data, - createdAt, - previewUrl, - blurhash, - projectId, - pageId, - version, - ]; + id, + name, + data, + createdAt, + previewUrl, + blurhash, + projectId, + pageId, + version, + ]; void setParentAsInstance() => data.setParentAsInstance( - componentId: id, - componentSchema: schema, - componentVersion: version, - ); + componentId: id, + componentSchema: schema, + componentVersion: version, + ); } /// Holds component's node data and containing rect size data. @@ -163,14 +162,14 @@ class ComponentData with Equatable, SerializableMixin { @override List get props => [ - width, - height, - nodes, - variables, - conditions, - parentId, - originalParentId, - ]; + width, + height, + nodes, + variables, + conditions, + parentId, + originalParentId, + ]; @override Map toJson() => _$ComponentDataToJson(this); @@ -189,16 +188,15 @@ class ComponentData with Equatable, SerializableMixin { Map>? conditions, String? parentId, String? originalParentId, - }) => - ComponentData( - width: width ?? this.width, - height: height ?? this.height, - nodes: nodes ?? this.nodes, - variables: variables ?? this.variables, - conditions: conditions ?? this.conditions, - parentId: parentId ?? this.parentId, - originalParentId: originalParentId ?? this.originalParentId, - ); + }) => ComponentData( + width: width ?? this.width, + height: height ?? this.height, + nodes: nodes ?? this.nodes, + variables: variables ?? this.variables, + conditions: conditions ?? this.conditions, + parentId: parentId ?? this.parentId, + originalParentId: originalParentId ?? this.originalParentId, + ); void setParentAsInstance({ required String componentId, diff --git a/lib/src/model/font_family.dart b/lib/src/model/font_family.dart index 5815e8fb..88b2617b 100644 --- a/lib/src/model/font_family.dart +++ b/lib/src/model/font_family.dart @@ -44,8 +44,8 @@ class FontFamilyModel extends PrivacyBase { // Privacy required super.privacy, - }) : id = id ?? name, - super.private(); + }) : id = id ?? name, + super.private(); /// Factory constructor for creating a new [FontFamilyModel] instance from /// JSON data. @@ -100,14 +100,19 @@ class FontVariantModel extends Equatable { /// A factory constructor that creates an instance of [FontVariantModel] by /// parsing given [name]. - factory FontVariantModel.fromName(String name, - {String fontURL = '', String previewURL = ''}) { + factory FontVariantModel.fromName( + String name, { + String fontURL = '', + String previewURL = '', + }) { FontWeightNumeric fileFontWeight = FontWeightNumeric.w400; String fileFontStyle = 'Normal'; // Convert 'extra-bold' or 'extra bold' into 'extrabold'. - final String modifiedName = - name.toLowerCase().replaceAll('-', '').replaceAll(' ', ''); + final String modifiedName = name + .toLowerCase() + .replaceAll('-', '') + .replaceAll(' ', ''); // Check font weight. if (modifiedName.contains('thin')) { diff --git a/lib/src/model/font_file_data.dart b/lib/src/model/font_file_data.dart index e8d91bb7..5e851c86 100644 --- a/lib/src/model/font_file_data.dart +++ b/lib/src/model/font_file_data.dart @@ -18,6 +18,11 @@ class FontFileData extends Equatable { }); @override - List get props => - [fileName, fileExtension, familyName, style, bytes]; + List get props => [ + fileName, + fileExtension, + familyName, + style, + bytes, + ]; } diff --git a/lib/src/model/model_http_request.dart b/lib/src/model/model_http_request.dart index 97a9d47f..59b9dd7c 100644 --- a/lib/src/model/model_http_request.dart +++ b/lib/src/model/model_http_request.dart @@ -6,12 +6,7 @@ import '../../codelessly_sdk.dart'; part 'model_http_request.g.dart'; -enum HttpMethod { - get, - post, - put, - delete, -} +enum HttpMethod { get, post, put, delete } enum RequestBodyType { text, form } @@ -98,9 +93,9 @@ class HttpApiData extends PrivacyBase { required super.users, required super.roles, required super.public, - }) : variables = List.of(variables), - lastUpdated = lastUpdated ?? DateTime.now(), - created = created ?? DateTime.now(); + }) : variables = List.of(variables), + lastUpdated = lastUpdated ?? DateTime.now(), + created = created ?? DateTime.now(); HttpApiData.private({ required this.directory, @@ -123,10 +118,10 @@ class HttpApiData extends PrivacyBase { // Privacy required super.privacy, - }) : variables = List.of(variables), - lastUpdated = lastUpdated ?? DateTime.now(), - created = created ?? DateTime.now(), - super.private(); + }) : variables = List.of(variables), + lastUpdated = lastUpdated ?? DateTime.now(), + created = created ?? DateTime.now(), + super.private(); HttpApiData copyWith({ HttpMethod? method, @@ -150,28 +145,27 @@ class HttpApiData extends PrivacyBase { // Privacy PrivacyBase? privacy, - }) => - HttpApiData.private( - method: method ?? this.method, - url: url ?? this.url, - headers: headers ?? this.headers, - queryParams: queryParams ?? this.queryParams, - body: body ?? this.body, - formFields: formFields ?? this.formFields, - variables: variables ?? this.variables, - name: name ?? this.name, - project: project ?? this.project, - id: id ?? this.id, - isDeleted: isDeleted ?? this.isDeleted, - lastUpdated: lastUpdated ?? this.lastUpdated, - created: created ?? this.created, - bodyType: bodyType ?? this.bodyType, - isDraft: isDraft ?? this.isDraft, - directory: directory ?? this.directory, - requestBodyContentType: - requestBodyContentType ?? this.requestBodyContentType, - privacy: privacy ?? this, - ); + }) => HttpApiData.private( + method: method ?? this.method, + url: url ?? this.url, + headers: headers ?? this.headers, + queryParams: queryParams ?? this.queryParams, + body: body ?? this.body, + formFields: formFields ?? this.formFields, + variables: variables ?? this.variables, + name: name ?? this.name, + project: project ?? this.project, + id: id ?? this.id, + isDeleted: isDeleted ?? this.isDeleted, + lastUpdated: lastUpdated ?? this.lastUpdated, + created: created ?? this.created, + bodyType: bodyType ?? this.bodyType, + isDraft: isDraft ?? this.isDraft, + directory: directory ?? this.directory, + requestBodyContentType: + requestBodyContentType ?? this.requestBodyContentType, + privacy: privacy ?? this, + ); factory HttpApiData.fromJson(Map json) => _$HttpApiDataFromJson(json); diff --git a/lib/src/model/privacy_base.dart b/lib/src/model/privacy_base.dart index 8abf8f80..59389d38 100644 --- a/lib/src/model/privacy_base.dart +++ b/lib/src/model/privacy_base.dart @@ -16,10 +16,10 @@ enum Role { /// ----------------- POWER HIERARCHY ----------------- bool isHigherLevelThan(Role other) => switch (this) { - admin => other != admin, - editor => other == viewer, - viewer => false, - }; + admin => other != admin, + editor => other == viewer, + viewer => false, + }; bool isHigherOrEqualLevelThan(Role other) => isHigherLevelThan(other) || this == other; @@ -37,35 +37,35 @@ enum Role { /// Only admins can modify project metadata. bool get canManageProject => switch (this) { - admin => true, - editor || viewer => false, - }; + admin => true, + editor || viewer => false, + }; /// Only admins and editors can edit projects. bool get canEditProject => switch (this) { - admin || editor => true, - viewer => false, - }; + admin || editor => true, + viewer => false, + }; /// Only admins and editors can manage team members. bool get canManageTeam => switch (this) { - admin || editor => true, - viewer => false, - }; + admin || editor => true, + viewer => false, + }; /// Anyone can manage project members. bool get canManageProjectMembers => true; factory Role.fromJson(String value) => switch (value) { - 'owner' || 'admin' => admin, - 'editor' => editor, - _ => viewer, - }; + 'owner' || 'admin' => admin, + 'editor' => editor, + _ => viewer, + }; String toJson() => switch (this) { - Role.admin => 'owner', - _ => name, - }; + Role.admin => 'owner', + _ => name, + }; } /// Represents the privacy controls of a given model that this mixin @@ -98,14 +98,13 @@ abstract class PrivacyBase with SerializableMixin, Equatable { required Set users, required Map roles, required bool public, - }) => - _PrivacyBaseImpl( - owner: owner, - teams: teams, - users: users, - roles: roles, - public: public, - ); + }) => _PrivacyBaseImpl( + owner: owner, + teams: teams, + users: users, + roles: roles, + public: public, + ); /// An owner ID must always exist in the roles map. It's an error /// if it doesn't. @@ -134,19 +133,19 @@ abstract class PrivacyBase with SerializableMixin, Equatable { required Set? users, required this.roles, required bool? public, - }) : teams = teams ?? const {}, - users = users ?? const {}, - public = public ?? false; + }) : teams = teams ?? const {}, + users = users ?? const {}, + public = public ?? false; /// Creates a new [PrivacyBase] derived from another [PrivacyBase]. PrivacyBase.private({required PrivacyBase privacy, bool? public}) - : this( - owner: privacy.owner, - teams: privacy.teams, - users: privacy.users, - roles: privacy.roles, - public: public ?? privacy.public, - ); + : this( + owner: privacy.owner, + teams: privacy.teams, + users: privacy.users, + roles: privacy.roles, + public: public ?? privacy.public, + ); /// While this PrivacyBase object is extended by other models, calling /// toJson on those models will return data in addition to these core @@ -162,12 +161,12 @@ abstract class PrivacyBase with SerializableMixin, Equatable { /// } @JsonKey(includeFromJson: false, includeToJson: false) PrivacyBase get privacy => _PrivacyBaseImpl( - owner: owner, - teams: teams, - users: users, - roles: roles, - public: public, - ); + owner: owner, + teams: teams, + users: users, + roles: roles, + public: public, + ); @override List get props => [users, roles, teams, public]; diff --git a/lib/src/model/publish_source.dart b/lib/src/model/publish_source.dart index 6056c316..ee37b741 100644 --- a/lib/src/model/publish_source.dart +++ b/lib/src/model/publish_source.dart @@ -20,57 +20,57 @@ enum PublishSource { /// The path to the data collection. String get rootDataCollection => switch (this) { - PublishSource.publish => 'data', - PublishSource.preview => 'preview_data', - PublishSource.template => 'template_data', - }; + PublishSource.publish => 'data', + PublishSource.preview => 'preview_data', + PublishSource.template => 'template_data', + }; /// The path to the publish model collection. String get serverPath => switch (this) { - PublishSource.publish => 'publish', - PublishSource.preview => 'publish_preview', - PublishSource.template => 'templates', - }; + PublishSource.publish => 'publish', + PublishSource.preview => 'publish_preview', + PublishSource.template => 'templates', + }; /// The path to the mirrored or opposite collection. String get mirroredServerPath => switch (this) { - PublishSource.publish => 'publish_preview', - PublishSource.preview => 'publish', - PublishSource.template => 'publish', - }; + PublishSource.publish => 'publish_preview', + PublishSource.preview => 'publish', + PublishSource.template => 'publish', + }; /// The cache key for the published model. String get modelCacheKey => switch (this) { - PublishSource.publish => 'publish_layout_model', - PublishSource.preview => 'preview_layout_model', - PublishSource.template => 'template_layout_model', - }; + PublishSource.publish => 'publish_layout_model', + PublishSource.preview => 'preview_layout_model', + PublishSource.template => 'template_layout_model', + }; /// The cache key for the font files. String get fontsCacheKey => switch (this) { - PublishSource.publish => 'publish_fonts', - PublishSource.preview => 'preview_fonts', - PublishSource.template => 'template_fonts', - }; + PublishSource.publish => 'publish_fonts', + PublishSource.preview => 'preview_fonts', + PublishSource.template => 'template_fonts', + }; /// The cache key for the APIs. String get apisCacheKey => switch (this) { - PublishSource.publish => 'publish_apis', - PublishSource.preview => 'preview_apis', - PublishSource.template => 'template_apis', - }; + PublishSource.publish => 'publish_apis', + PublishSource.preview => 'preview_apis', + PublishSource.template => 'template_apis', + }; /// The cache key for the APIs. String get variablesCacheKey => switch (this) { - PublishSource.publish => 'publish_variables', - PublishSource.preview => 'preview_variables', - PublishSource.template => 'template_variables', - }; + PublishSource.publish => 'publish_variables', + PublishSource.preview => 'preview_variables', + PublishSource.template => 'template_variables', + }; /// The cache key for the APIs. String get conditionsCacheKey => switch (this) { - PublishSource.publish => 'publish_conditions', - PublishSource.preview => 'preview_conditions', - PublishSource.template => 'template_conditions', - }; + PublishSource.publish => 'publish_conditions', + PublishSource.preview => 'preview_conditions', + PublishSource.template => 'template_conditions', + }; } diff --git a/lib/src/model/sdk_publish_model.dart b/lib/src/model/sdk_publish_model.dart index 12bd2af0..19e5b7c3 100644 --- a/lib/src/model/sdk_publish_model.dart +++ b/lib/src/model/sdk_publish_model.dart @@ -16,7 +16,7 @@ part 'sdk_publish_model.g.dart'; /// The [dataMap] is the actual data map that holds the data. typedef LookupMapRecord = ({ Map lookupMap, - Map dataMap + Map dataMap, }); /// An extension type that allows for automatic correction of custom ids to @@ -171,20 +171,20 @@ class SDKPublishModel extends PrivacyBase { final List disabledLayouts; static Map serializeLookupMapFonts( - LookupMap map) => - map.dataMap.map((key, value) => MapEntry(key, value.toJson())); + LookupMap map, + ) => map.dataMap.map((key, value) => MapEntry(key, value.toJson())); static Map serializeLookupMapLayouts( - LookupMap map) => - map.dataMap.map((key, value) => MapEntry(key, value.toJson())); + LookupMap map, + ) => map.dataMap.map((key, value) => MapEntry(key, value.toJson())); static Map serializeLookupMapConditions( - LookupMap map) => - map.dataMap.map((key, value) => MapEntry(key, value.toJson())); + LookupMap map, + ) => map.dataMap.map((key, value) => MapEntry(key, value.toJson())); static Map serializeLookupMapVariables( - LookupMap map) => - map.dataMap.map((key, value) => MapEntry(key, value.toJson())); + LookupMap map, + ) => map.dataMap.map((key, value) => MapEntry(key, value.toJson())); static Object? readLookupMap(Map json, String key) { return {'layoutIDMap': json['layoutIDMap'], key: json[key]}; @@ -203,8 +203,8 @@ class SDKPublishModel extends PrivacyBase { lookupMap: layoutIDMap, dataMap: { for (final item in data.entries) - item.key: SDKPublishLayout.fromJson(item.value) - } + item.key: SDKPublishLayout.fromJson(item.value), + }, )); } @@ -215,8 +215,8 @@ class SDKPublishModel extends PrivacyBase { lookupMap: layoutIDMap, dataMap: { for (final item in data.entries) - item.key: SDKPublishFont.fromJson(item.value) - } + item.key: SDKPublishFont.fromJson(item.value), + }, )); } @@ -228,8 +228,8 @@ class SDKPublishModel extends PrivacyBase { lookupMap: layoutIDMap, dataMap: { for (final item in data.entries) - item.key: SDKLayoutVariables.fromJson(item.value) - } + item.key: SDKLayoutVariables.fromJson(item.value), + }, )); } @@ -241,8 +241,8 @@ class SDKPublishModel extends PrivacyBase { lookupMap: layoutIDMap, dataMap: { for (final item in data.entries) - item.key: SDKLayoutConditions.fromJson(item.value) - } + item.key: SDKLayoutConditions.fromJson(item.value), + }, )); } @@ -271,28 +271,36 @@ class SDKPublishModel extends PrivacyBase { required super.users, required super.roles, required super.public, - }) : layoutIDMap = layoutIDMap ?? {}, - pages = pages ?? [], - updates = updates ?? SDKPublishUpdates(), - layouts = layouts ?? - LookupMap( - (lookupMap: layoutIDMap ?? {}, dataMap: {}), - ), - fonts = fonts ?? - LookupMap( - (lookupMap: layoutIDMap ?? {}, dataMap: {}), - ), - variables = variables ?? - LookupMap( - (lookupMap: layoutIDMap ?? {}, dataMap: {}), - ), - conditions = conditions ?? - LookupMap( - (lookupMap: layoutIDMap ?? {}, dataMap: {}), - ), - apis = apis ?? {}, - lastUpdated = lastUpdated ?? DateTime.now(), - disabledLayouts = disabledLayouts ?? []; + }) : layoutIDMap = layoutIDMap ?? {}, + pages = pages ?? [], + updates = updates ?? SDKPublishUpdates(), + layouts = + layouts ?? + LookupMap(( + lookupMap: layoutIDMap ?? {}, + dataMap: {}, + )), + fonts = + fonts ?? + LookupMap(( + lookupMap: layoutIDMap ?? {}, + dataMap: {}, + )), + variables = + variables ?? + LookupMap(( + lookupMap: layoutIDMap ?? {}, + dataMap: {}, + )), + conditions = + conditions ?? + LookupMap(( + lookupMap: layoutIDMap ?? {}, + dataMap: {}, + )), + apis = apis ?? {}, + lastUpdated = lastUpdated ?? DateTime.now(), + disabledLayouts = disabledLayouts ?? []; /// Creates a new instance of [SDKPublishModel] with privacy inherited from /// another [PrivacyBase]. @@ -316,25 +324,29 @@ class SDKPublishModel extends PrivacyBase { // Privacy required super.privacy, - }) : layoutIDMap = layoutIDMap ?? {}, - pages = pages ?? [], - updates = updates ?? SDKPublishUpdates(), - layouts = LookupMap( - (lookupMap: layoutIDMap ?? {}, dataMap: layouts ?? {}), - ), - fonts = LookupMap( - (lookupMap: layoutIDMap ?? {}, dataMap: fonts ?? {}), - ), - variables = LookupMap( - (lookupMap: layoutIDMap ?? {}, dataMap: variables ?? {}), - ), - conditions = LookupMap( - (lookupMap: layoutIDMap ?? {}, dataMap: conditions ?? {}), - ), - lastUpdated = lastUpdated ?? DateTime.now(), - apis = apis ?? {}, - disabledLayouts = disabledLayouts ?? [], - super.private(); + }) : layoutIDMap = layoutIDMap ?? {}, + pages = pages ?? [], + updates = updates ?? SDKPublishUpdates(), + layouts = LookupMap(( + lookupMap: layoutIDMap ?? {}, + dataMap: layouts ?? {}, + )), + fonts = LookupMap(( + lookupMap: layoutIDMap ?? {}, + dataMap: fonts ?? {}, + )), + variables = LookupMap(( + lookupMap: layoutIDMap ?? {}, + dataMap: variables ?? {}, + )), + conditions = LookupMap(( + lookupMap: layoutIDMap ?? {}, + dataMap: conditions ?? {}, + )), + lastUpdated = lastUpdated ?? DateTime.now(), + apis = apis ?? {}, + disabledLayouts = disabledLayouts ?? [], + super.private(); /// Creates a new instance of [SDKPublishModel] from a JSON map. factory SDKPublishModel.fromJson(Map json) { @@ -396,22 +408,22 @@ class SDKPublishModel extends PrivacyBase { @override List get props => [ - ...super.props, - projectId, - fonts, - pages, - updates, - layoutIDMap, - layouts, - apis, - variables, - conditions, - entryLayoutId, - entryPageId, - entryCanvasId, - lastUpdated, - disabledLayouts, - ]; + ...super.props, + projectId, + fonts, + pages, + updates, + layoutIDMap, + layouts, + apis, + variables, + conditions, + entryLayoutId, + entryPageId, + entryCanvasId, + lastUpdated, + disabledLayouts, + ]; } @JsonSerializable() @@ -461,9 +473,9 @@ class SDKPublishLayout extends PrivacyBase { required super.users, required super.roles, required super.public, - }) : breakpoints = breakpoints ?? [], - canvasIds = canvases.keys.toSet(), - hidden = hidden ?? false; + }) : breakpoints = breakpoints ?? [], + canvasIds = canvases.keys.toSet(), + hidden = hidden ?? false; /// Creates a new instance of [SDKPublishLayout] with privacy inherited from /// another [PrivacyBase]. @@ -478,10 +490,10 @@ class SDKPublishLayout extends PrivacyBase { // Privacy required super.privacy, - }) : breakpoints = breakpoints ?? [], - canvasIds = canvases.keys.toSet(), - hidden = hidden ?? false, - super.private(); + }) : breakpoints = breakpoints ?? [], + canvasIds = canvases.keys.toSet(), + hidden = hidden ?? false, + super.private(); /// Returns true if the layout is expired. @JsonKey(includeFromJson: false, includeToJson: false) @@ -524,15 +536,15 @@ class SDKPublishLayout extends PrivacyBase { @override List get props => [ - ...super.props, - id, - pageId, - projectId, - canvases, - lastUpdated, - breakpoints, - hidden, - ]; + ...super.props, + id, + pageId, + projectId, + canvases, + lastUpdated, + breakpoints, + hidden, + ]; } /// Represents a single variation of a common font. @@ -581,17 +593,17 @@ class SDKPublishFont extends PrivacyBase { /// Creates a new instance of [SDKPublishFont] with privacy inherited from /// another [PrivacyBase]. - SDKPublishFont.private( - {String? id, - required this.url, - required this.family, - required this.weight, - this.style, + SDKPublishFont.private({ + String? id, + required this.url, + required this.family, + required this.weight, + this.style, - // Privacy - required super.privacy}) - : id = id ?? family, - super.private(); + // Privacy + required super.privacy, + }) : id = id ?? family, + super.private(); /// Creates a copy of this instance with the provided parameters. SDKPublishFont copyWith({ @@ -623,24 +635,14 @@ class SDKPublishFont extends PrivacyBase { Map toJson() => _$SDKPublishFontToJson(this); @override - List get props => [ - ...super.props, - url, - family, - weight, - style, - ]; + List get props => [...super.props, url, family, weight, style]; } /// Defines an interpretation of what kind of update has been made to a given /// layout or font. /// /// This is used inside the data managers directly. -enum UpdateType { - delete, - add, - update, -} +enum UpdateType { delete, add, update } @JsonSerializable() class SDKPublishUpdates with Equatable { @@ -716,14 +718,14 @@ class SDKPublishUpdates with Equatable { @override List get props => [ - fonts, - layouts, - apis, - variables, - conditions, - layoutFonts, - layoutApis, - ]; + fonts, + layouts, + apis, + variables, + conditions, + layoutFonts, + layoutApis, + ]; /// Creates a new instance of [SDKPublishUpdates] from a JSON map. factory SDKPublishUpdates.fromJson(Map json) => diff --git a/lib/src/transformers/defaults/default_properties.dart b/lib/src/transformers/defaults/default_properties.dart index 045047d9..0e688793 100644 --- a/lib/src/transformers/defaults/default_properties.dart +++ b/lib/src/transformers/defaults/default_properties.dart @@ -5,100 +5,100 @@ import '../../../codelessly_sdk.dart'; // TODO: InkWells. ListTileProperties get defaultListTileProperties => ListTileProperties( - isThreeLine: ListTileDefaults.isThreeLine, - dense: ListTileDefaults.dense, - visualDensity: ListTileDefaults.visualDensity, - shape: ListTileDefaults.shape, - selectedColor: ListTileDefaults.selectedColor.toColorRGBA(), - iconColor: ListTileDefaults.iconColor.toColorRGBA(), - textColor: ListTileDefaults.textColor.toColorRGBA(), - contentPadding: ListTileDefaults.contentPadding, - enabled: ListTileDefaults.enabled, - selected: ListTileDefaults.selected, - focusColor: ListTileDefaults.focusColor.toColorRGBA(), - hoverColor: ListTileDefaults.hoverColor.toColorRGBA(), - autofocus: ListTileDefaults.autofocus, - cornerRadius: ListTileDefaults.cornerRadius, - tileColor: ListTileDefaults.tileColor.toColorRGBA(), - selectedTileColor: ListTileDefaults.selectedTileColor.toColorRGBA(), - enableFeedback: ListTileDefaults.enableFeedback, - horizontalTitleGap: ListTileDefaults.horizontalTitleGap, - minVerticalPadding: ListTileDefaults.minVerticalPadding, - minLeadingWidth: ListTileDefaults.minLeadingWidth, - ); + isThreeLine: ListTileDefaults.isThreeLine, + dense: ListTileDefaults.dense, + visualDensity: ListTileDefaults.visualDensity, + shape: ListTileDefaults.shape, + selectedColor: ListTileDefaults.selectedColor.toColorRGBA(), + iconColor: ListTileDefaults.iconColor.toColorRGBA(), + textColor: ListTileDefaults.textColor.toColorRGBA(), + contentPadding: ListTileDefaults.contentPadding, + enabled: ListTileDefaults.enabled, + selected: ListTileDefaults.selected, + focusColor: ListTileDefaults.focusColor.toColorRGBA(), + hoverColor: ListTileDefaults.hoverColor.toColorRGBA(), + autofocus: ListTileDefaults.autofocus, + cornerRadius: ListTileDefaults.cornerRadius, + tileColor: ListTileDefaults.tileColor.toColorRGBA(), + selectedTileColor: ListTileDefaults.selectedTileColor.toColorRGBA(), + enableFeedback: ListTileDefaults.enableFeedback, + horizontalTitleGap: ListTileDefaults.horizontalTitleGap, + minVerticalPadding: ListTileDefaults.minVerticalPadding, + minLeadingWidth: ListTileDefaults.minLeadingWidth, +); TextFieldProperties get defaultTextFieldProperties => TextFieldProperties( - autoCorrect: TextFieldDefaults.autoCorrect, - autoFocus: TextFieldDefaults.autoFocus, - enableInteractiveSelection: TextFieldDefaults.enableInteractiveSelection, - enabled: TextFieldDefaults.enabled, - obscureText: TextFieldDefaults.obscureText, - readOnly: TextFieldDefaults.readOnly, - showCursor: TextFieldDefaults.showCursor, - keyboardType: TextFieldDefaults.keyboardType, - selectionHeightStyle: TextFieldDefaults.selectionHeightStyle, - selectionWidthStyle: TextFieldDefaults.selectionWidthStyle, - textAlign: TextFieldDefaults.textAlign, - textAlignVertical: TextFieldDefaults.textAlignVertical, - cursorColor: TextFieldDefaults.cursorColor.toColorRGB(), - cursorHeight: TextFieldDefaults.cursorHeight, - cursorWidth: TextFieldDefaults.cursorWidth, - cursorRadius: TextFieldDefaults.cursorRadius, - maxLines: TextFieldDefaults.maxLines, - obscuringCharacter: TextFieldDefaults.obscuringCharacter, - inputStyle: TextFieldDefaults.style, - decoration: TextFieldDefaults.decoration, - expands: false, - ); + autoCorrect: TextFieldDefaults.autoCorrect, + autoFocus: TextFieldDefaults.autoFocus, + enableInteractiveSelection: TextFieldDefaults.enableInteractiveSelection, + enabled: TextFieldDefaults.enabled, + obscureText: TextFieldDefaults.obscureText, + readOnly: TextFieldDefaults.readOnly, + showCursor: TextFieldDefaults.showCursor, + keyboardType: TextFieldDefaults.keyboardType, + selectionHeightStyle: TextFieldDefaults.selectionHeightStyle, + selectionWidthStyle: TextFieldDefaults.selectionWidthStyle, + textAlign: TextFieldDefaults.textAlign, + textAlignVertical: TextFieldDefaults.textAlignVertical, + cursorColor: TextFieldDefaults.cursorColor.toColorRGB(), + cursorHeight: TextFieldDefaults.cursorHeight, + cursorWidth: TextFieldDefaults.cursorWidth, + cursorRadius: TextFieldDefaults.cursorRadius, + maxLines: TextFieldDefaults.maxLines, + obscuringCharacter: TextFieldDefaults.obscuringCharacter, + inputStyle: TextFieldDefaults.style, + decoration: TextFieldDefaults.decoration, + expands: false, +); ButtonProperties get defaultButtonProperties => ButtonProperties( - buttonType: ButtonTypeEnum.elevated, - buttonColor: ButtonDefaults.buttonColor.toColorRGBA(), - shadowColor: ButtonDefaults.shadowColor.toColorRGBA(), - label: ButtonDefaults.label, - elevation: ButtonDefaults.elevation, - cornerRadius: ButtonDefaults.cornerRadius, - enabled: ButtonDefaults.enabled, - labelAlignment: ButtonDefaults.labelAlignment, - placement: ButtonDefaults.placement, - gap: ButtonDefaults.gap, - labelStyle: ButtonDefaults.labelStyle, - icon: ButtonDefaults.icon, - ); + buttonType: ButtonTypeEnum.elevated, + buttonColor: ButtonDefaults.buttonColor.toColorRGBA(), + shadowColor: ButtonDefaults.shadowColor.toColorRGBA(), + label: ButtonDefaults.label, + elevation: ButtonDefaults.elevation, + cornerRadius: ButtonDefaults.cornerRadius, + enabled: ButtonDefaults.enabled, + labelAlignment: ButtonDefaults.labelAlignment, + placement: ButtonDefaults.placement, + gap: ButtonDefaults.gap, + labelStyle: ButtonDefaults.labelStyle, + icon: ButtonDefaults.icon, +); CheckboxProperties get defaultCheckboxProperties => CheckboxProperties( - checkColor: CheckBoxDefaults.checkColor.toColorRGBA(), - activeColor: CheckBoxDefaults.activeColor.toColorRGBA(), - hoverColor: CheckBoxDefaults.hoverColor.toColorRGBA(), - focusColor: CheckBoxDefaults.focusColor.toColorRGBA(), - tristate: CheckBoxDefaults.tristate, - borderColor: CheckBoxDefaults.borderColor.toColorRGBA(), - borderWidth: CheckBoxDefaults.borderWidth, - cornerRadius: CheckBoxDefaults.cornerRadius, - splashRadius: CheckBoxDefaults.splashRadius, - autofocus: CheckBoxDefaults.autofocus, - ); + checkColor: CheckBoxDefaults.checkColor.toColorRGBA(), + activeColor: CheckBoxDefaults.activeColor.toColorRGBA(), + hoverColor: CheckBoxDefaults.hoverColor.toColorRGBA(), + focusColor: CheckBoxDefaults.focusColor.toColorRGBA(), + tristate: CheckBoxDefaults.tristate, + borderColor: CheckBoxDefaults.borderColor.toColorRGBA(), + borderWidth: CheckBoxDefaults.borderWidth, + cornerRadius: CheckBoxDefaults.cornerRadius, + splashRadius: CheckBoxDefaults.splashRadius, + autofocus: CheckBoxDefaults.autofocus, +); SwitchProperties get defaultSwitchProperties => SwitchProperties( - activeTrackColor: SwitchDefaults.activeTrackColor.toColorRGBA(), - inactiveTrackColor: SwitchDefaults.inactiveTrackColor.toColorRGBA(), - activeThumbColor: SwitchDefaults.activeThumbColor.toColorRGBA(), - inactiveThumbColor: SwitchDefaults.inactiveThumbColor.toColorRGBA(), - hoverColor: SwitchDefaults.hoverColor.toColorRGBA(), - focusColor: SwitchDefaults.focusColor.toColorRGBA(), - splashRadius: SwitchDefaults.splashRadius, - autofocus: SwitchDefaults.autofocus, - ); + activeTrackColor: SwitchDefaults.activeTrackColor.toColorRGBA(), + inactiveTrackColor: SwitchDefaults.inactiveTrackColor.toColorRGBA(), + activeThumbColor: SwitchDefaults.activeThumbColor.toColorRGBA(), + inactiveThumbColor: SwitchDefaults.inactiveThumbColor.toColorRGBA(), + hoverColor: SwitchDefaults.hoverColor.toColorRGBA(), + focusColor: SwitchDefaults.focusColor.toColorRGBA(), + splashRadius: SwitchDefaults.splashRadius, + autofocus: SwitchDefaults.autofocus, +); RadioProperties get defaultRadioProperties => RadioProperties( - activeColor: RadioDefaults.activeColor.toColorRGBA(), - inactiveColor: RadioDefaults.inactiveColor.toColorRGBA(), - hoverColor: RadioDefaults.hoverColor.toColorRGBA(), - focusColor: RadioDefaults.focusColor.toColorRGBA(), - splashRadius: RadioDefaults.splashRadius, - autofocus: RadioDefaults.autofocus, - toggleable: RadioDefaults.toggleable, - ); + activeColor: RadioDefaults.activeColor.toColorRGBA(), + inactiveColor: RadioDefaults.inactiveColor.toColorRGBA(), + hoverColor: RadioDefaults.hoverColor.toColorRGBA(), + focusColor: RadioDefaults.focusColor.toColorRGBA(), + splashRadius: RadioDefaults.splashRadius, + autofocus: RadioDefaults.autofocus, + toggleable: RadioDefaults.toggleable, +); M2NavigationBarProperties get defaultM2FixedNavigationBarProperties => M2NavigationBarProperties( @@ -107,15 +107,17 @@ M2NavigationBarProperties get defaultM2FixedNavigationBarProperties => navigationBarType: M2NavigationBarDefaults.navigationBarType, landscapeLayout: M2NavigationBarDefaults.landscapeLayout, selectedLabelStyle: TextProp.general( - fontSize: M2NavigationBarDefaults.labelFontSize, - fills: [M2NavigationBarDefaults.selectedLabelColorDark.toPaint()]), + fontSize: M2NavigationBarDefaults.labelFontSize, + fills: [M2NavigationBarDefaults.selectedLabelColorDark.toPaint()], + ), unselectedLabelStyle: TextProp.general( - fontSize: M2NavigationBarDefaults.labelFontSize, - fills: [M2NavigationBarDefaults.unselectedLabelColorDark.toPaint()]), - selectedIconColor: - M2NavigationBarDefaults.selectedIconColor.toColorRGBA(), - unselectedIconColor: - M2NavigationBarDefaults.unselectedIconColor.toColorRGBA(), + fontSize: M2NavigationBarDefaults.labelFontSize, + fills: [M2NavigationBarDefaults.unselectedLabelColorDark.toPaint()], + ), + selectedIconColor: M2NavigationBarDefaults.selectedIconColor + .toColorRGBA(), + unselectedIconColor: M2NavigationBarDefaults.unselectedIconColor + .toColorRGBA(), unselectedIconSize: M2NavigationBarDefaults.unselectedIconSize, selectedIconSize: M2NavigationBarDefaults.selectedIconSize, labelBehavior: M2NavigationBarDefaults.labelBehavior, @@ -129,15 +131,17 @@ M2NavigationBarProperties get defaultM2ShiftingNavigationBarProperties => navigationBarType: M2NavigationBarDefaults.navigationBarType, landscapeLayout: M2NavigationBarDefaults.landscapeLayout, selectedLabelStyle: TextProp.general( - fontSize: M2NavigationBarDefaults.labelFontSize, - fills: [M2NavigationBarDefaults.selectedLabelColorLight.toPaint()]), + fontSize: M2NavigationBarDefaults.labelFontSize, + fills: [M2NavigationBarDefaults.selectedLabelColorLight.toPaint()], + ), unselectedLabelStyle: TextProp.general( - fontSize: M2NavigationBarDefaults.labelFontSize, - fills: [M2NavigationBarDefaults.unselectedLabelColorLight.toPaint()]), - selectedIconColor: - M2NavigationBarDefaults.selectedIconColor.toColorRGBA(), - unselectedIconColor: - M2NavigationBarDefaults.unselectedIconColor.toColorRGBA(), + fontSize: M2NavigationBarDefaults.labelFontSize, + fills: [M2NavigationBarDefaults.unselectedLabelColorLight.toPaint()], + ), + selectedIconColor: M2NavigationBarDefaults.selectedIconColor + .toColorRGBA(), + unselectedIconColor: M2NavigationBarDefaults.unselectedIconColor + .toColorRGBA(), unselectedIconSize: M2NavigationBarDefaults.unselectedIconSize, selectedIconSize: M2NavigationBarDefaults.selectedIconSize, labelBehavior: M2NavigationBarDefaults.labelBehavior, @@ -150,10 +154,10 @@ M3NavigationBarProperties get defaultM3NavigationBarProperties => elevation: M3NavigationBarDefaults.elevation, selectedLabelStyle: M3NavigationBarDefaults.selectedLabelStyle, unselectedLabelStyle: M3NavigationBarDefaults.unselectedLabelStyle, - unselectedIconColor: - M3NavigationBarDefaults.unselectedIconColor.toColorRGBA(), - selectedIconColor: - M3NavigationBarDefaults.selectedIconColor.toColorRGBA(), + unselectedIconColor: M3NavigationBarDefaults.unselectedIconColor + .toColorRGBA(), + selectedIconColor: M3NavigationBarDefaults.selectedIconColor + .toColorRGBA(), unselectedIconSize: M3NavigationBarDefaults.unselectedIconSize, selectedIconSize: M3NavigationBarDefaults.selectedIconSize, labelBehavior: M3NavigationBarDefaults.labelBehavior, @@ -162,61 +166,57 @@ M3NavigationBarProperties get defaultM3NavigationBarProperties => ); SliderProperties get defaultSliderProperties => SliderProperties( - activeTrackColor: SliderDefaults.activeTrackColor.toColorRGBA(), - inactiveTrackColor: SliderDefaults.inactiveTrackColor.toColorRGBA(), - overlayColor: SliderDefaults.overlayColor.toColorRGBA(), - thumbColor: SliderDefaults.thumbColor.toColorRGBA(), - autofocus: SliderDefaults.autofocus, - min: SliderDefaults.min, - max: SliderDefaults.max, - trackHeight: SliderDefaults.trackHeight, - label: SliderDefaults.label, - isDiscrete: SliderDefaults.isDiscrete, - showLabel: SliderDefaults.showLabel, - activeTickMarkColor: SliderDefaults.activeTickMarkColor.toColorRGBA(), - inactiveTickMarkColor: SliderDefaults.inactiveTickMarkColor.toColorRGBA(), - valueIndicatorColor: SliderDefaults.valueIndicatorColor.toColorRGBA(), - valueIndicatorTextColor: - SliderDefaults.valueIndicatorTextColor.toColorRGBA(), - valueIndicatorFontSize: SliderDefaults.valueIndicatorFontSize, - allowFractionalPoints: SliderDefaults.allowFractionalPoints, - thumbRadius: SliderDefaults.thumbRadius, - showThumb: SliderDefaults.showThumb, - trackShape: SliderDefaults.trackShape, - tickMarkRadius: SliderDefaults.tickMarkRadius, - valueIndicatorShape: SliderDefaults.valueIndicatorShape, - overlayRadius: SliderDefaults.overlayRadius, - deriveOverlayColorFromThumb: SliderDefaults.deriveOverlayColorFromThumb, - ); + activeTrackColor: SliderDefaults.activeTrackColor.toColorRGBA(), + inactiveTrackColor: SliderDefaults.inactiveTrackColor.toColorRGBA(), + overlayColor: SliderDefaults.overlayColor.toColorRGBA(), + thumbColor: SliderDefaults.thumbColor.toColorRGBA(), + autofocus: SliderDefaults.autofocus, + min: SliderDefaults.min, + max: SliderDefaults.max, + trackHeight: SliderDefaults.trackHeight, + label: SliderDefaults.label, + isDiscrete: SliderDefaults.isDiscrete, + showLabel: SliderDefaults.showLabel, + activeTickMarkColor: SliderDefaults.activeTickMarkColor.toColorRGBA(), + inactiveTickMarkColor: SliderDefaults.inactiveTickMarkColor.toColorRGBA(), + valueIndicatorColor: SliderDefaults.valueIndicatorColor.toColorRGBA(), + valueIndicatorTextColor: SliderDefaults.valueIndicatorTextColor.toColorRGBA(), + valueIndicatorFontSize: SliderDefaults.valueIndicatorFontSize, + allowFractionalPoints: SliderDefaults.allowFractionalPoints, + thumbRadius: SliderDefaults.thumbRadius, + showThumb: SliderDefaults.showThumb, + trackShape: SliderDefaults.trackShape, + tickMarkRadius: SliderDefaults.tickMarkRadius, + valueIndicatorShape: SliderDefaults.valueIndicatorShape, + overlayRadius: SliderDefaults.overlayRadius, + deriveOverlayColorFromThumb: SliderDefaults.deriveOverlayColorFromThumb, +); -ExpansionTileProperties get defaultExpansionTileProperties => - ExpansionTileProperties( - backgroundColor: ExpansionTileDefaults.backgroundColor.toColorRGBA(), - collapsedBackgroundColor: - ExpansionTileDefaults.collapsedBackgroundColor.toColorRGBA(), - initiallyExpanded: ExpansionTileDefaults.initiallyExpanded, - maintainState: ExpansionTileDefaults.maintainState, - tilePadding: ExpansionTileDefaults.tilePadding, - expandedAlignment: ExpansionTileDefaults.expandedAlignment, - expandedCrossAxisAlignment: - ExpansionTileDefaults.expandedCrossAxisAlignment, - childrenPadding: ExpansionTileDefaults.childrenPadding, - iconColor: ExpansionTileDefaults.iconColor.toColorRGBA(), - collapsedIconColor: - ExpansionTileDefaults.collapsedIconColor.toColorRGBA(), - textColor: ExpansionTileDefaults.textColor.toColorRGBA(), - collapsedTextColor: - ExpansionTileDefaults.collapsedTextColor.toColorRGBA(), - controlAffinity: ExpansionTileDefaults.controlAffinity, - visualDensity: ExpansionTileDefaults.visualDensity, - ); +ExpansionTileProperties +get defaultExpansionTileProperties => ExpansionTileProperties( + backgroundColor: ExpansionTileDefaults.backgroundColor.toColorRGBA(), + collapsedBackgroundColor: ExpansionTileDefaults.collapsedBackgroundColor + .toColorRGBA(), + initiallyExpanded: ExpansionTileDefaults.initiallyExpanded, + maintainState: ExpansionTileDefaults.maintainState, + tilePadding: ExpansionTileDefaults.tilePadding, + expandedAlignment: ExpansionTileDefaults.expandedAlignment, + expandedCrossAxisAlignment: ExpansionTileDefaults.expandedCrossAxisAlignment, + childrenPadding: ExpansionTileDefaults.childrenPadding, + iconColor: ExpansionTileDefaults.iconColor.toColorRGBA(), + collapsedIconColor: ExpansionTileDefaults.collapsedIconColor.toColorRGBA(), + textColor: ExpansionTileDefaults.textColor.toColorRGBA(), + collapsedTextColor: ExpansionTileDefaults.collapsedTextColor.toColorRGBA(), + controlAffinity: ExpansionTileDefaults.controlAffinity, + visualDensity: ExpansionTileDefaults.visualDensity, +); FloatingActionButtonProperties get defaultFloatingActionButtonProperties => FloatingActionButtonProperties( - backgroundColor: - FloatingActionButtonDefaults.backgroundColor.toColorRGBA(), - foregroundColor: - FloatingActionButtonDefaults.foregroundColor.toColorRGBA(), + backgroundColor: FloatingActionButtonDefaults.backgroundColor + .toColorRGBA(), + foregroundColor: FloatingActionButtonDefaults.foregroundColor + .toColorRGBA(), label: FloatingActionButtonDefaults.label, type: FloatingActionButtonDefaults.type, elevation: FloatingActionButtonDefaults.elevation, @@ -233,47 +233,47 @@ FloatingActionButtonProperties get defaultFloatingActionButtonProperties => ); DividerProperties get defaultDividerProperties => DividerProperties( - color: DividerDefaults.color.toColorRGBA(), - thickness: DividerDefaults.thickness, - indent: DividerDefaults.indent, - endIndent: DividerDefaults.endIndent, - ); + color: DividerDefaults.color.toColorRGBA(), + thickness: DividerDefaults.thickness, + indent: DividerDefaults.indent, + endIndent: DividerDefaults.endIndent, +); CupertinoLoadingIndicatorProperties - get defaultCupertinoLoadingIndicatorProperties => - CupertinoLoadingIndicatorProperties( - color: CupertinoLoadingIndicatorDefaults.color.toColorRGBA(), - radius: CupertinoLoadingIndicatorDefaults.radius, - ); +get defaultCupertinoLoadingIndicatorProperties => + CupertinoLoadingIndicatorProperties( + color: CupertinoLoadingIndicatorDefaults.color.toColorRGBA(), + radius: CupertinoLoadingIndicatorDefaults.radius, + ); MaterialLoadingIndicatorProperties - get defaultMaterialLoadingIndicatorProperties => - MaterialLoadingIndicatorProperties( - color: MaterialLoadingIndicatorDefaults.color.toColorRGBA(), - strokeWidth: MaterialLoadingIndicatorDefaults.strokeWidth, - ); +get defaultMaterialLoadingIndicatorProperties => + MaterialLoadingIndicatorProperties( + color: MaterialLoadingIndicatorDefaults.color.toColorRGBA(), + strokeWidth: MaterialLoadingIndicatorDefaults.strokeWidth, + ); ProgressBarProperties get defaultProgressBarProperties => ProgressBarProperties( - maxValue: ProgressBarDefaults.maxValue, - backgroundColor: ProgressBarDefaults.backgroundColor.toColorRGBA(), - progressColor: ProgressBarDefaults.progressColor.toColorRGBA(), - animate: ProgressBarDefaults.animate, - animationDurationInMillis: ProgressBarDefaults.animationDurationInMillis, - cornerRadius: ProgressBarDefaults.cornerRadius, - ); + maxValue: ProgressBarDefaults.maxValue, + backgroundColor: ProgressBarDefaults.backgroundColor.toColorRGBA(), + progressColor: ProgressBarDefaults.progressColor.toColorRGBA(), + animate: ProgressBarDefaults.animate, + animationDurationInMillis: ProgressBarDefaults.animationDurationInMillis, + cornerRadius: ProgressBarDefaults.cornerRadius, +); TabBarProperties get defaultTabBarProperties => TabBarProperties( - dividerColor: TabBarDefaults.dividerColor.toColorRGBA(), - indicatorColor: TabBarDefaults.indicatorColor.toColorRGBA(), - indicatorWeight: TabBarDefaults.indicatorWeight, - labelColor: TabBarDefaults.labelColor.toColorRGBA(), - labelPadding: TabBarDefaults.labelPadding, - labelStyle: TabBarDefaults.labelStyle, - unselectedLabelColor: TabBarDefaults.unselectedLabelColor.toColorRGBA(), - unselectedLabelStyle: TabBarDefaults.unselectedLabelStyle, - indicatorSize: TabBarDefaults.indicatorSize, - indicatorPadding: TabBarDefaults.indicatorPadding, - tabItemDirection: TabBarDefaults.tabItemDirection, - gap: TabBarDefaults.gap, - showDivider: false, - ); + dividerColor: TabBarDefaults.dividerColor.toColorRGBA(), + indicatorColor: TabBarDefaults.indicatorColor.toColorRGBA(), + indicatorWeight: TabBarDefaults.indicatorWeight, + labelColor: TabBarDefaults.labelColor.toColorRGBA(), + labelPadding: TabBarDefaults.labelPadding, + labelStyle: TabBarDefaults.labelStyle, + unselectedLabelColor: TabBarDefaults.unselectedLabelColor.toColorRGBA(), + unselectedLabelStyle: TabBarDefaults.unselectedLabelStyle, + indicatorSize: TabBarDefaults.indicatorSize, + indicatorPadding: TabBarDefaults.indicatorPadding, + tabItemDirection: TabBarDefaults.tabItemDirection, + gap: TabBarDefaults.gap, + showDivider: false, +); diff --git a/lib/src/transformers/defaults/theme_defaults.dart b/lib/src/transformers/defaults/theme_defaults.dart index 27919670..ee5be900 100644 --- a/lib/src/transformers/defaults/theme_defaults.dart +++ b/lib/src/transformers/defaults/theme_defaults.dart @@ -59,8 +59,9 @@ class ButtonDefaults { static const Color shadowColor = Color(0xFFA5A5A5); static const String label = 'Button'; static const double elevation = 0; - static const CornerRadius cornerRadius = - CornerRadius.all(RadiusModel.circular(4)); + static const CornerRadius cornerRadius = CornerRadius.all( + RadiusModel.circular(4), + ); static const bool enabled = true; static const TextAlignHorizontalEnum labelAlignment = TextAlignHorizontalEnum.center; @@ -95,10 +96,8 @@ class TextFieldDefaults { static const int maxLines = 1; static const String obscuringCharacter = '•'; - static TextProp get style => TextProp.general( - fontSize: 14, - fills: [PaintModel.blackPaint], - ); + static TextProp get style => + TextProp.general(fontSize: 14, fills: [PaintModel.blackPaint]); static InputDecorationModel get decoration => InputDecorationModel(); static const Color fillColor = Color(0xffeeeeee); @@ -116,8 +115,9 @@ class CheckBoxDefaults { static const bool autofocus = false; static const bool tristate = false; static const double borderWidth = 1.5; - static const CornerRadius cornerRadius = - CornerRadius.all(RadiusModel.circular(3)); + static const CornerRadius cornerRadius = CornerRadius.all( + RadiusModel.circular(3), + ); CheckBoxDefaults._(); } @@ -150,8 +150,9 @@ class RadioDefaults { class AppBarDefaults { static const bool centerTitle = false; static const double elevation = 0; - static const MultiSourceIconModel leading = - MultiSourceIconModel.icon(color: null); + static const MultiSourceIconModel leading = MultiSourceIconModel.icon( + color: null, + ); static const bool automaticallyImplyLeading = false; static TextProp get titleStyle => TextProp.general(fontSize: 18); @@ -191,13 +192,13 @@ class M3NavigationBarDefaults { static const double elevation = 0; static TextProp get selectedLabelStyle => TextProp( - fontSize: 13, - fontName: const FontName( - family: 'Roboto', - style: 'Normal', - weight: FontWeightNumeric.w600, - ), - ); + fontSize: 13, + fontName: const FontName( + family: 'Roboto', + style: 'Normal', + weight: FontWeightNumeric.w600, + ), + ); static TextProp get unselectedLabelStyle => TextProp(fontSize: 13); static const Color unselectedIconColor = Colors.black; @@ -271,8 +272,10 @@ class FloatingActionButtonDefaults { static const double highlightElevation = 12; static const double hoverElevation = 8; static const FloatingActionButtonType type = FloatingActionButtonType.regular; - static const MultiSourceIconModel icon = - MultiSourceIconModel.icon(size: 24, color: null); + static const MultiSourceIconModel icon = MultiSourceIconModel.icon( + size: 24, + color: null, + ); static const String label = 'Button'; static TextProp get labelStyle => @@ -306,8 +309,9 @@ class ProgressBarDefaults { static const double maxValue = 100; static const bool animate = true; static const int animationDurationInMillis = 300; - static const CornerRadius cornerRadius = - CornerRadius.all(RadiusModel.circular(8)); + static const CornerRadius cornerRadius = CornerRadius.all( + RadiusModel.circular(8), + ); } class TabBarDefaults { @@ -326,7 +330,9 @@ class TabBarDefaults { static const EdgeInsetsModel indicatorPadding = EdgeInsetsModel.zero; static const AxisC tabItemDirection = AxisC.horizontal; static const double gap = 10; - static const EdgeInsetsModel labelPadding = - EdgeInsetsModel.symmetric(horizontal: 16, vertical: 12); + static const EdgeInsetsModel labelPadding = EdgeInsetsModel.symmetric( + horizontal: 16, + vertical: 12, + ); static const Color dividerColor = Colors.grey; } diff --git a/lib/src/transformers/node_transformer.dart b/lib/src/transformers/node_transformer.dart index 8b3d657a..84befc50 100644 --- a/lib/src/transformers/node_transformer.dart +++ b/lib/src/transformers/node_transformer.dart @@ -56,18 +56,18 @@ abstract class BuildSettings with Equatable { @override List get props => [ - withOpacity, - withVisibility, - withBlendModes, - withRotation, - withMargins, - withConstraints, - withReactions, - withAlignment, - isPreview, - useInk, - obscureImages, - ]; + withOpacity, + withVisibility, + withBlendModes, + withRotation, + withMargins, + withConstraints, + withReactions, + withAlignment, + isPreview, + useInk, + obscureImages, + ]; } /// A class that holds information about how to build a node that is to be @@ -153,13 +153,13 @@ class WidgetBuildSettings extends BuildSettings { @override List get props => [ - ...super.props, - debugLabel, - nullSubstitutionMode, - replaceVariablesWithSymbols, - buildRawWidget, - brightness, - ]; + ...super.props, + debugLabel, + nullSubstitutionMode, + replaceVariablesWithSymbols, + buildRawWidget, + brightness, + ]; } /// This is the base class for all transformers. @@ -175,8 +175,12 @@ class WidgetBuildSettings extends BuildSettings { /// [WidgetNodeTransformerManager]. /// /// Codegen uses different `O`, `BX`, and `BS` types. -abstract class NodeTypeTransformer { +abstract class NodeTypeTransformer< + N extends BaseNode, + O, + BX, + BS extends BuildSettings +> { /// Creates a new instance of the transformer. /// /// [getNode] is a function that returns a node by its id. @@ -192,11 +196,7 @@ abstract class NodeTypeTransformer /// Creates a new instance of the transformer. NodeWidgetTransformer(GetNode getNode, this.manager) - : super(getNode, manager); + : super(getNode, manager); } diff --git a/lib/src/transformers/node_transformer_manager.dart b/lib/src/transformers/node_transformer_manager.dart index ce9ee296..57948e2d 100644 --- a/lib/src/transformers/node_transformer_manager.dart +++ b/lib/src/transformers/node_transformer_manager.dart @@ -32,8 +32,12 @@ import '../../codelessly_sdk.dart'; /// `NT` is the node transformer type, probably a [NodeWidgetTransformer]. /// /// Codegen uses different O, BC, BS, and NT types for its transformers. -abstract class NodeTransformerManager { +abstract class NodeTransformerManager< + O, + BC, + BS extends BuildSettings, + NT extends NodeTypeTransformer +> { final GetNode getNode; NodeTransformerManager(this.getNode); @@ -44,10 +48,7 @@ abstract class NodeTransformerManager { @override Widget buildWidget( - AccordionNode node, BuildContext context, WidgetBuildSettings settings) { + AccordionNode node, + BuildContext context, + WidgetBuildSettings settings, + ) { return PassiveAccordionWidget( node: node, isInitiallyExpanded: node.properties.isExpanded, @@ -27,8 +30,11 @@ class PassiveAccordionTransformer extends NodeWidgetTransformer { ) { final String? childId = node.children.elementAtOrNull(index); if (childId == null) return const SizedBox.shrink(); - return manager.buildWidgetFromNode(getNode(childId), context, - settings: settings); + return manager.buildWidgetFromNode( + getNode(childId), + context, + settings: settings, + ); } Widget buildPreview({ diff --git a/lib/src/transformers/node_transformers/passive_app_bar_transformer.dart b/lib/src/transformers/node_transformers/passive_app_bar_transformer.dart index 59dee4b1..21227ab2 100644 --- a/lib/src/transformers/node_transformers/passive_app_bar_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_app_bar_transformer.dart @@ -38,7 +38,8 @@ class PassiveAppBarTransformer extends NodeWidgetTransformer { ), List variablesOverrides = const [], }) { - final Widget? leading = node.properties.leading.icon.show && + final Widget? leading = + node.properties.leading.icon.show && !node.properties.automaticallyImplyLeading ? retrieveIconWidget( node.properties.leading.icon, @@ -84,7 +85,8 @@ class PassiveAppBarTransformer extends NodeWidgetTransformer { surfaceTintColor: node.properties.backgroundColor.toFlutterColor(), backgroundColor: node.properties.backgroundColor.toFlutterColor(), elevation: node.properties.elevation, - foregroundColor: node.properties.foregroundColor?.toFlutterColor() ?? + foregroundColor: + node.properties.foregroundColor?.toFlutterColor() ?? node.properties.titleStyle.fills.firstOrNull?.toFlutterColor(), scrolledUnderElevation: node.properties.scrolledUnderElevation, shadowColor: node.properties.shadowColor.toFlutterColor(), @@ -93,7 +95,8 @@ class PassiveAppBarTransformer extends NodeWidgetTransformer { in node.properties.actions.whereType()) buildIconOrButton( context, - icon: retrieveIconWidget(item.icon, item.icon.size) ?? + icon: + retrieveIconWidget(item.icon, item.icon.size) ?? const SizedBox.shrink(), size: item.icon.size, tooltip: item.tooltip, @@ -111,9 +114,7 @@ class PassiveAppBarTransformer extends NodeWidgetTransformer { required List reactions, }) { final bool hasReactions = reactions - .where( - (reaction) => reaction.trigger.type == TriggerType.click, - ) + .where((reaction) => reaction.trigger.type == TriggerType.click) .isNotEmpty; if (hasReactions) { return IconButton( @@ -129,7 +130,10 @@ class PassiveAppBarTransformer extends NodeWidgetTransformer { void onTriggerAction(BuildContext context, List reactions) async { final bool executed = await FunctionsRepository.triggerAction( - context, reactions: reactions, TriggerType.click); + context, + reactions: reactions, + TriggerType.click, + ); // only navigate back by default if no actions are defined on the leading // icon. @@ -152,10 +156,7 @@ class PassiveAppBarTransformer extends NodeWidgetTransformer { basicBoxLocal: NodeBox(0, 0, width, height), properties: props, ); - return PassiveAppBarWidget( - node: node, - settings: settings, - ); + return PassiveAppBarWidget(node: node, settings: settings); } Widget buildPreview( @@ -173,17 +174,22 @@ class PassiveAppBarTransformer extends NodeWidgetTransformer { properties: properties ?? node!.properties, id: '', name: 'AppBar', - basicBoxLocal: - NodeBox(0, 0, width ?? 250, height ?? kDefaultAppBarHeight), - retainedOuterBoxLocal: - NodeBox(0, 0, width ?? 250, height ?? kDefaultAppBarHeight), + basicBoxLocal: NodeBox( + 0, + 0, + width ?? 250, + height ?? kDefaultAppBarHeight, + ), + retainedOuterBoxLocal: NodeBox( + 0, + 0, + width ?? 250, + height ?? kDefaultAppBarHeight, + ), ); return Theme( data: Theme.of(context).copyWith(platform: TargetPlatform.android), - child: PassiveAppBarWidget( - node: previewNode, - settings: settings, - ), + child: PassiveAppBarWidget(node: previewNode, settings: settings), ); } } @@ -218,9 +224,7 @@ class PassiveAppBarWidget extends StatelessWidget required List reactions, }) { final bool hasReactions = reactions - .where( - (reaction) => reaction.trigger.type == TriggerType.click, - ) + .where((reaction) => reaction.trigger.type == TriggerType.click) .isNotEmpty; if (hasReactions && onTriggerAction != null) { return IconButton( @@ -236,7 +240,8 @@ class PassiveAppBarWidget extends StatelessWidget @override Widget build(BuildContext context) { - final Widget? leading = node.properties.leading.icon.show && + final Widget? leading = + node.properties.leading.icon.show && !node.properties.automaticallyImplyLeading ? retrieveIconWidget(node.properties.leading.icon, null) : null; @@ -281,7 +286,8 @@ class PassiveAppBarWidget extends StatelessWidget ? node.properties.automaticallyImplyLeading : false, title: title, - foregroundColor: node.properties.foregroundColor?.toFlutterColor() ?? + foregroundColor: + node.properties.foregroundColor?.toFlutterColor() ?? node.properties.titleStyle.fills.firstOrNull?.toFlutterColor(), scrolledUnderElevation: node.properties.scrolledUnderElevation, titleSpacing: node.properties.titleSpacing, @@ -291,7 +297,8 @@ class PassiveAppBarWidget extends StatelessWidget in node.properties.actions.whereType()) buildIconOrButton( context, - icon: retrieveIconWidget(item.icon, item.icon.size) ?? + icon: + retrieveIconWidget(item.icon, item.icon.size) ?? const SizedBox.shrink(), size: item.icon.size, tooltip: item.tooltip, diff --git a/lib/src/transformers/node_transformers/passive_button_transformer.dart b/lib/src/transformers/node_transformers/passive_button_transformer.dart index d39cf75b..0e944da9 100644 --- a/lib/src/transformers/node_transformers/passive_button_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_button_transformer.dart @@ -14,10 +14,7 @@ class PassiveButtonTransformer extends NodeWidgetTransformer { BuildContext context, WidgetBuildSettings settings, ) { - return buildButtonFromNode( - node, - settings: settings, - ); + return buildButtonFromNode(node, settings: settings); } Widget buildFromProps( @@ -86,19 +83,26 @@ class PassiveButtonWidget extends StatelessWidget { @override Widget build(BuildContext context) { - final ScopedValues scopedValues = - ScopedValues.of(context, variablesOverrides: variablesOverrides); + final ScopedValues scopedValues = ScopedValues.of( + context, + variablesOverrides: variablesOverrides, + ); final ButtonStyle buttonStyle = createMasterButtonStyle( node, elevation: elevation, scopedValues: scopedValues, ); - final double effectiveIconSize = - min(node.properties.icon.size ?? 24, node.basicBoxLocal.height); - Widget? iconWidget = - retrieveIconWidget(node.properties.icon, effectiveIconSize); + final double effectiveIconSize = min( + node.properties.icon.size ?? 24, + node.basicBoxLocal.height, + ); + Widget? iconWidget = retrieveIconWidget( + node.properties.icon, + effectiveIconSize, + ); - final bool enabled = PropertyValueDelegate.getPropertyValue( + final bool enabled = + PropertyValueDelegate.getPropertyValue( node, 'enabled', scopedValues: scopedValues, @@ -113,10 +117,8 @@ class PassiveButtonWidget extends StatelessWidget { final List spans = node.properties.label .splitMap( variablePathRegex, - onMatch: (match) => VariableSpan( - variable: match[0]!, - style: const TextStyle(), - ), + onMatch: (match) => + VariableSpan(variable: match[0]!, style: const TextStyle()), onNonMatch: (text) => TextSpan(text: text), ) .toList(); @@ -125,7 +127,8 @@ class PassiveButtonWidget extends StatelessWidget { textAlign: node.properties.labelAlignment.flutterTextAlignment, ); } else { - final String value = PropertyValueDelegate.getPropertyValue( + final String value = + PropertyValueDelegate.getPropertyValue( node, 'label', scopedValues: scopedValues, @@ -164,7 +167,7 @@ class PassiveButtonWidget extends StatelessWidget { if (node.properties.icon.show) SizedBox(width: node.properties.gap), iconWidget, - } + }, ], ) : label, @@ -190,7 +193,7 @@ class PassiveButtonWidget extends StatelessWidget { if (node.properties.icon.show) SizedBox(width: node.properties.gap), iconWidget, - } + }, ], ) : label, @@ -216,7 +219,7 @@ class PassiveButtonWidget extends StatelessWidget { if (node.properties.icon.show) SizedBox(width: node.properties.gap), iconWidget, - } + }, ], ) : label, @@ -240,7 +243,7 @@ class PassiveButtonWidget extends StatelessWidget { if (node.properties.placement == IconPlacementEnum.end) ...{ SizedBox(width: node.properties.gap), iconWidget, - } + }, ], ) : label, @@ -264,7 +267,7 @@ class PassiveButtonWidget extends StatelessWidget { if (node.properties.placement == IconPlacementEnum.end) ...{ SizedBox(width: node.properties.gap), iconWidget, - } + }, ], ) : label, diff --git a/lib/src/transformers/node_transformers/passive_canvas_transformer.dart b/lib/src/transformers/node_transformers/passive_canvas_transformer.dart index 7b5c2d8e..f6c1415c 100644 --- a/lib/src/transformers/node_transformers/passive_canvas_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_canvas_transformer.dart @@ -24,16 +24,16 @@ class PassiveCanvasTransformer extends NodeWidgetTransformer { WidgetBuildSettings settings, ) { if (node.properties.topAppBarPlaceholderId == null) return null; - SinglePlaceholderNode appBarPlaceholderNode = - getNode(node.properties.topAppBarPlaceholderId!) - as SinglePlaceholderNode; + SinglePlaceholderNode appBarPlaceholderNode = getNode( + node.properties.topAppBarPlaceholderId!, + ) as SinglePlaceholderNode; final childId = appBarPlaceholderNode.childrenOrEmpty.firstOrNull; if (childId == null) return null; final BaseNode appBarNode = getNode(childId); - final NodeWidgetTransformer transformer = - manager.getTransformerByNode(appBarNode); + final NodeWidgetTransformer transformer = manager + .getTransformerByNode(appBarNode); if (transformer is PassiveAppBarTransformer) { final Widget appBarChild = manager.buildWidgetFromNode( @@ -56,14 +56,13 @@ class PassiveCanvasTransformer extends NodeWidgetTransformer { final Widget appBarChild = manager.buildWidgetFromNode( appBarNode, context, - settings: settings.copyWith( - brightness: node.properties.brightness, - ), + settings: settings.copyWith(brightness: node.properties.brightness), ); return PreferredSize( - preferredSize: - Size.fromHeight(appBarPlaceholderNode.outerBoxLocal.height), + preferredSize: Size.fromHeight( + appBarPlaceholderNode.outerBoxLocal.height, + ), child: SafeArea(child: appBarChild), ); } @@ -75,14 +74,15 @@ class PassiveCanvasTransformer extends NodeWidgetTransformer { WidgetBuildSettings settings, ) { if (node.properties.navigationBarPlaceholderId == null) return null; - SinglePlaceholderNode navigationBarPlaceholderNode = - getNode(node.properties.navigationBarPlaceholderId!) - as SinglePlaceholderNode; + SinglePlaceholderNode navigationBarPlaceholderNode = getNode( + node.properties.navigationBarPlaceholderId!, + ) as SinglePlaceholderNode; if (navigationBarPlaceholderNode.childrenOrEmpty.isEmpty) return null; - final navigationBarNode = - getNode(navigationBarPlaceholderNode.childrenOrEmpty.first); + final navigationBarNode = getNode( + navigationBarPlaceholderNode.childrenOrEmpty.first, + ); Widget navBar = manager.buildWidgetByID( node.properties.navigationBarPlaceholderId!, @@ -151,7 +151,8 @@ class PassiveCanvasTransformer extends NodeWidgetTransformer { ); } - final bool needsAScaffold = appBar != null || + final bool needsAScaffold = + appBar != null || floatingActionButton != null || bottomNavigationBar != null; @@ -181,10 +182,10 @@ class PassiveCanvasTransformer extends NodeWidgetTransformer { } return manager.getTransformer().buildRectangle( - node, - children: [scaffold], - settings: settings, - ); + node, + children: [scaffold], + settings: settings, + ); } Widget _wrapInScaffoldForAutoScale({ @@ -220,8 +221,9 @@ class PassiveCanvasTransformer extends NodeWidgetTransformer { Align( alignment: Alignment.topCenter, child: ConstrainedBox( - constraints: - BoxConstraints(maxWidth: node.resolvedConstraints.maxWidth!), + constraints: BoxConstraints( + maxWidth: node.resolvedConstraints.maxWidth!, + ), child: body, ), ), @@ -241,7 +243,8 @@ class PassiveCanvasTransformer extends NodeWidgetTransformer { ); } - final bool needsAScaffold = appBar != null || + final bool needsAScaffold = + appBar != null || floatingActionButton != null || bottomNavigationBar != null; @@ -293,18 +296,19 @@ class PassiveCanvasTransformer extends NodeWidgetTransformer { } return manager.getTransformer().buildRectangle( - node, - children: [scaffold], - settings: settings, - ); + node, + children: [scaffold], + settings: settings, + ); } void onFaBPressed(BuildContext context, CanvasNode node) { FunctionsRepository.triggerAction( - context, - node: node, - TriggerType.click, - reactions: node.properties.floatingActionButton?.reactions); + context, + node: node, + TriggerType.click, + reactions: node.properties.floatingActionButton?.reactions, + ); } /// This would not work if the canvas is expected to have 2 different forms. @@ -334,15 +338,15 @@ class PassiveCanvasTransformer extends NodeWidgetTransformer { settings: settings, builder: (context) => switch (node.scaleMode) { ScaleMode.responsive => _wrapInScaffoldForResponsive( - context: context, - node: node, - settings: settings, - ), + context: context, + node: node, + settings: settings, + ), ScaleMode.autoScale => _wrapInScaffoldForAutoScale( - context: context, - node: node, - settings: settings, - ) + context: context, + node: node, + settings: settings, + ), }, ); } @@ -355,7 +359,7 @@ class PassiveCanvasTransformer extends NodeWidgetTransformer { 'fill-${fill.id}', scopedValues: ScopedValues.of(context), ) ?? - fill + fill, ]; if (fills.length == 1 && fills[0].type == PaintType.solid && @@ -404,8 +408,11 @@ class _PassiveCanvasWidgetState extends State { // Listen for navigation events to update the system UI brightness back // again, such as if this canvas was navigated away from, but then the view // pops and goes back to this canvas. - codelessly?.addNavigationListener('canvas-${widget.node.id}', - (event, layoutId, canvasId) { + codelessly?.addNavigationListener('canvas-${widget.node.id}', ( + event, + layoutId, + canvasId, + ) { if (canvasId == widget.node.id) { updateSystemBrightness(context); } @@ -445,13 +452,15 @@ class _PassiveCanvasWidgetState extends State { List collectOnLoadActions() => widget.node.reactions .whereTriggerType(TriggerType.load) .map((e) => e.action) - .where((action) => - action.enabled && - // Because calling api is handled in LayoutBuilder. - action.type != ActionType.callApi && - action.type != ActionType.navigation && - action.type != ActionType.submit && - action.type != ActionType.link) + .where( + (action) => + action.enabled && + // Because calling api is handled in LayoutBuilder. + action.type != ActionType.callApi && + action.type != ActionType.navigation && + action.type != ActionType.submit && + action.type != ActionType.link, + ) .toList(); void triggerOnLoadActions(BuildContext context) { @@ -488,12 +497,13 @@ class _PassiveCanvasWidgetState extends State { @override Widget build(BuildContext context) { - final Brightness brightness = - widget.node.properties.brightness.toFlutterBrightness(context); + final Brightness brightness = widget.node.properties.brightness + .toFlutterBrightness(context); return AnnotatedRegion( value: SystemUiOverlayStyle( - statusBarColor: - brightness == Brightness.light ? Colors.black : Colors.white, + statusBarColor: brightness == Brightness.light + ? Colors.black + : Colors.white, statusBarIconBrightness: brightness, statusBarBrightness: brightness, ), diff --git a/lib/src/transformers/node_transformers/passive_checkbox_transformer.dart b/lib/src/transformers/node_transformers/passive_checkbox_transformer.dart index 17a73c04..b9895742 100644 --- a/lib/src/transformers/node_transformers/passive_checkbox_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_checkbox_transformer.dart @@ -14,15 +14,9 @@ class PassiveCheckboxTransformer extends NodeWidgetTransformer { WidgetBuildSettings settings, ) { if (settings.isPreview) { - return PreviewCheckboxWidget( - node: node, - settings: settings, - ); + return PreviewCheckboxWidget(node: node, settings: settings); } else { - return PassiveCheckboxWidget( - node: node, - settings: settings, - ); + return PassiveCheckboxWidget(node: node, settings: settings); } } } @@ -72,15 +66,24 @@ class PassiveCheckboxWidget extends StatelessWidget { void onChanged(BuildContext context, bool? internalValue) { NodeStateProvider.setState(context, internalValue); - FunctionsRepository.setPropertyValue(context, - node: node, property: 'value', value: internalValue); + FunctionsRepository.setPropertyValue( + context, + node: node, + property: 'value', + value: internalValue, + ); FunctionsRepository.triggerAction( - context, node: node, TriggerType.changed, value: internalValue); + context, + node: node, + TriggerType.changed, + value: internalValue, + ); } @override Widget build(BuildContext context) { - final bool? value = PropertyValueDelegate.getPropertyValue( + final bool? value = + PropertyValueDelegate.getPropertyValue( node, 'value', scopedValues: ScopedValues.of( @@ -123,7 +126,8 @@ class TransformerCheckbox extends StatefulWidget { class _TransformerCheckboxState extends State { @override Widget build(BuildContext context) { - final double scale = widget.node.basicBoxLocal.width / + final double scale = + widget.node.basicBoxLocal.width / (widget.node.properties.compact ? Checkbox.width : kCheckboxDefaultSize); @@ -134,7 +138,8 @@ class _TransformerCheckboxState extends State { scale: scale, child: Checkbox( key: ValueKey( - '${widget.node.id}-${IndexedItemProvider.maybeOf(context)?.index ?? ''}'), + '${widget.node.id}-${IndexedItemProvider.maybeOf(context)?.index ?? ''}', + ), value: widget.node.properties.tristate ? widget.value : (widget.value ?? false), diff --git a/lib/src/transformers/node_transformers/passive_divider_transformer.dart b/lib/src/transformers/node_transformers/passive_divider_transformer.dart index d4b4b689..b9b87422 100644 --- a/lib/src/transformers/node_transformers/passive_divider_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_divider_transformer.dart @@ -20,8 +20,9 @@ class PassiveDividerTransformer extends NodeWidgetTransformer { required DividerProperties props, required double height, required double width, - WidgetBuildSettings settings = - const WidgetBuildSettings(debugLabel: 'buildFromProps'), + WidgetBuildSettings settings = const WidgetBuildSettings( + debugLabel: 'buildFromProps', + ), }) { final node = DividerNode( id: '', @@ -39,8 +40,9 @@ class PassiveDividerTransformer extends NodeWidgetTransformer { double? height, double? width, ValueChanged? onChanged, - WidgetBuildSettings settings = - const WidgetBuildSettings(debugLabel: 'buildPreview'), + WidgetBuildSettings settings = const WidgetBuildSettings( + debugLabel: 'buildPreview', + ), }) { final previewNode = DividerNode( properties: properties ?? node?.properties ?? DividerProperties(), @@ -58,8 +60,9 @@ class PassiveDividerTransformer extends NodeWidgetTransformer { Widget buildFromNode( DividerNode node, { - WidgetBuildSettings settings = - const WidgetBuildSettings(debugLabel: 'buildFromNode'), + WidgetBuildSettings settings = const WidgetBuildSettings( + debugLabel: 'buildFromNode', + ), }) { return PassiveDividerWidget( node: node, diff --git a/lib/src/transformers/node_transformers/passive_dropdown_transformer.dart b/lib/src/transformers/node_transformers/passive_dropdown_transformer.dart index 96f1b9a0..8d6ffccd 100644 --- a/lib/src/transformers/node_transformers/passive_dropdown_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_dropdown_transformer.dart @@ -51,14 +51,26 @@ class PassiveDropdownTransformer extends NodeWidgetTransformer { void onTap(BuildContext context, DropdownNode node) => FunctionsRepository.triggerAction(context, node: node, TriggerType.click); - void onChanged(BuildContext context, DropdownNode node, int index, - Object internalValue) { + void onChanged( + BuildContext context, + DropdownNode node, + int index, + Object internalValue, + ) { NodeStateProvider.setState(context, internalValue); - FunctionsRepository.setPropertyValue(context, - node: node, property: 'value', value: internalValue); + FunctionsRepository.setPropertyValue( + context, + node: node, + property: 'value', + value: internalValue, + ); FunctionsRepository.triggerAction( - context, node: node, TriggerType.changed, value: internalValue); + context, + node: node, + TriggerType.changed, + value: internalValue, + ); } } @@ -83,39 +95,42 @@ class PassiveDropdownWidget extends StatelessWidget { for (final (index, value) in items.indexed) DropdownMenuItem( value: value, - alignment: node.properties.itemAlignment.flutterAlignmentGeometry ?? + alignment: + node.properties.itemAlignment.flutterAlignmentGeometry ?? AlignmentDirectional.centerStart, child: ChangeNotifierProvider.value( value: context.read(), child: IndexedItemProvider( item: IndexedItem(index, value), - child: Builder(builder: (context) { - String label = '$value'; - if (node.properties.useDataSource) { - String? labelText = - PropertyValueDelegate.getPropertyValue( - node, - 'itemLabel', - scopedValues: ScopedValues.of(context), - ); - labelText ??= node.properties.itemLabel; - label = PropertyValueDelegate.substituteVariables( - labelText, - nullSubstitutionMode: settings.nullSubstitutionMode, - scopedValues: ScopedValues.of(context), + child: Builder( + builder: (context) { + String label = '$value'; + if (node.properties.useDataSource) { + String? labelText = + PropertyValueDelegate.getPropertyValue( + node, + 'itemLabel', + scopedValues: ScopedValues.of(context), + ); + labelText ??= node.properties.itemLabel; + label = PropertyValueDelegate.substituteVariables( + labelText, + nullSubstitutionMode: settings.nullSubstitutionMode, + scopedValues: ScopedValues.of(context), + ); + } + return Text( + label, + style: TextUtils.retrieveTextStyleFromProp( + node.properties.itemTextStyle, + ), + overflow: TextOverflow.ellipsis, ); - } - return Text( - label, - style: TextUtils.retrieveTextStyleFromProp( - node.properties.itemTextStyle, - ), - overflow: TextOverflow.ellipsis, - ); - }), + }, + ), ), ), - ) + ), ]; } @@ -124,41 +139,45 @@ class PassiveDropdownWidget extends StatelessWidget { for (final (index, value) in items.indexed) DropdownMenuItem( value: Object, - alignment: node.properties.itemAlignment.flutterAlignmentGeometry ?? + alignment: + node.properties.itemAlignment.flutterAlignmentGeometry ?? AlignmentDirectional.centerStart, child: IndexedItemProvider( item: IndexedItem(index, value), - child: Builder(builder: (context) { - String label = '$value'; - if (node.properties.useDataSource) { - String? labelText = - PropertyValueDelegate.getPropertyValue( - node, - 'itemLabel', - scopedValues: ScopedValues.of(context), - ); - labelText ??= node.properties.itemLabel; - label = PropertyValueDelegate.substituteVariables( - labelText, - nullSubstitutionMode: settings.nullSubstitutionMode, - scopedValues: ScopedValues.of(context), - ); - } - return Container( - constraints: BoxConstraints( + child: Builder( + builder: (context) { + String label = '$value'; + if (node.properties.useDataSource) { + String? labelText = + PropertyValueDelegate.getPropertyValue( + node, + 'itemLabel', + scopedValues: ScopedValues.of(context), + ); + labelText ??= node.properties.itemLabel; + label = PropertyValueDelegate.substituteVariables( + labelText, + nullSubstitutionMode: settings.nullSubstitutionMode, + scopedValues: ScopedValues.of(context), + ); + } + return Container( + constraints: BoxConstraints( maxWidth: - node.basicBoxLocal.width - node.properties.iconSize), - child: Text( - label, - style: TextUtils.retrieveTextStyleFromProp( - node.properties.selectedItemTextStyle, + node.basicBoxLocal.width - node.properties.iconSize, ), - overflow: TextOverflow.ellipsis, - ), - ); - }), + child: Text( + label, + style: TextUtils.retrieveTextStyleFromProp( + node.properties.selectedItemTextStyle, + ), + overflow: TextOverflow.ellipsis, + ), + ); + }, + ), ), - ) + ), ]; } @@ -167,14 +186,15 @@ class PassiveDropdownWidget extends StatelessWidget { final ScopedValues scopedValues = ScopedValues.of(context); final List items = node.properties.useDataSource ? PropertyValueDelegate.getPropertyValue( - node, - 'data', - scopedValues: scopedValues, - ) ?? - [] + node, + 'data', + scopedValues: scopedValues, + ) ?? + [] : node.properties.items; - Object? value = PropertyValueDelegate.getPropertyValue( + Object? value = + PropertyValueDelegate.getPropertyValue( node, 'value', scopedValues: scopedValues, @@ -188,55 +208,64 @@ class PassiveDropdownWidget extends StatelessWidget { hoverColor: node.properties.hoverColor?.toFlutterColor(), splashColor: node.properties.splashColor?.toFlutterColor(), ), - child: Builder(builder: (context) { - final child = DropdownButton( - value: items.isEmpty || items.count(value) != 1 ? null : value, - isDense: node.properties.dense, - isExpanded: node.properties.expanded, - autofocus: node.properties.autoFocus, - enableFeedback: node.properties.enableFeedback, - alignment: node.properties.selectedItemAlignment - .flutterAlignmentGeometry ?? - Alignment.centerLeft, - hint: node.properties.hint.isNotEmpty - ? Text( - node.properties.hint, - style: TextUtils.retrieveTextStyleFromProp( - node.properties.hintStyle, - ), - overflow: TextOverflow.ellipsis, - ) - : null, - iconDisabledColor: - node.properties.iconDisabledColor.toFlutterColor(), - iconEnabledColor: node.properties.iconEnabledColor.toFlutterColor(), - iconSize: node.properties.iconSize, - icon: retrieveIconWidget( - node.properties.icon, node.properties.iconSize), - dropdownColor: node.properties.dropdownColor.toFlutterColor(), - focusColor: node.properties.focusColor.toFlutterColor(), - elevation: node.properties.elevation, - borderRadius: node.properties.borderRadius.borderRadius, - onTap: onTap, - padding: node.padding.flutterEdgeInsets, - onChanged: node.properties.enabled - ? (value) { - if (value == null) return; - final index = items.indexOf(value); - onChanged?.call(index, value); - } - : null, - underline: - node.properties.underline ? null : const SizedBox.shrink(), - items: buildItems(context, items), - selectedItemBuilder: (context) => - selectedItemBuilder(context, items), - ); - if (node.horizontalFit.isWrap) { - return IntrinsicWidth(child: child); - } - return child; - }), + child: Builder( + builder: (context) { + final child = DropdownButton( + value: items.isEmpty || items.count(value) != 1 ? null : value, + isDense: node.properties.dense, + isExpanded: node.properties.expanded, + autofocus: node.properties.autoFocus, + enableFeedback: node.properties.enableFeedback, + alignment: + node + .properties + .selectedItemAlignment + .flutterAlignmentGeometry ?? + Alignment.centerLeft, + hint: node.properties.hint.isNotEmpty + ? Text( + node.properties.hint, + style: TextUtils.retrieveTextStyleFromProp( + node.properties.hintStyle, + ), + overflow: TextOverflow.ellipsis, + ) + : null, + iconDisabledColor: node.properties.iconDisabledColor + .toFlutterColor(), + iconEnabledColor: node.properties.iconEnabledColor + .toFlutterColor(), + iconSize: node.properties.iconSize, + icon: retrieveIconWidget( + node.properties.icon, + node.properties.iconSize, + ), + dropdownColor: node.properties.dropdownColor.toFlutterColor(), + focusColor: node.properties.focusColor.toFlutterColor(), + elevation: node.properties.elevation, + borderRadius: node.properties.borderRadius.borderRadius, + onTap: onTap, + padding: node.padding.flutterEdgeInsets, + onChanged: node.properties.enabled + ? (value) { + if (value == null) return; + final index = items.indexOf(value); + onChanged?.call(index, value); + } + : null, + underline: node.properties.underline + ? null + : const SizedBox.shrink(), + items: buildItems(context, items), + selectedItemBuilder: (context) => + selectedItemBuilder(context, items), + ); + if (node.horizontalFit.isWrap) { + return IntrinsicWidth(child: child); + } + return child; + }, + ), ), ); } diff --git a/lib/src/transformers/node_transformers/passive_embedded_video_transformer.dart b/lib/src/transformers/node_transformers/passive_embedded_video_transformer.dart index d201c56d..75d1bc98 100644 --- a/lib/src/transformers/node_transformers/passive_embedded_video_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_embedded_video_transformer.dart @@ -29,11 +29,14 @@ class PassiveEmbeddedVideoTransformer String url = '', EmbeddedVideoSource source = EmbeddedVideoSource.youtube, required String baseUrl, - WidgetBuildSettings settings = - const WidgetBuildSettings(debugLabel: 'buildPreview', isPreview: true), + WidgetBuildSettings settings = const WidgetBuildSettings( + debugLabel: 'buildPreview', + isPreview: true, + ), }) { final previewNode = EmbeddedVideoNode( - properties: properties ?? + properties: + properties ?? node?.properties ?? (source == EmbeddedVideoSource.youtube ? EmbeddedYoutubeVideoProperties(url: url) @@ -133,10 +136,14 @@ class _PassiveEmbeddedVideoWidgetState switch (widget.node.properties.source) { case EmbeddedVideoSource.youtube: videoWidget = buildEmbeddedYoutubeVideo( - context, widget.node.properties as EmbeddedYoutubeVideoProperties); + context, + widget.node.properties as EmbeddedYoutubeVideoProperties, + ); case EmbeddedVideoSource.vimeo: videoWidget = buildEmbeddedVimeoVideo( - context, widget.node.properties as EmbeddedVimeoVideoProperties); + context, + widget.node.properties as EmbeddedVimeoVideoProperties, + ); } return AdaptiveNodeBox(node: widget.node, child: videoWidget); @@ -147,10 +154,7 @@ class _PassiveEmbeddedVideoWidgetState EmbeddedYoutubeVideoProperties properties, ) { if (isPlatformSupportedForWebView && !widget.settings.isPreview) { - return WebViewWidget( - controller: _controller, - key: ValueKey(properties), - ); + return WebViewWidget(controller: _controller, key: ValueKey(properties)); } if (properties.metadata != null) { return AdaptiveNodeBox( @@ -166,9 +170,10 @@ class _PassiveEmbeddedVideoWidgetState child: Container( decoration: BoxDecoration( image: const DecorationImage( - image: AssetImage('assets/images/image_youtube_video.png'), - fit: BoxFit.cover, - alignment: Alignment.topCenter), + image: AssetImage('assets/images/image_youtube_video.png'), + fit: BoxFit.cover, + alignment: Alignment.topCenter, + ), borderRadius: BorderRadius.circular(4), border: Border.all(color: Theme.of(context).colorScheme.secondary), ), @@ -189,18 +194,18 @@ class _PassiveEmbeddedVideoWidgetState } Widget buildEmbeddedVimeoVideo( - BuildContext context, EmbeddedVimeoVideoProperties properties) { + BuildContext context, + EmbeddedVimeoVideoProperties properties, + ) { if (isPlatformSupportedForWebView && !widget.settings.isPreview) { - return WebViewWidget( - key: ValueKey(properties), - controller: _controller, - ); + return WebViewWidget(key: ValueKey(properties), controller: _controller); } if (properties.metadata != null) { return AdaptiveNodeBox( node: widget.node, - child: - Center(child: VimeoVideoPreviewUI(metadata: properties.metadata!)), + child: Center( + child: VimeoVideoPreviewUI(metadata: properties.metadata!), + ), ); } @@ -211,9 +216,10 @@ class _PassiveEmbeddedVideoWidgetState child: Container( decoration: BoxDecoration( image: const DecorationImage( - image: AssetImage('assets/images/image_vimeo_video.png'), - fit: BoxFit.cover, - alignment: Alignment.topCenter), + image: AssetImage('assets/images/image_vimeo_video.png'), + fit: BoxFit.cover, + alignment: Alignment.topCenter, + ), borderRadius: BorderRadius.circular(4), border: Border.all(color: Theme.of(context).colorScheme.secondary), ), @@ -264,24 +270,26 @@ class YoutubeVideoPreviewUI extends StatelessWidget { child: Stack( children: [ Positioned.fill( - child: Builder(builder: (context) { - if (isEmpty) { - return Container( - color: Colors.black, - alignment: Alignment.center, - child: const Text( - 'Video Unavailable', - style: TextStyle(color: Colors.grey), - ), + child: Builder( + builder: (context) { + if (isEmpty) { + return Container( + color: Colors.black, + alignment: Alignment.center, + child: const Text( + 'Video Unavailable', + style: TextStyle(color: Colors.grey), + ), + ); + } + return Image.network( + metadata.cloudThumbnailUrl.isNotEmpty + ? metadata.cloudThumbnailUrl + : metadata.thumbnailUrl, + fit: BoxFit.cover, ); - } - return Image.network( - metadata.cloudThumbnailUrl.isNotEmpty - ? metadata.cloudThumbnailUrl - : metadata.thumbnailUrl, - fit: BoxFit.cover, - ); - }), + }, + ), ), Positioned( left: 0, @@ -365,24 +373,26 @@ class VimeoVideoPreviewUI extends StatelessWidget { child: Stack( children: [ Positioned.fill( - child: Builder(builder: (context) { - if (isEmpty) { - return Container( - color: Colors.black, - alignment: Alignment.center, - child: const Text( - 'Video Unavailable', - style: TextStyle(color: Colors.grey), - ), + child: Builder( + builder: (context) { + if (isEmpty) { + return Container( + color: Colors.black, + alignment: Alignment.center, + child: const Text( + 'Video Unavailable', + style: TextStyle(color: Colors.grey), + ), + ); + } + return Image.network( + metadata.cloudThumbnailUrl.isNotEmpty + ? metadata.cloudThumbnailUrl + : metadata.thumbnailUrl, + fit: BoxFit.cover, ); - } - return Image.network( - metadata.cloudThumbnailUrl.isNotEmpty - ? metadata.cloudThumbnailUrl - : metadata.thumbnailUrl, - fit: BoxFit.cover, - ); - }), + }, + ), ), Positioned( left: 0, @@ -393,8 +403,10 @@ class VimeoVideoPreviewUI extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( - padding: - const EdgeInsets.symmetric(horizontal: 10, vertical: 6), + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 6, + ), decoration: BoxDecoration( color: Colors.black.withValues(alpha: 0.7), borderRadius: BorderRadius.circular(6), @@ -412,8 +424,10 @@ class VimeoVideoPreviewUI extends StatelessWidget { ), const SizedBox(height: 6), Container( - padding: - const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), decoration: BoxDecoration( color: Colors.black.withValues(alpha: 0.7), borderRadius: BorderRadius.circular(6), @@ -444,10 +458,7 @@ class VimeoVideoPreviewUI extends StatelessWidget { color: Colors.black.withValues(alpha: 0.8), borderRadius: BorderRadius.circular(4), ), - child: const Icon( - Icons.play_arrow, - color: Colors.white, - ), + child: const Icon(Icons.play_arrow, color: Colors.white), ), ), Positioned( @@ -464,9 +475,7 @@ class VimeoVideoPreviewUI extends StatelessWidget { child: Container( height: 10, decoration: BoxDecoration( - border: Border.all( - color: Colors.grey, - ), + border: Border.all(color: Colors.grey), ), ), ), @@ -477,11 +486,7 @@ class VimeoVideoPreviewUI extends StatelessWidget { color: Colors.white, ), const SizedBox(width: 4), - const Icon( - Icons.settings, - size: 14, - color: Colors.white, - ), + const Icon(Icons.settings, size: 14, color: Colors.white), const SizedBox(width: 4), Image.asset( 'assets/images/vimeo_logo.png', diff --git a/lib/src/transformers/node_transformers/passive_expansion_tile_transformer.dart b/lib/src/transformers/node_transformers/passive_expansion_tile_transformer.dart index 04f7942f..57f2341b 100644 --- a/lib/src/transformers/node_transformers/passive_expansion_tile_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_expansion_tile_transformer.dart @@ -69,14 +69,16 @@ class PassiveExpansionTileTransformer ) : null; - final ExpansionTileNode previewNode = node ?? + final ExpansionTileNode previewNode = + node ?? ExpansionTileNode( id: 'expansion_tile', name: 'Expansion Tile', basicBoxLocal: NodeBox(0, 0, width, height), retainedOuterBoxLocal: NodeBox(0, 0, width, height), verticalFit: SizeFit.shrinkWrap, - properties: (props ?? + properties: + (props ?? node?.properties ?? ExpansionTileProperties( controlAffinity: controlAffinity, @@ -99,11 +101,8 @@ class PassiveExpansionTileTransformer trailingNode: trailingNode, initiallyExpanded: initiallyExpanded, getNode: getNode, - buildWidgetFromNode: (node, context) => manager.buildWidgetFromNode( - node, - context, - settings: settings, - ), + buildWidgetFromNode: (node, context) => + manager.buildWidgetFromNode(node, context, settings: settings), ); } @@ -153,16 +152,18 @@ class PassiveExpansionTileWidget extends StatelessWidget { /// Defines whether the ExpansionTile should render its internal rotating /// chevron icon on the leading part. bool shouldBuildLeading() { - final effectiveAffinity = - node.properties.effectiveAffinity(node.properties.controlAffinity); + final effectiveAffinity = node.properties.effectiveAffinity( + node.properties.controlAffinity, + ); return effectiveAffinity == ListTileControlAffinityC.leading; } /// Defines whether the ExpansionTile should render its internal trailing /// icon on the trailing part. bool shouldBuildTrailing() { - final effectiveAffinity = - node.properties.effectiveAffinity(node.properties.controlAffinity); + final effectiveAffinity = node.properties.effectiveAffinity( + node.properties.controlAffinity, + ); return effectiveAffinity == ListTileControlAffinityC.trailing; } @@ -235,8 +236,9 @@ class PassiveExpansionTileWidget extends StatelessWidget { child: Theme( data: Theme.of(context).copyWith( visualDensity: node.properties.visualDensity.flutterVisualDensity, - dividerColor: - node.properties.showDividers ? null : Colors.transparent, + dividerColor: node.properties.showDividers + ? null + : Colors.transparent, ), child: ExpansionTile( key: ValueKey(node.id), @@ -259,18 +261,17 @@ class PassiveExpansionTileWidget extends StatelessWidget { tilePadding: node.properties.tilePadding?.flutterEdgeInsets, expandedCrossAxisAlignment: node.properties.expandedCrossAxisAlignment.flutterAxis, - expandedAlignment: - node.properties.expandedAlignment.flutterAlignment, + expandedAlignment: node.properties.expandedAlignment.flutterAlignment, childrenPadding: node.properties.childrenPadding?.flutterEdgeInsets, backgroundColor: node.properties.backgroundColor?.toFlutterColor(), - collapsedBackgroundColor: - node.properties.collapsedBackgroundColor?.toFlutterColor(), + collapsedBackgroundColor: node.properties.collapsedBackgroundColor + ?.toFlutterColor(), iconColor: node.properties.iconColor?.toFlutterColor(), textColor: node.properties.textColor?.toFlutterColor(), - collapsedTextColor: - node.properties.collapsedTextColor?.toFlutterColor(), - collapsedIconColor: - node.properties.collapsedIconColor?.toFlutterColor(), + collapsedTextColor: node.properties.collapsedTextColor + ?.toFlutterColor(), + collapsedIconColor: node.properties.collapsedIconColor + ?.toFlutterColor(), controlAffinity: node.properties .effectiveAffinity(node.properties.controlAffinity) .flutterControlAffinity, @@ -287,30 +288,25 @@ class PassiveExpansionTileWidget extends StatelessWidget { leading: leading == null ? null : leading is SinglePlaceholderNode && - leading.children.isEmpty && - shouldBuildLeading() - ? null - : StrictNodeBox( - node: leading, - child: Stack( - children: [ - buildWidgetFromNode(leading, context), - ], - ), - ), + leading.children.isEmpty && + shouldBuildLeading() + ? null + : StrictNodeBox( + node: leading, + child: Stack( + children: [buildWidgetFromNode(leading, context)], + ), + ), trailing: trailing == null ? null : trailing is SinglePlaceholderNode && - trailing.children.isEmpty && - shouldBuildTrailing() - ? null - : StrictNodeBox( - node: trailing, - child: buildWidgetFromNode( - trailing, - context, - ), - ), + trailing.children.isEmpty && + shouldBuildTrailing() + ? null + : StrictNodeBox( + node: trailing, + child: buildWidgetFromNode(trailing, context), + ), children: children .map((id) => buildWidgetFromNode(getNode(id), context)) .toList(), diff --git a/lib/src/transformers/node_transformers/passive_external_component_transformer.dart b/lib/src/transformers/node_transformers/passive_external_component_transformer.dart index 66c7923e..9d02e27b 100644 --- a/lib/src/transformers/node_transformers/passive_external_component_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_external_component_transformer.dart @@ -30,8 +30,8 @@ class PassiveExternalComponentWidget extends StatelessWidget { @override Widget build(BuildContext context) { - final CodelesslyContext codelesslyContext = - context.watch(); + final CodelesslyContext codelesslyContext = context + .watch(); final WidgetBuilder? builder = codelesslyContext.externalComponentBuilders[node.properties.builderID]; diff --git a/lib/src/transformers/node_transformers/passive_floating_action_button_transformer.dart b/lib/src/transformers/node_transformers/passive_floating_action_button_transformer.dart index fdf7cda0..bc111069 100644 --- a/lib/src/transformers/node_transformers/passive_floating_action_button_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_floating_action_button_transformer.dart @@ -21,23 +21,28 @@ class PassiveFloatingActionButtonTransformer required FloatingActionButtonProperties props, double? width, double? height, - WidgetBuildSettings settings = - const WidgetBuildSettings(debugLabel: 'buildFromProps'), + WidgetBuildSettings settings = const WidgetBuildSettings( + debugLabel: 'buildFromProps', + ), }) { final node = FloatingActionButtonNode( id: '', name: 'FAB', - basicBoxLocal: - NodeBox(0, 0, width ?? props.type.size, height ?? props.type.size), - retainedOuterBoxLocal: - NodeBox(0, 0, width ?? props.type.size, props.type.size), + basicBoxLocal: NodeBox( + 0, + 0, + width ?? props.type.size, + height ?? props.type.size, + ), + retainedOuterBoxLocal: NodeBox( + 0, + 0, + width ?? props.type.size, + props.type.size, + ), properties: props, ); - return buildFromNode( - context, - node, - settings: settings, - ); + return buildFromNode(context, node, settings: settings); } Widget buildPreview({ @@ -45,8 +50,9 @@ class PassiveFloatingActionButtonTransformer FloatingActionButtonNode? node, double? width, VoidCallback? onPressed, - WidgetBuildSettings settings = - const WidgetBuildSettings(debugLabel: 'buildPreview'), + WidgetBuildSettings settings = const WidgetBuildSettings( + debugLabel: 'buildPreview', + ), }) { properties = properties ?? node?.properties ?? FloatingActionButtonProperties(); @@ -54,10 +60,18 @@ class PassiveFloatingActionButtonTransformer properties: properties, id: '', name: 'FAB', - basicBoxLocal: - NodeBox(0, 0, width ?? properties.type.size, properties.type.size), - retainedOuterBoxLocal: - NodeBox(0, 0, width ?? properties.type.size, properties.type.size), + basicBoxLocal: NodeBox( + 0, + 0, + width ?? properties.type.size, + properties.type.size, + ), + retainedOuterBoxLocal: NodeBox( + 0, + 0, + width ?? properties.type.size, + properties.type.size, + ), ); return PassiveFloatingActionButtonWidget( node: previewNode, @@ -69,8 +83,9 @@ class PassiveFloatingActionButtonTransformer Widget buildFromNode( BuildContext context, FloatingActionButtonNode node, { - WidgetBuildSettings settings = - const WidgetBuildSettings(debugLabel: 'buildFromNode'), + WidgetBuildSettings settings = const WidgetBuildSettings( + debugLabel: 'buildFromNode', + ), }) { return PassiveFloatingActionButtonWidget( node: node, @@ -80,8 +95,11 @@ class PassiveFloatingActionButtonTransformer } void onPressed(BuildContext context, List reactions) => - FunctionsRepository.triggerAction(context, TriggerType.click, - reactions: reactions); + FunctionsRepository.triggerAction( + context, + TriggerType.click, + reactions: reactions, + ); } class PassiveFloatingActionButtonWidget extends StatelessWidget { @@ -104,12 +122,7 @@ class PassiveFloatingActionButtonWidget extends StatelessWidget { return SizedBox( width: node.middleBoxLocal.width, height: node.middleBoxLocal.height, - child: buildFAB( - node.id, - fab, - onPressed: onPressed, - elevation: elevation, - ), + child: buildFAB(node.id, fab, onPressed: onPressed, elevation: elevation), ); } @@ -173,8 +186,10 @@ class PassiveFloatingActionButtonWidget extends StatelessWidget { return FloatingActionButton.extended( heroTag: null, onPressed: onPressed, - extendedPadding: - const EdgeInsets.symmetric(vertical: 2, horizontal: 10), + extendedPadding: const EdgeInsets.symmetric( + vertical: 2, + horizontal: 10, + ), backgroundColor: fab.backgroundColor.toFlutterColor(), foregroundColor: fab.foregroundColor.toFlutterColor(), elevation: elevation ?? fab.elevation, @@ -185,8 +200,9 @@ class PassiveFloatingActionButtonWidget extends StatelessWidget { hoverColor: fab.hoverColor?.toFlutterColor(), splashColor: fab.splashColor?.toFlutterColor(), extendedIconLabelSpacing: fab.extendedIconLabelSpacing, - extendedTextStyle: - TextUtils.retrieveTextStyleFromProp(fab.labelStyle), + extendedTextStyle: TextUtils.retrieveTextStyleFromProp( + fab.labelStyle, + ), shape: shape, icon: !fab.icon.show ? null : getFABIcon(fab), label: Text(fab.label), diff --git a/lib/src/transformers/node_transformers/passive_grid_view_transformer.dart b/lib/src/transformers/node_transformers/passive_grid_view_transformer.dart index 0cff190c..47b4829c 100644 --- a/lib/src/transformers/node_transformers/passive_grid_view_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_grid_view_transformer.dart @@ -15,7 +15,10 @@ class PassiveGridViewTransformer extends NodeWidgetTransformer { @override Widget buildWidget( - GridViewNode node, BuildContext context, WidgetBuildSettings settings) { + GridViewNode node, + BuildContext context, + WidgetBuildSettings settings, + ) { return PassiveGridViewWidget( node: node, manager: manager, @@ -37,7 +40,9 @@ class PassiveGridViewWidget extends StatelessWidget { }); Query> constructQuery( - BuildContext context, Codelessly codelessly) { + BuildContext context, + Codelessly codelessly, + ) { final PublishSource source = codelessly.config?.publishSource ?? PublishSource.preview; @@ -104,7 +109,8 @@ class PassiveGridViewWidget extends StatelessWidget { return GridViewBuilder( primary: node.primary, gridDelegate: node.properties.gridDelegate, - shrinkWrap: node.properties.itemCount != null && + shrinkWrap: + node.properties.itemCount != null && (node.scrollDirection == AxisC.horizontal ? node.isHorizontalWrap : node.isVerticalWrap), @@ -112,8 +118,9 @@ class PassiveGridViewWidget extends StatelessWidget { padding: node.padding.flutterEdgeInsets, keyboardDismissBehavior: node.keyboardDismissBehavior.flutterKeyboardDismissBehavior, - physics: - node.physics.flutterScrollPhysics(node.shouldAlwaysScroll), + physics: node.physics.flutterScrollPhysics( + node.shouldAlwaysScroll, + ), scrollDirection: node.scrollDirection.flutterAxis, cacheExtent: node.properties.cacheExtent, reverse: node.reverse, @@ -124,10 +131,7 @@ class PassiveGridViewWidget extends StatelessWidget { final data = doc?.data(); return IndexedItemProvider( key: ValueKey(index), - item: IndexedItem( - index, - {'id': ?id, ...?data}, - ), + item: IndexedItem(index, {'id': ?id, ...?data}), child: Builder( builder: (context) { // This builder is important to pass a context that has @@ -150,7 +154,8 @@ class PassiveGridViewWidget extends StatelessWidget { ); } - final List? data = PropertyValueDelegate.getVariableValueFromPath( + final List? data = + PropertyValueDelegate.getVariableValueFromPath( node.variables['data'] ?? '', scopedValues: ScopedValues.of(context), ) ?? @@ -168,7 +173,8 @@ class PassiveGridViewWidget extends StatelessWidget { child: GridViewBuilder( primary: node.primary, gridDelegate: node.properties.gridDelegate, - shrinkWrap: node.properties.itemCount != null && + shrinkWrap: + node.properties.itemCount != null && (node.scrollDirection == AxisC.horizontal ? node.isHorizontalWrap : node.isVerticalWrap), @@ -244,8 +250,9 @@ class GridViewBuilder extends StatelessWidget { padding: padding, physics: physics, scrollDirection: scrollDirection, - scrollCacheExtent: - cacheExtent == null ? null : ScrollCacheExtent.pixels(cacheExtent!), + scrollCacheExtent: cacheExtent == null + ? null + : ScrollCacheExtent.pixels(cacheExtent!), reverse: reverse, clipBehavior: clipBehavior, itemBuilder: itemBuilder!, diff --git a/lib/src/transformers/node_transformers/passive_icon_transformer.dart b/lib/src/transformers/node_transformers/passive_icon_transformer.dart index aac7b5c0..f927bc36 100644 --- a/lib/src/transformers/node_transformers/passive_icon_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_icon_transformer.dart @@ -37,6 +37,8 @@ class PassiveIconWidget extends StatelessWidget { Widget build(BuildContext context) { return retrieveIconWidget(node.properties.icon, node.basicBoxLocal.width) ?? SizedBox( - width: node.basicBoxLocal.width, height: node.basicBoxLocal.height); + width: node.basicBoxLocal.width, + height: node.basicBoxLocal.height, + ); } } diff --git a/lib/src/transformers/node_transformers/passive_list_tile_transformer.dart b/lib/src/transformers/node_transformers/passive_list_tile_transformer.dart index 684acf11..af8d6a8f 100644 --- a/lib/src/transformers/node_transformers/passive_list_tile_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_list_tile_transformer.dart @@ -29,12 +29,18 @@ class PassiveListTileTransformer extends NodeWidgetTransformer { } void onTap(BuildContext context, List reactions) => - FunctionsRepository.triggerAction(context, TriggerType.click, - reactions: reactions); + FunctionsRepository.triggerAction( + context, + TriggerType.click, + reactions: reactions, + ); void onLongPress(BuildContext context, List reactions) => - FunctionsRepository.triggerAction(context, TriggerType.longPress, - reactions: reactions); + FunctionsRepository.triggerAction( + context, + TriggerType.longPress, + reactions: reactions, + ); Widget buildPreview( BuildContext context, { @@ -51,7 +57,8 @@ class PassiveListTileTransformer extends NodeWidgetTransformer { replaceVariablesWithSymbols: true, ), }) { - final previewNode = node ?? + final previewNode = + node ?? ListTileNode( id: 'list_tile', name: 'List Tile', @@ -128,7 +135,7 @@ class PassiveListTileTransformer extends NodeWidgetTransformer { end: titleText.length, fills: [PaintModel.blackPaint], fontSize: 16, - ) + ), ], ); @@ -146,7 +153,7 @@ class PassiveListTileTransformer extends NodeWidgetTransformer { end: subtitleText.length, fills: [PaintModel.blackPaint], fontSize: 14, - ) + ), ], ); @@ -156,12 +163,7 @@ class PassiveListTileTransformer extends NodeWidgetTransformer { basicBoxLocal: NodeBox(0, 0, width, height), retainedOuterBoxLocal: NodeBox(0, 0, width, height), properties: properties, - children: [ - leading.id, - title.id, - subtitle.id, - trailing.id, - ], + children: [leading.id, title.id, subtitle.id, trailing.id], leading: leading.id, title: title.id, subtitle: subtitle.id, @@ -278,8 +280,8 @@ class PassiveListTileWidget extends StatelessWidget { isThreeLine: node.properties.isThreeLine, shape: getShapeFromMixin(node.properties), selected: node.properties.selected, - selectedTileColor: - node.properties.selectedTileColor?.toFlutterColor(), + selectedTileColor: node.properties.selectedTileColor + ?.toFlutterColor(), hoverColor: node.properties.hoverColor?.toFlutterColor(), focusColor: node.properties.focusColor?.toFlutterColor(), enabled: node.properties.enabled, @@ -297,35 +299,35 @@ class PassiveListTileWidget extends StatelessWidget { title: title == null ? null : titleWidget ?? - SizedBox( - width: title.basicBoxLocal.width, - height: title.basicBoxLocal.height, - child: buildWidgetFromNode(title, context), - ), + SizedBox( + width: title.basicBoxLocal.width, + height: title.basicBoxLocal.height, + child: buildWidgetFromNode(title, context), + ), subtitle: subtitle == null ? null : subtitleWidget ?? - SizedBox( - width: subtitle.basicBoxLocal.width, - height: subtitle.basicBoxLocal.height, - child: buildWidgetFromNode(subtitle, context), - ), + SizedBox( + width: subtitle.basicBoxLocal.width, + height: subtitle.basicBoxLocal.height, + child: buildWidgetFromNode(subtitle, context), + ), leading: leading == null ? null : leadingWidget ?? - SizedBox( - width: leading.basicBoxLocal.width, - height: leading.basicBoxLocal.height, - child: buildWidgetFromNode(leading, context), - ), + SizedBox( + width: leading.basicBoxLocal.width, + height: leading.basicBoxLocal.height, + child: buildWidgetFromNode(leading, context), + ), trailing: trailing == null ? null : trailingWidget ?? - SizedBox( - width: trailing.basicBoxLocal.width, - height: trailing.basicBoxLocal.height, - child: buildWidgetFromNode(trailing, context), - ), + SizedBox( + width: trailing.basicBoxLocal.width, + height: trailing.basicBoxLocal.height, + child: buildWidgetFromNode(trailing, context), + ), ), ), ); diff --git a/lib/src/transformers/node_transformers/passive_list_view_transformer.dart b/lib/src/transformers/node_transformers/passive_list_view_transformer.dart index 79a63c9a..787a78ba 100644 --- a/lib/src/transformers/node_transformers/passive_list_view_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_list_view_transformer.dart @@ -15,7 +15,10 @@ class PassiveListViewTransformer extends NodeWidgetTransformer { @override Widget buildWidget( - ListViewNode node, BuildContext context, WidgetBuildSettings settings) { + ListViewNode node, + BuildContext context, + WidgetBuildSettings settings, + ) { return PassiveListViewWidget( node: node, manager: manager, @@ -37,7 +40,9 @@ class PassiveListViewWidget extends StatelessWidget { }); Query> constructQuery( - BuildContext context, Codelessly codelessly) { + BuildContext context, + Codelessly codelessly, + ) { final PublishSource source = codelessly.config?.publishSource ?? PublishSource.preview; @@ -112,8 +117,9 @@ class PassiveListViewWidget extends StatelessWidget { padding: node.padding.flutterEdgeInsets, keyboardDismissBehavior: node.keyboardDismissBehavior.flutterKeyboardDismissBehavior, - physics: - node.physics.flutterScrollPhysics(node.shouldAlwaysScroll), + physics: node.physics.flutterScrollPhysics( + node.shouldAlwaysScroll, + ), scrollDirection: node.scrollDirection.flutterAxis, cacheExtent: node.properties.cacheExtent, reverse: node.reverse, @@ -121,9 +127,9 @@ class PassiveListViewWidget extends StatelessWidget { clipBehavior: node.clipsContent ? Clip.hardEdge : Clip.none, separatedBuilder: node.properties.hasSeparator ? (context, index) => ListViewItemSeparator( - scrollDirection: node.scrollDirection, - properties: node.properties, - ) + scrollDirection: node.scrollDirection, + properties: node.properties, + ) : null, itemBuilder: (context, index) { final doc = docs.elementAtOrNull(index); @@ -131,22 +137,21 @@ class PassiveListViewWidget extends StatelessWidget { final data = doc?.data(); return IndexedItemProvider( key: ValueKey(index), - item: IndexedItem( - index, - {'id': ?id, ...?data}, + item: IndexedItem(index, {'id': ?id, ...?data}), + child: Builder( + builder: (context) { + // This builder is important to pass a context that has + // the IndexedItemProvider. + return KeyedSubtree( + key: ValueKey(index), + child: manager.buildWidgetByID( + itemNode, + context, + settings: settings, + ), + ); + }, ), - child: Builder(builder: (context) { - // This builder is important to pass a context that has - // the IndexedItemProvider. - return KeyedSubtree( - key: ValueKey(index), - child: manager.buildWidgetByID( - itemNode, - context, - settings: settings, - ), - ); - }), ); }, ); @@ -155,7 +160,8 @@ class PassiveListViewWidget extends StatelessWidget { ); } - final List? data = PropertyValueDelegate.getVariableValueFromPath( + final List? data = + PropertyValueDelegate.getVariableValueFromPath( node.variables['data'] ?? '', scopedValues: ScopedValues.of(context), ) ?? @@ -180,9 +186,9 @@ class PassiveListViewWidget extends StatelessWidget { final bool shrinkWrap = parentScrollable != null ? true : node.properties.itemCount != null && - (node.scrollDirection == AxisC.horizontal - ? node.isHorizontalWrap - : node.isVerticalWrap); + (node.scrollDirection == AxisC.horizontal + ? node.isHorizontalWrap + : node.isVerticalWrap); return AdaptiveNodeBox( node: node, @@ -199,28 +205,31 @@ class PassiveListViewWidget extends StatelessWidget { reverse: node.reverse, itemExtent: node.properties.itemExtent, clipBehavior: node.clipsContent ? Clip.hardEdge : Clip.none, - separatedBuilder: (node.properties.itemCount != null || data != null) && + separatedBuilder: + (node.properties.itemCount != null || data != null) && node.properties.hasSeparator ? (context, index) => ListViewItemSeparator( - scrollDirection: node.scrollDirection, - properties: node.properties, - ) + scrollDirection: node.scrollDirection, + properties: node.properties, + ) : null, itemBuilder: (context, index) => IndexedItemProvider( key: ValueKey(index), item: IndexedItem(index, data?.elementAtOrNull(index)), - child: Builder(builder: (context) { - // This builder is important to pass a context that has - // the IndexedItemProvider. - return KeyedSubtree( - key: ValueKey(index), - child: manager.buildWidgetByID( - itemNode, - context, - settings: settings, - ), - ); - }), + child: Builder( + builder: (context) { + // This builder is important to pass a context that has + // the IndexedItemProvider. + return KeyedSubtree( + key: ValueKey(index), + child: manager.buildWidgetByID( + itemNode, + context, + settings: settings, + ), + ); + }, + ), ), ), ); @@ -337,10 +346,12 @@ class ListViewItemSeparator extends StatelessWidget { ); case ListItemSeparator.space: return SizedBox( - width: - scrollDirection.isHorizontal ? properties.separatorSpacing : null, - height: - scrollDirection.isVertical ? properties.separatorSpacing : null, + width: scrollDirection.isHorizontal + ? properties.separatorSpacing + : null, + height: scrollDirection.isVertical + ? properties.separatorSpacing + : null, ); } } diff --git a/lib/src/transformers/node_transformers/passive_loading_indicator_transformer.dart b/lib/src/transformers/node_transformers/passive_loading_indicator_transformer.dart index e43d86a3..33eede2d 100644 --- a/lib/src/transformers/node_transformers/passive_loading_indicator_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_loading_indicator_transformer.dart @@ -23,8 +23,9 @@ class PassiveLoadingIndicatorTransformer required double height, required double width, bool animate = true, - WidgetBuildSettings settings = - const WidgetBuildSettings(debugLabel: 'buildFromProps'), + WidgetBuildSettings settings = const WidgetBuildSettings( + debugLabel: 'buildFromProps', + ), }) { final node = LoadingIndicatorNode( id: '', @@ -43,8 +44,9 @@ class PassiveLoadingIndicatorTransformer double? width, ValueChanged? onChanged, bool animate = false, - WidgetBuildSettings settings = - const WidgetBuildSettings(debugLabel: 'buildPreview'), + WidgetBuildSettings settings = const WidgetBuildSettings( + debugLabel: 'buildPreview', + ), }) { final previewNode = LoadingIndicatorNode( properties: node?.properties ?? properties!, @@ -64,8 +66,9 @@ class PassiveLoadingIndicatorTransformer Widget buildFromNode( LoadingIndicatorNode node, { bool animate = true, - WidgetBuildSettings settings = - const WidgetBuildSettings(debugLabel: 'buildFromNode'), + WidgetBuildSettings settings = const WidgetBuildSettings( + debugLabel: 'buildFromNode', + ), }) { return PassiveLoadingIndicatorWidget( node: node, @@ -108,8 +111,8 @@ class PassiveLoadingIndicatorWidget extends StatelessWidget { @override Widget build(BuildContext context) { if (node.properties.isMaterial) { - final MaterialLoadingIndicatorProperties props = - node.properties.asMaterial(); + final MaterialLoadingIndicatorProperties props = node.properties + .asMaterial(); return Center( child: SizedBox.square( @@ -126,8 +129,8 @@ class PassiveLoadingIndicatorWidget extends StatelessWidget { ), ); } else if (node.properties.isCupertino) { - final CupertinoLoadingIndicatorProperties props = - node.properties.asCupertino(); + final CupertinoLoadingIndicatorProperties props = node.properties + .asCupertino(); return CupertinoActivityIndicator( radius: (node.basicBoxLocal.width ~/ 2).toDouble(), color: props.color.toFlutterColor(), diff --git a/lib/src/transformers/node_transformers/passive_navigation_bar_transformer.dart b/lib/src/transformers/node_transformers/passive_navigation_bar_transformer.dart index 4e178567..8a1090bc 100644 --- a/lib/src/transformers/node_transformers/passive_navigation_bar_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_navigation_bar_transformer.dart @@ -25,8 +25,11 @@ class PassiveNavigationBarTransformer void onChanged(NavigationBarNode node, BuildContext context, int index) { NodeStateProvider.setState(context, index); final item = node.properties.items[index]; - FunctionsRepository.triggerAction(context, TriggerType.changed, - reactions: item.reactions); + FunctionsRepository.triggerAction( + context, + TriggerType.changed, + reactions: item.reactions, + ); } Widget buildFromProps( @@ -34,8 +37,9 @@ class PassiveNavigationBarTransformer double width = 250, double height = kBottomNavigationBarHeight, required NavigationBarProperties props, - WidgetBuildSettings settings = - const WidgetBuildSettings(debugLabel: 'buildFromProps'), + WidgetBuildSettings settings = const WidgetBuildSettings( + debugLabel: 'buildFromProps', + ), }) { final node = NavigationBarNode( id: '', @@ -57,17 +61,26 @@ class PassiveNavigationBarTransformer double? width, int currentIndex = 0, ValueChanged? onChanged, - WidgetBuildSettings settings = - const WidgetBuildSettings(debugLabel: 'buildPreview'), + WidgetBuildSettings settings = const WidgetBuildSettings( + debugLabel: 'buildPreview', + ), }) { final previewNode = NavigationBarNode( properties: properties ?? node!.properties, id: '', name: 'NavigationBar', - basicBoxLocal: - NodeBox(0, 0, width ?? 300, height ?? kBottomNavigationBarHeight), - retainedOuterBoxLocal: - NodeBox(0, 0, width ?? 300, height ?? kBottomNavigationBarHeight), + basicBoxLocal: NodeBox( + 0, + 0, + width ?? 300, + height ?? kBottomNavigationBarHeight, + ), + retainedOuterBoxLocal: NodeBox( + 0, + 0, + width ?? 300, + height ?? kBottomNavigationBarHeight, + ), currentIndex: currentIndex, verticalFit: SizeFit.shrinkWrap, ); @@ -109,7 +122,8 @@ class _PassiveNavigationBarWidgetState @override void initState() { - currentIndex = context.getNodeValue(widget.node.id, 'currentIndex') ?? + currentIndex = + context.getNodeValue(widget.node.id, 'currentIndex') ?? widget.node.currentIndex; super.initState(); } @@ -152,10 +166,7 @@ class _PassiveNavigationBarWidgetState child: navBar, ); } - navBar = AdaptiveNodeBox( - node: widget.node, - child: navBar, - ); + navBar = AdaptiveNodeBox(node: widget.node, child: navBar); // BottomAppBars have a hard requirement for a scaffold, and scaffolds // have a hard requirement for a size. @@ -200,16 +211,18 @@ class _PassiveNavigationBarWidgetState labelTextStyle: WidgetStateProperty.resolveWith((states) { if (states.contains(WidgetState.selected)) { return TextUtils.retrieveTextStyleFromProp( - style.selectedLabelStyle); + style.selectedLabelStyle, + ); } return TextUtils.retrieveTextStyleFromProp( - style.unselectedLabelStyle); + style.unselectedLabelStyle, + ); }), ), child: MediaQuery( - data: MediaQuery.of(context).copyWith( - padding: widget.applyBottomPadding ? null : EdgeInsets.zero, - ), + data: MediaQuery.of( + context, + ).copyWith(padding: widget.applyBottomPadding ? null : EdgeInsets.zero), child: NavigationBar( selectedIndex: currentIndex, onDestinationSelected: onChanged, @@ -218,7 +231,7 @@ class _PassiveNavigationBarWidgetState height: node.basicBoxLocal.height, labelBehavior: style.labelBehavior.flutterLabelBehavior, destinations: [ - for (final itemModel in style.items) getM3Item(itemModel, style) + for (final itemModel in style.items) getM3Item(itemModel, style), ], ), ), @@ -233,10 +246,13 @@ class _PassiveNavigationBarWidgetState final M2NavigationBarProperties style = node.properties as M2NavigationBarProperties; - final Color? selectedLabelColor = - style.selectedLabelStyle.fills.firstOrNull?.toFlutterColor(); - final Color? unselectedLabelColor = - style.unselectedLabelStyle.fills.firstOrNull?.toFlutterColor(); + final Color? selectedLabelColor = style.selectedLabelStyle.fills.firstOrNull + ?.toFlutterColor(); + final Color? unselectedLabelColor = style + .unselectedLabelStyle + .fills + .firstOrNull + ?.toFlutterColor(); final IconThemeData selectedIconTheme = IconThemeData( color: style.selectedIconColor?.toFlutterColor(), @@ -249,9 +265,11 @@ class _PassiveNavigationBarWidgetState ); final TextStyle selectedLabelStyle = TextUtils.retrieveTextStyleFromProp( - style.selectedLabelStyle.copyWith(fills: [])); + style.selectedLabelStyle.copyWith(fills: []), + ); final unselectedLabelStyle = TextUtils.retrieveTextStyleFromProp( - style.unselectedLabelStyle.copyWith(fills: [])); + style.unselectedLabelStyle.copyWith(fills: []), + ); final double elevation = style.makeNotched ? 0 : style.elevation; @@ -276,9 +294,9 @@ class _PassiveNavigationBarWidgetState /// /// We don't want that. We disable it here. return MediaQuery( - data: MediaQuery.of(context).copyWith( - padding: widget.applyBottomPadding ? null : EdgeInsets.zero, - ), + data: MediaQuery.of( + context, + ).copyWith(padding: widget.applyBottomPadding ? null : EdgeInsets.zero), child: BottomNavigationBar( currentIndex: currentIndex, onTap: onChanged, @@ -296,7 +314,7 @@ class _PassiveNavigationBarWidgetState showUnselectedLabels: style.labelBehavior.showForUnselected(), iconSize: style.unselectedIconSize, items: [ - for (final itemModel in style.items) getM2Item(itemModel, style) + for (final itemModel in style.items) getM2Item(itemModel, style), ], ), ); @@ -314,12 +332,12 @@ class _PassiveNavigationBarWidgetState final Widget? selectedIconDj = itemModel.differSelectedIcon && itemModel.selectedIcon != null - ? retrieveNavBarItemIconWidget( - itemModel.selectedIcon!, - null, - // style.selectedIconSize, - ) - : null; + ? retrieveNavBarItemIconWidget( + itemModel.selectedIcon!, + null, + // style.selectedIconSize, + ) + : null; return BottomNavigationBarItem( icon: unselectedIconDj, activeIcon: selectedIconDj, @@ -328,9 +346,11 @@ class _PassiveNavigationBarWidgetState // bottom navigation bar when it is shifting type and color per item // is not provided. // Detailed explanation: https://codelessly.slack.com/archives/C018XK7431R/p1659077643469429 - backgroundColor: itemModel.backgroundColor?.toFlutterColor() ?? + backgroundColor: + itemModel.backgroundColor?.toFlutterColor() ?? style.backgroundColor?.toFlutterColor().orNullIf( - style.navigationBarType != M2NavigationBarType.shifting), + style.navigationBarType != M2NavigationBarType.shifting, + ), label: itemModel.label, tooltip: itemModel.tooltip.orNullIf(itemModel.label == itemModel.tooltip), ); @@ -348,12 +368,12 @@ class _PassiveNavigationBarWidgetState final Widget? selectedIconDj = itemModel.differSelectedIcon && itemModel.selectedIcon != null - ? retrieveNavBarItemIconWidget( - itemModel.selectedIcon!, - null, - // style.selectedIconSize, - ) - : null; + ? retrieveNavBarItemIconWidget( + itemModel.selectedIcon!, + null, + // style.selectedIconSize, + ) + : null; return NavigationDestination( icon: unselectedIcon, selectedIcon: selectedIconDj, diff --git a/lib/src/transformers/node_transformers/passive_page_view_transformer.dart b/lib/src/transformers/node_transformers/passive_page_view_transformer.dart index 5d071c9d..03a34edb 100644 --- a/lib/src/transformers/node_transformers/passive_page_view_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_page_view_transformer.dart @@ -11,7 +11,10 @@ class PassivePageViewTransformer extends NodeWidgetTransformer { @override Widget buildWidget( - PageViewNode node, BuildContext context, WidgetBuildSettings settings) { + PageViewNode node, + BuildContext context, + WidgetBuildSettings settings, + ) { return PassivePageViewWidget( node: node, manager: manager, @@ -21,14 +24,26 @@ class PassivePageViewTransformer extends NodeWidgetTransformer { } void onPageChanged(BuildContext context, int index, PageViewNode node) { - FunctionsRepository.setNodeValue(context, - node: node, property: 'indexValue', value: index); + FunctionsRepository.setNodeValue( + context, + node: node, + property: 'indexValue', + value: index, + ); - FunctionsRepository.setPropertyVariable(context, - node: node, property: 'indexValue', value: '$index'); + FunctionsRepository.setPropertyVariable( + context, + node: node, + property: 'indexValue', + value: '$index', + ); - FunctionsRepository.triggerAction(context, TriggerType.changed, - node: node, value: index); + FunctionsRepository.triggerAction( + context, + TriggerType.changed, + node: node, + value: index, + ); } } @@ -81,10 +96,10 @@ class _PassivePageViewWidgetState extends State { if (widget.node.variables['indexValue'] != null) { final int? currentPropertyValue = PropertyValueDelegate.getPropertyValue( - widget.node, - 'indexValue', - scopedValues: scopedValues, - ); + widget.node, + 'indexValue', + scopedValues: scopedValues, + ); if (currentPropertyValue != null && controller.page?.round() != currentPropertyValue) { _jumpToPage(currentPropertyValue); @@ -128,14 +143,14 @@ class _PassivePageViewWidgetState extends State { child: DraggableScrollConfiguration( child: PageView.builder( itemCount: itemCount, - physics: widget.node.physics - .flutterScrollPhysics(widget.node.shouldAlwaysScroll), + physics: widget.node.physics.flutterScrollPhysics( + widget.node.shouldAlwaysScroll, + ), scrollDirection: widget.node.scrollDirection.flutterAxis, reverse: widget.node.reverse, clipBehavior: widget.node.clipsContent ? Clip.hardEdge : Clip.none, padEnds: widget.node.properties.padEnds, - allowImplicitScrolling: - widget.node.properties.allowImplicitScrolling, + allowImplicitScrolling: widget.node.properties.allowImplicitScrolling, pageSnapping: widget.node.properties.pageSnapping, controller: controller, onPageChanged: (index) => widget.onPageChanged?.call(context, index), diff --git a/lib/src/transformers/node_transformers/passive_progressbar_transformer.dart b/lib/src/transformers/node_transformers/passive_progressbar_transformer.dart index 7cdc3605..e0ede75e 100644 --- a/lib/src/transformers/node_transformers/passive_progressbar_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_progressbar_transformer.dart @@ -42,17 +42,26 @@ class PassiveProgressBarTransformer double? currentValue, ValueChanged? onChanged, bool animate = false, - WidgetBuildSettings settings = - const WidgetBuildSettings(debugLabel: 'buildPreview'), + WidgetBuildSettings settings = const WidgetBuildSettings( + debugLabel: 'buildPreview', + ), }) { final previewNode = ProgressBarNode( properties: properties ?? node?.properties ?? ProgressBarProperties(), id: '', name: 'ProgressBar', - basicBoxLocal: NodeBox(0, 0, width ?? kProgressBarDefaultWidth, - height ?? kProgressBarDefaultHeight), - retainedOuterBoxLocal: NodeBox(0, 0, width ?? kProgressBarDefaultWidth, - height ?? kProgressBarDefaultHeight), + basicBoxLocal: NodeBox( + 0, + 0, + width ?? kProgressBarDefaultWidth, + height ?? kProgressBarDefaultHeight, + ), + retainedOuterBoxLocal: NodeBox( + 0, + 0, + width ?? kProgressBarDefaultWidth, + height ?? kProgressBarDefaultHeight, + ), currentValue: currentValue ?? node?.currentValue ?? 0, ); return PassiveProgressBarWidget( @@ -75,11 +84,19 @@ class PassiveProgressBarTransformer } void onChanged(BuildContext context, ProgressBarNode node, double value) { - FunctionsRepository.setPropertyValue(context, - node: node, property: 'currentValue', value: value); + FunctionsRepository.setPropertyValue( + context, + node: node, + property: 'currentValue', + value: value, + ); FunctionsRepository.triggerAction( - context, node: node, TriggerType.changed, value: value); + context, + node: node, + TriggerType.changed, + value: value, + ); } } @@ -105,7 +122,8 @@ class PassiveProgressBarWidget extends StatelessWidget { context, variablesOverrides: variablesOverrides, ); - final double currentValue = PropertyValueDelegate.getPropertyValue( + final double currentValue = + PropertyValueDelegate.getPropertyValue( node, 'currentValue', scopedValues: scopedValues, @@ -114,11 +132,11 @@ class PassiveProgressBarWidget extends StatelessWidget { final Color progressColor = PropertyValueDelegate.getPropertyValue( - node, - 'progressColor', - scopedValues: scopedValues, - )?.toFlutterColor() ?? - node.properties.progressColor.toFlutterColor(); + node, + 'progressColor', + scopedValues: scopedValues, + )?.toFlutterColor() ?? + node.properties.progressColor.toFlutterColor(); return AdaptiveNodeBox( node: node, diff --git a/lib/src/transformers/node_transformers/passive_rectangle_transformer.dart b/lib/src/transformers/node_transformers/passive_rectangle_transformer.dart index 39c6f5e2..cc778d34 100644 --- a/lib/src/transformers/node_transformers/passive_rectangle_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_rectangle_transformer.dart @@ -19,16 +19,15 @@ class PassiveRectangleTransformer extends NodeWidgetTransformer { final List children = []; for (String childID in node.childrenOrEmpty) { - final Widget builtWidget = - manager.buildWidgetByID(childID, context, settings: settings); + final Widget builtWidget = manager.buildWidgetByID( + childID, + context, + settings: settings, + ); children.add(builtWidget); } - return buildRectangle( - node, - settings: settings, - children: children, - ); + return buildRectangle(node, settings: settings, children: children); } Widget buildRectangle( @@ -81,27 +80,25 @@ class PassiveRectangleWidget extends StatelessWidget { /// TODO Birju / Saad. Why does the commented out parent code below break published layouts only? /// Saad's note: The below code looks like AdaptiveNodeBox - final double? width = (node.horizontalFit == SizeFit.shrinkWrap || - isPlaceholder) + final double? width = + (node.horizontalFit == SizeFit.shrinkWrap || isPlaceholder) ? null : (node.horizontalFit == - SizeFit - .expanded /* && - (parent is! CanvasNode || parent.properties.bodyId != node.id)*/ - ) - ? double.infinity - : node.basicBoxLocal.width; - - final double? height = (node.verticalFit == SizeFit.shrinkWrap || - isPlaceholder) + SizeFit + .expanded /* && + (parent is! CanvasNode || parent.properties.bodyId != node.id)*/ ) + ? double.infinity + : node.basicBoxLocal.width; + + final double? height = + (node.verticalFit == SizeFit.shrinkWrap || isPlaceholder) ? null : (node.verticalFit == - SizeFit - .expanded /*&& - (parent is! CanvasNode || parent.properties.bodyId != node.id)*/ - ) - ? double.infinity - : node.basicBoxLocal.height; + SizeFit + .expanded /*&& + (parent is! CanvasNode || parent.properties.bodyId != node.id)*/ ) + ? double.infinity + : node.basicBoxLocal.height; Widget? portalWidget; if (node case PortalMixin portal) { @@ -143,10 +140,7 @@ class PassiveRectangleWidget extends StatelessWidget { node, wrapWithPaddingAndScroll( node, - [ - ...children, - ?portalWidget, - ], + [...children, ?portalWidget], stackAlignment: stackAlignment, applyPadding: applyPadding, ), @@ -154,10 +148,11 @@ class PassiveRectangleWidget extends StatelessWidget { ], ); - bool hasComplexRendering = node is GeometryMixin && + bool hasComplexRendering = + node is GeometryMixin && (node as GeometryMixin).fills.any( - (PaintModel fill) => fill.blendMode != BlendModeC.srcOver, - ); + (PaintModel fill) => fill.blendMode != BlendModeC.srcOver, + ); if (hasComplexRendering) { child = BlendMask(child: child); @@ -190,13 +185,11 @@ List wrapWithInkWell( if (inkWell == null) return children; - final List onClickReactions = (node as ReactionMixin) - .reactions + final List onClickReactions = (node as ReactionMixin).reactions .where((reaction) => reaction.trigger.type == TriggerType.click) .toList(); - final List onLongPressReactions = (node as ReactionMixin) - .reactions + final List onLongPressReactions = (node as ReactionMixin).reactions .where((reaction) => reaction.trigger.type == TriggerType.longPress) .toList(); @@ -206,17 +199,17 @@ List wrapWithInkWell( onTap: onClickReactions.isEmpty ? null : () => FunctionsRepository.triggerAction( - context, - TriggerType.click, - reactions: onClickReactions, - ), + context, + TriggerType.click, + reactions: onClickReactions, + ), onLongPress: onLongPressReactions.isEmpty ? null : () => FunctionsRepository.triggerAction( - context, - TriggerType.longPress, - reactions: onLongPressReactions, - ), + context, + TriggerType.longPress, + reactions: onLongPressReactions, + ), borderRadius: getBorderRadius(node), overlayColor: inkWell.overlayColor != null ? WidgetStatePropertyAll( @@ -227,9 +220,7 @@ List wrapWithInkWell( highlightColor: inkWell.highlightColor?.toFlutterColor(), hoverColor: inkWell.hoverColor?.toFlutterColor(), focusColor: inkWell.focusColor?.toFlutterColor(), - child: Stack( - children: children, - ), + child: Stack(children: children), ), ); @@ -239,10 +230,7 @@ List wrapWithInkWell( class PortalPreviewWidget extends StatelessWidget { final BaseNode node; - const PortalPreviewWidget({ - super.key, - required this.node, - }); + const PortalPreviewWidget({super.key, required this.node}); @override Widget build(BuildContext context) { @@ -330,32 +318,34 @@ List retrieveBoxShadow(BaseNode node, ScopedValues scopedValues) { if (node is! DefaultShapeNode) return []; return node.effects .where((effect) => effect.type == EffectType.dropShadow && effect.visible) - .map( - (effect) { - final ColorRGBA? color = - PropertyValueDelegate.getPropertyValue( - node, - 'shadow-color-${effect.id}', - scopedValues: scopedValues, - ) ?? - effect.color; - return BoxShadow( - spreadRadius: effect.spread!, - offset: - Offset(effect.offset!.x.toDouble(), effect.offset!.y.toDouble()), - blurRadius: effect.radius, - color: color!.toFlutterColor(), - ); - }, - ).toList(); + .map((effect) { + final ColorRGBA? color = + PropertyValueDelegate.getPropertyValue( + node, + 'shadow-color-${effect.id}', + scopedValues: scopedValues, + ) ?? + effect.color; + return BoxShadow( + spreadRadius: effect.spread!, + offset: Offset( + effect.offset!.x.toDouble(), + effect.offset!.y.toDouble(), + ), + blurRadius: effect.radius, + color: color!.toFlutterColor(), + ); + }) + .toList(); } BorderRadius? getBorderRadius(BaseNode node) { if (node is CornerMixin) { if (node.cornerRadius.linked && node.cornerRadius.type == RadiusType.elliptical) { - return BorderRadius.all(Radius.elliptical( - node.basicBoxLocal.width, node.basicBoxLocal.height)); + return BorderRadius.all( + Radius.elliptical(node.basicBoxLocal.width, node.basicBoxLocal.height), + ); } return node.cornerRadius.borderRadius; } else { @@ -397,12 +387,13 @@ void getLinearGradientAlignment(double rotationDeg) {} Gradient? retrieveGradient(PaintModel paint) { try { - final double rotation = - decomposeRelativeTransform(paint.gradientTransform).rotation; + final double rotation = decomposeRelativeTransform(paint.gradientTransform) + .rotation; // We sort the colors to mimic the way Figma changes the order. - final gradientStops = paint.gradientStops! - .sorted((g1, g2) => g1.position.compareTo(g2.position)); + final gradientStops = paint.gradientStops!.sorted( + (g1, g2) => g1.position.compareTo(g2.position), + ); final radians = rotation * pi / 180; if (paint.type == PaintType.gradientLinear) { @@ -496,8 +487,17 @@ List applyGradientRotation(List? t1, double angle) { final cosA = cos(rads); final sinA = sin(rads); - final vec_math.Matrix3 matrix = - vec_math.Matrix3(cosA, sinA, tx, -sinA, cosA, ty, 0, 0, 1); + final vec_math.Matrix3 matrix = vec_math.Matrix3( + cosA, + sinA, + tx, + -sinA, + cosA, + ty, + 0, + 0, + 1, + ); final List list = List.filled(9, 0); matrix.copyIntoArray(list); @@ -638,11 +638,7 @@ List buildFills( } lastBlendTree.clear(); - raster.add(wrapFillWithPositioned( - node: node, - fill: fill, - paint: model, - )); + raster.add(wrapFillWithPositioned(node: node, fill: fill, paint: model)); } return raster.reversed.toList(); @@ -652,19 +648,18 @@ Widget wrapFillWithPositioned({ required GeometryMixin node, required Widget fill, required PaintModel paint, -}) => - switch (paint.type) { - PaintType.solid => Positioned.fill(child: fill), - PaintType.gradientLinear || - PaintType.gradientRadial || - PaintType.gradientAngular || - PaintType.gradientDiamond => - Positioned.fill(child: fill), - PaintType.image => node.isHorizontalWrap || node.isVerticalWrap - ? Positioned(child: fill) - : Positioned.fill(child: fill), - PaintType.emoji => fill, - }; +}) => switch (paint.type) { + PaintType.solid => Positioned.fill(child: fill), + PaintType.gradientLinear || + PaintType.gradientRadial || + PaintType.gradientAngular || + PaintType.gradientDiamond => Positioned.fill(child: fill), + PaintType.image => + node.isHorizontalWrap || node.isVerticalWrap + ? Positioned(child: fill) + : Positioned.fill(child: fill), + PaintType.emoji => fill, +}; Widget buildFill( GeometryMixin node, { @@ -713,7 +708,8 @@ Widget buildFill( // Substitute URL value from [CodelesslyContext]'s [data] map if // [imageURL] represents a JSON path. String? imageURL = paint.croppedImageURL ?? paint.downloadUrl!; - final imageURLValue = PropertyValueDelegate.getPropertyValue( + final imageURLValue = + PropertyValueDelegate.getPropertyValue( node, 'fill-image-${paint.id}', scopedValues: scopedValues, @@ -749,13 +745,13 @@ Widget buildFill( width: (node.horizontalFit == SizeFit.shrinkWrap) ? null : (node.horizontalFit == SizeFit.expanded) - ? double.infinity - : node.basicBoxLocal.width, + ? double.infinity + : node.basicBoxLocal.width, height: (node.verticalFit == SizeFit.shrinkWrap) ? null : (node.verticalFit == SizeFit.expanded) - ? double.infinity - : node.basicBoxLocal.height, + ? double.infinity + : node.basicBoxLocal.height, paint: paint, node: node, bytes: bytes, @@ -765,16 +761,10 @@ Widget buildFill( } if (modifiedOpacity != 1) { - child = Opacity( - opacity: modifiedOpacity, - child: child, - ); + child = Opacity(opacity: modifiedOpacity, child: child); } if (imageRotation != null) { - child = Transform.rotate( - angle: imageRotation, - child: child, - ); + child = Transform.rotate(angle: imageRotation, child: child); } return child; case PaintType.emoji: @@ -796,21 +786,20 @@ class RelativeTransform { }); const RelativeTransform.empty() - : translation = const [0, 0], - rotation = 0, - scale = const [0, 0], - skew = const [0, 0]; + : translation = const [0, 0], + rotation = 0, + scale = const [0, 0], + skew = const [0, 0]; RelativeTransform copyWith({ List? translation, double? rotation, List? scale, List? skew, - }) => - RelativeTransform( - translation: translation ?? this.translation, - rotation: rotation ?? this.rotation, - scale: scale ?? this.scale, - skew: skew ?? this.skew, - ); + }) => RelativeTransform( + translation: translation ?? this.translation, + rotation: rotation ?? this.rotation, + scale: scale ?? this.scale, + skew: skew ?? this.skew, + ); } diff --git a/lib/src/transformers/node_transformers/passive_row_column_transformer.dart b/lib/src/transformers/node_transformers/passive_row_column_transformer.dart index 0ec42cc5..affaeccd 100644 --- a/lib/src/transformers/node_transformers/passive_row_column_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_row_column_transformer.dart @@ -85,11 +85,7 @@ class PassiveRowColumnTransformer extends NodeWidgetTransformer { if (fixWidth == null && fixHeight == null) return res; - return SizedBox( - width: fixWidth, - height: fixHeight, - child: res, - ); + return SizedBox(width: fixWidth, height: fixHeight, child: res); } else { Widget res = Column( mainAxisAlignment: rowColumnNode.mainAxisAlignment.flutterAxis, @@ -122,11 +118,7 @@ class PassiveRowColumnTransformer extends NodeWidgetTransformer { if (fixWidth == null && fixHeight == null) return res; - return SizedBox( - width: fixWidth, - height: fixHeight, - child: res, - ); + return SizedBox(width: fixWidth, height: fixHeight, child: res); } } @@ -148,10 +140,11 @@ class PassiveRowColumnTransformer extends NodeWidgetTransformer { final AxisC mainAxis = ((parentRowColumn as RowColumnMixin).rowColumnType == RowColumnType.row - ? AxisC.horizontal - : AxisC.vertical); - final SizeFit mainAxisFit = - (mainAxis == AxisC.horizontal ? node.horizontalFit : node.verticalFit); + ? AxisC.horizontal + : AxisC.vertical); + final SizeFit mainAxisFit = (mainAxis == AxisC.horizontal + ? node.horizontalFit + : node.verticalFit); // final SizeFit crossAxisFit = // (mainAxis == AxisC.horizontal ? node.verticalFit : node.horizontalFit); @@ -232,10 +225,7 @@ class PassiveRowColumnTransformer extends NodeWidgetTransformer { // We can do this because children on cross axis get restricted bounds. if (mainAxisFit.isFlex) { if (mainAxisFit == SizeFit.flexible) { - childWidget = Flexible( - flex: flex, - child: childWidget, - ); + childWidget = Flexible(flex: flex, child: childWidget); } else { if (mainAxisMaxConstraint != null) { // When there's constraints on the main axis, we need to use @@ -269,13 +259,12 @@ class PassiveRowColumnTransformer extends NodeWidgetTransformer { BaseNode node, BuildContext context, WidgetBuildSettings settings, - ) => - PassiveRowColumnWidget( - node: node, - children: node.childrenOrEmpty.map(getNode).toList(), - manager: manager, - settings: settings, - ); + ) => PassiveRowColumnWidget( + node: node, + children: node.childrenOrEmpty.map(getNode).toList(), + manager: manager, + settings: settings, + ); } class PassiveRowColumnWidget extends StatelessWidget { @@ -295,8 +284,11 @@ class PassiveRowColumnWidget extends StatelessWidget { @override Widget build(BuildContext context) { final List widgetChildren = children.map((child) { - final Widget builtWidget = - manager.buildWidgetByID(child.id, context, settings: settings); + final Widget builtWidget = manager.buildWidgetByID( + child.id, + context, + settings: settings, + ); return PassiveRowColumnTransformer.wrapChildWithSizeFits( child, @@ -308,20 +300,21 @@ class PassiveRowColumnWidget extends StatelessWidget { final Widget child = (node is DefaultShapeNode) ? manager.getTransformer().buildRectangle( - node as DefaultShapeNode, - applyPadding: node is! ScrollableMixin || - !(node as ScrollableMixin).isScrollable, - children: [ - PassiveRowColumnTransformer.buildRowColumnWidget( - node, - childrenWidgets: widgetChildren, - childrenNodes: children, - withScroll: true, - getNode: manager.getNode, - ) - ], - settings: settings, - ) + node as DefaultShapeNode, + applyPadding: + node is! ScrollableMixin || + !(node as ScrollableMixin).isScrollable, + children: [ + PassiveRowColumnTransformer.buildRowColumnWidget( + node, + childrenWidgets: widgetChildren, + childrenNodes: children, + withScroll: true, + getNode: manager.getNode, + ), + ], + settings: settings, + ) : PassiveRowColumnTransformer.buildRowColumnWidget( node, childrenWidgets: widgetChildren, diff --git a/lib/src/transformers/node_transformers/passive_slider_transformer.dart b/lib/src/transformers/node_transformers/passive_slider_transformer.dart index 7a6dd704..a7ecc97f 100644 --- a/lib/src/transformers/node_transformers/passive_slider_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_slider_transformer.dart @@ -14,15 +14,9 @@ class PassiveSliderTransformer extends NodeWidgetTransformer { WidgetBuildSettings settings, ) { if (settings.isPreview) { - return PreviewSliderWidget( - node: node, - settings: settings, - ); + return PreviewSliderWidget(node: node, settings: settings); } else { - return PassiveSliderWidget( - node: node, - settings: settings, - ); + return PassiveSliderWidget(node: node, settings: settings); } } } @@ -93,7 +87,8 @@ class PassiveSliderWidget extends StatelessWidget { context, variablesOverrides: variablesOverrides, ); - final double value = PropertyValueDelegate.getPropertyValue( + final double value = + PropertyValueDelegate.getPropertyValue( node, 'value', scopedValues: scopedValues, @@ -135,8 +130,9 @@ class TransformerSlider extends StatelessWidget { SliderTrackShapeEnum.gapped => const GappedSliderTrackShape(), }; - final SliderComponentShape valueIndicatorShape = - switch (node.properties.valueIndicatorShape) { + final SliderComponentShape valueIndicatorShape = switch (node + .properties + .valueIndicatorShape) { SliderValueIndicatorShape.rectangle => const RectangularSliderValueIndicatorShape(), SliderValueIndicatorShape.paddle => @@ -146,11 +142,13 @@ class TransformerSlider extends StatelessWidget { const RoundedRectSliderValueIndicatorShape(), }; - final ShowValueIndicator showValueIndicator = - switch (node.properties.showValueIndicator) { - null => node.properties.showLabel - ? ShowValueIndicator.onDrag - : ShowValueIndicator.never, + final ShowValueIndicator showValueIndicator = switch (node + .properties + .showValueIndicator) { + null => + node.properties.showLabel + ? ShowValueIndicator.onDrag + : ShowValueIndicator.never, ShowValueIndicatorC.onlyForDiscrete => ShowValueIndicator.onlyForDiscrete, ShowValueIndicatorC.onlyForContinuous => ShowValueIndicator.onlyForContinuous, @@ -158,8 +156,7 @@ class TransformerSlider extends StatelessWidget { ShowValueIndicatorC.alwaysVisible => ShowValueIndicator.alwaysVisible, ShowValueIndicatorC.never => ShowValueIndicator.never, }; - final bool showsLabel = - showValueIndicator != ShowValueIndicator.never; + final bool showsLabel = showValueIndicator != ShowValueIndicator.never; return SizedBox( width: node.basicBoxLocal.width, @@ -171,12 +168,13 @@ class TransformerSlider extends StatelessWidget { ? node.properties.overlayColor.toFlutterColor() : Colors.transparent, activeTrackColor: node.properties.activeTrackColor.toFlutterColor(), - inactiveTrackColor: - node.properties.inactiveTrackColor.toFlutterColor(), + inactiveTrackColor: node.properties.inactiveTrackColor + .toFlutterColor(), thumbColor: node.properties.thumbColor.toFlutterColor(), thumbShape: RoundSliderThumbShape( - enabledThumbRadius: - node.properties.showThumb ? node.properties.thumbRadius : 0, + enabledThumbRadius: node.properties.showThumb + ? node.properties.thumbRadius + : 0, ), trackShape: trackShape, // GappedSliderTrackShape requires a gap; 6 is the Material 3 @@ -190,22 +188,23 @@ class TransformerSlider extends StatelessWidget { ) : null, showValueIndicator: showValueIndicator, - secondaryActiveTrackColor: - node.properties.secondaryActiveTrackColor?.toFlutterColor(), - activeTickMarkColor: - node.properties.activeTickMarkColor.toFlutterColor(), - inactiveTickMarkColor: - node.properties.inactiveTickMarkColor.toFlutterColor(), - valueIndicatorColor: - node.properties.valueIndicatorColor.toFlutterColor(), + secondaryActiveTrackColor: node.properties.secondaryActiveTrackColor + ?.toFlutterColor(), + activeTickMarkColor: node.properties.activeTickMarkColor + .toFlutterColor(), + inactiveTickMarkColor: node.properties.inactiveTickMarkColor + .toFlutterColor(), + valueIndicatorColor: node.properties.valueIndicatorColor + .toFlutterColor(), valueIndicatorTextStyle: TextStyle( color: node.properties.valueIndicatorTextColor.toFlutterColor(), fontSize: node.properties.valueIndicatorFontSize, ), valueIndicatorShape: valueIndicatorShape, overlayShape: RoundSliderOverlayShape( - overlayRadius: - node.properties.showThumb ? node.properties.overlayRadius : 0, + overlayRadius: node.properties.showThumb + ? node.properties.overlayRadius + : 0, ), ), child: Slider( @@ -214,11 +213,14 @@ class TransformerSlider extends StatelessWidget { autofocus: node.properties.autofocus, min: node.properties.min, max: node.properties.max, - divisions: - node.properties.isDiscrete ? node.properties.divisions : null, + divisions: node.properties.isDiscrete + ? node.properties.divisions + : null, label: showsLabel ? getSliderLabel(node, value) : null, - secondaryTrackValue: node.properties.secondaryTrackValue - ?.clamp(node.properties.min, node.properties.max), + secondaryTrackValue: node.properties.secondaryTrackValue?.clamp( + node.properties.min, + node.properties.max, + ), padding: node.properties.padding?.flutterEdgeInsets, ), ), diff --git a/lib/src/transformers/node_transformers/passive_spacer_transformer.dart b/lib/src/transformers/node_transformers/passive_spacer_transformer.dart index 800d39be..ed514c98 100644 --- a/lib/src/transformers/node_transformers/passive_spacer_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_spacer_transformer.dart @@ -10,16 +10,15 @@ class PassiveSpacerTransformer extends NodeWidgetTransformer { final BaseNode parentNode = getNode(node.parentID); if (parentNode is! RowColumnMixin) { - return AdaptiveNodeBox( - node: node, - ); + return AdaptiveNodeBox(node: node); } final AxisC mainAxis = parentNode.mainAxis; final SizeFit horizontalFit = node.horizontalFit; final SizeFit verticalFit = node.verticalFit; - final SizeFit mainFit = - (mainAxis == AxisC.horizontal ? horizontalFit : verticalFit); + final SizeFit mainFit = (mainAxis == AxisC.horizontal + ? horizontalFit + : verticalFit); final Widget spacerWidget; if (mainFit.isFlex) { diff --git a/lib/src/transformers/node_transformers/passive_stack_transformer.dart b/lib/src/transformers/node_transformers/passive_stack_transformer.dart index 545fe979..9122c460 100644 --- a/lib/src/transformers/node_transformers/passive_stack_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_stack_transformer.dart @@ -17,15 +17,18 @@ class PassiveStackTransformer extends NodeWidgetTransformer { required AlignmentModel commonAlignment, required WidgetBuildSettings settings, }) { - assert(childWidget != null || manager != null, - 'Either childWidget or manager must be provided.'); + assert( + childWidget != null || manager != null, + 'Either childWidget or manager must be provided.', + ); - Widget child = childWidget ?? + Widget child = + childWidget ?? manager!.buildWidgetFromNode(node, context, settings: settings); final bool potentiallyPositionable = node.alignment == AlignmentModel.none || - (parent.isOneOrBothWrap && node.alignment != commonAlignment); + (parent.isOneOrBothWrap && node.alignment != commonAlignment); if (potentiallyPositionable) { // You cannot use the Align widget when inside a shrink-wrapping Stack. @@ -46,14 +49,14 @@ class PassiveStackTransformer extends NodeWidgetTransformer { : null, right: align.x >= 0 ? (parent.innerBoxLocal.width - node.outerBoxLocal.right) + - parent.innerBoxLocal.edgeRight + parent.innerBoxLocal.edgeRight : null, top: align.y <= 0 ? node.outerBoxLocal.top - parent.innerBoxLocal.edgeTop : null, bottom: align.y >= 0 ? (parent.innerBoxLocal.height - node.outerBoxLocal.bottom) + - parent.innerBoxLocal.edgeBottom + parent.innerBoxLocal.edgeBottom : null, child: child, ); @@ -100,29 +103,31 @@ class PassiveStackTransformer extends NodeWidgetTransformer { final double? left = horizontallyExpands ? 0 : node.edgePins.left != null - ? node.outerBoxLocal.x - parent.innerBoxGlobal.edgeLeft - : null; + ? node.outerBoxLocal.x - parent.innerBoxGlobal.edgeLeft + : null; final double? right = horizontallyExpands ? 0 : node.edgePins.right != null - ? node.edgePins.right! - parent.innerBoxGlobal.edgeRight - : null; + ? node.edgePins.right! - parent.innerBoxGlobal.edgeRight + : null; final double? top = verticallyExpands ? 0 : node.edgePins.top != null - ? node.outerBoxLocal.y - parent.innerBoxGlobal.edgeTop - : null; + ? node.outerBoxLocal.y - parent.innerBoxGlobal.edgeTop + : null; final double? bottom = verticallyExpands ? 0 : node.edgePins.bottom != null - ? node.edgePins.bottom! - parent.innerBoxGlobal.edgeBottom - : null; - final double? width = horizontallyExpands || + ? node.edgePins.bottom! - parent.innerBoxGlobal.edgeBottom + : null; + final double? width = + horizontallyExpands || node.isHorizontalWrap || node.edgePins.isHorizontallyExpanded ? null : node.outerBoxLocal.width; - final double? height = verticallyExpands || + final double? height = + verticallyExpands || node.isVerticalWrap || node.edgePins.isVerticallyExpanded ? null @@ -182,8 +187,7 @@ class PassiveStackTransformer extends NodeWidgetTransformer { BaseNode node, BuildContext context, WidgetBuildSettings settings, - ) => - buildWidgetForChildren(node, context, settings: settings); + ) => buildWidgetForChildren(node, context, settings: settings); Widget buildWidgetForChildren( BaseNode node, @@ -205,10 +209,13 @@ class PassiveStackTransformer extends NodeWidgetTransformer { for (final String childId in node.children) getNode(childId), ]; - final AlignmentModel commonAlignment = - retrieveCommonStackAlignment(node, children); - final Set potentiallyPositionableChildren = - children.where((child) { + final AlignmentModel commonAlignment = retrieveCommonStackAlignment( + node, + children, + ); + final Set potentiallyPositionableChildren = children.where(( + child, + ) { return child.alignment == AlignmentModel.none || (node.isOneOrBothWrap && child.alignment != commonAlignment); }).toSet(); @@ -236,13 +243,14 @@ class PassiveStackTransformer extends NodeWidgetTransformer { childrenWidgets.add(child); } - Widget stack = - manager.getTransformer().buildRectangle( - node, - stackAlignment: commonAlignment, - children: childrenWidgets, - settings: settings, - ); + Widget stack = manager + .getTransformer() + .buildRectangle( + node, + stackAlignment: commonAlignment, + children: childrenWidgets, + settings: settings, + ); // // This makes sure that children stay positioned correctly even if the stack // // is shrink wrapping. It makes it so the stack sizes itself to its tallest diff --git a/lib/src/transformers/node_transformers/passive_switch_transformer.dart b/lib/src/transformers/node_transformers/passive_switch_transformer.dart index 7644fda8..25d2c4c8 100644 --- a/lib/src/transformers/node_transformers/passive_switch_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_switch_transformer.dart @@ -14,15 +14,9 @@ class PassiveSwitchTransformer extends NodeWidgetTransformer { WidgetBuildSettings settings, ) { if (settings.isPreview) { - return PreviewSwitchWidget( - node: node, - settings: settings, - ); + return PreviewSwitchWidget(node: node, settings: settings); } else { - return PassiveSwitchWidget( - node: node, - settings: settings, - ); + return PassiveSwitchWidget(node: node, settings: settings); } } } @@ -72,10 +66,18 @@ class PassiveSwitchWidget extends StatelessWidget { void onChanged(BuildContext context, bool internalValue) { NodeStateProvider.setState(context, internalValue); - FunctionsRepository.setPropertyValue(context, - node: node, property: 'value', value: internalValue); + FunctionsRepository.setPropertyValue( + context, + node: node, + property: 'value', + value: internalValue, + ); FunctionsRepository.triggerAction( - context, node: node, TriggerType.changed, value: internalValue); + context, + node: node, + TriggerType.changed, + value: internalValue, + ); } @override @@ -84,7 +86,8 @@ class PassiveSwitchWidget extends StatelessWidget { context, variablesOverrides: variablesOverrides, ); - final bool value = PropertyValueDelegate.getPropertyValue( + final bool value = + PropertyValueDelegate.getPropertyValue( node, 'value', scopedValues: scopedValues, @@ -132,11 +135,11 @@ class TransformerSwitch extends StatelessWidget { onChanged: (value) => onChanged?.call(context, value), autofocus: node.properties.autofocus, activeTrackColor: node.properties.activeTrackColor.toFlutterColor(), - inactiveTrackColor: - node.properties.inactiveTrackColor.toFlutterColor(), + inactiveTrackColor: node.properties.inactiveTrackColor + .toFlutterColor(), activeThumbColor: node.properties.activeThumbColor.toFlutterColor(), - inactiveThumbColor: - node.properties.inactiveThumbColor.toFlutterColor(), + inactiveThumbColor: node.properties.inactiveThumbColor + .toFlutterColor(), hoverColor: node.properties.hoverColor.toFlutterColor(), focusColor: node.properties.focusColor.toFlutterColor(), splashRadius: node.properties.splashRadius, @@ -146,8 +149,9 @@ class TransformerSwitch extends StatelessWidget { } return node.properties.inactiveTrackBorderColor?.toFlutterColor(); }), - trackOutlineWidth: - WidgetStateProperty.all(node.properties.trackOutlineWidth), + trackOutlineWidth: WidgetStateProperty.all( + node.properties.trackOutlineWidth, + ), ), ), ), diff --git a/lib/src/transformers/node_transformers/passive_tab_bar_transformer.dart b/lib/src/transformers/node_transformers/passive_tab_bar_transformer.dart index f806b1ae..0c971251 100644 --- a/lib/src/transformers/node_transformers/passive_tab_bar_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_tab_bar_transformer.dart @@ -50,17 +50,22 @@ class PassiveTabBarTransformer extends NodeWidgetTransformer { id: '', name: 'tabBar', basicBoxLocal: NodeBox( - 0, 0, width ?? 32, height ?? node?.basicBoxLocal.height ?? 56), + 0, + 0, + width ?? 32, + height ?? node?.basicBoxLocal.height ?? 56, + ), retainedOuterBoxLocal: NodeBox( - 0, 0, width ?? 32, height ?? node?.basicBoxLocal.height ?? 56), + 0, + 0, + width ?? 32, + height ?? node?.basicBoxLocal.height ?? 56, + ), initialIndex: node?.initialIndex ?? 0, isScrollable: node?.isScrollable ?? false, physics: node?.physics ?? ScrollPhysicsC.platformDependent, ); - return PassiveTabBarWidget( - node: previewNode, - settings: settings, - ); + return PassiveTabBarWidget(node: previewNode, settings: settings); } Widget buildFromNode( @@ -78,8 +83,12 @@ class PassiveTabBarTransformer extends NodeWidgetTransformer { void onChanged(BuildContext context, TabBarNode node, int index) { NodeStateProvider.setState(context, index); final tab = node.properties.tabs[index]; - FunctionsRepository.triggerAction(context, TriggerType.changed, - reactions: tab.reactions, value: index); + FunctionsRepository.triggerAction( + context, + TriggerType.changed, + reactions: tab.reactions, + value: index, + ); } } @@ -143,17 +152,19 @@ class _PassiveTabBarWidgetState extends State final ScopedValues scopedValues = ScopedValues.of(context); final width = widget.node.isHorizontalExpanded || widget.node.isHorizontalWrap - ? null - : widget.node.basicBoxLocal.width; + ? null + : widget.node.basicBoxLocal.width; final height = widget.node.isVerticalExpanded || widget.node.isVerticalWrap ? null : widget.node.basicBoxLocal.height; - final TextStyle labelStyle = - TextUtils.retrieveTextStyleFromProp(widget.node.properties.labelStyle); + final TextStyle labelStyle = TextUtils.retrieveTextStyleFromProp( + widget.node.properties.labelStyle, + ); final TextStyle unselectedLabelStyle = TextUtils.retrieveTextStyleFromProp( - widget.node.properties.unselectedLabelStyle); + widget.node.properties.unselectedLabelStyle, + ); final Decoration? indicator = getIndicator(); @@ -166,23 +177,26 @@ class _PassiveTabBarWidgetState extends State controller: controller, padding: widget.node.padding.flutterEdgeInsets, overlayColor: WidgetStateProperty.all( - widget.node.properties.overlayColor?.toFlutterColor()), + widget.node.properties.overlayColor?.toFlutterColor(), + ), dividerColor: widget.node.properties.showDivider ? widget.node.properties.dividerColor?.toFlutterColor() : Colors.transparent, dividerHeight: widget.node.properties.showDivider ? widget.node.properties.dividerHeight : 0, - tabAlignment: widget.node.properties.tabAlignment - ?.toFlutter(isScrollable: widget.node.isScrollable), - indicatorColor: - widget.node.properties.indicatorColor.toFlutterColor(), + tabAlignment: widget.node.properties.tabAlignment?.toFlutter( + isScrollable: widget.node.isScrollable, + ), + indicatorColor: widget.node.properties.indicatorColor + .toFlutterColor(), indicatorPadding: widget.node.properties.indicatorPadding.flutterEdgeInsets, indicatorSize: widget.node.properties.indicatorSize.toFlutter(), isScrollable: widget.node.isScrollable, - indicatorWeight: - indicator != null ? 0 : widget.node.properties.indicatorWeight, + indicatorWeight: indicator != null + ? 0 + : widget.node.properties.indicatorWeight, labelStyle: labelStyle, indicator: indicator, splashBorderRadius: @@ -190,11 +204,12 @@ class _PassiveTabBarWidgetState extends State splashFactory: NoSplash.splashFactory, labelColor: widget.node.properties.labelColor?.toFlutterColor(), labelPadding: widget.node.properties.labelPadding.flutterEdgeInsets, - unselectedLabelColor: - widget.node.properties.unselectedLabelColor?.toFlutterColor(), + unselectedLabelColor: widget.node.properties.unselectedLabelColor + ?.toFlutterColor(), unselectedLabelStyle: unselectedLabelStyle, - physics: widget.node.physics - .flutterScrollPhysics(widget.node.shouldAlwaysScroll), + physics: widget.node.physics.flutterScrollPhysics( + widget.node.shouldAlwaysScroll, + ), onTap: widget.onChanged, tabs: [ for (final (index, tab) in widget.node.properties.tabs.indexed) @@ -209,9 +224,12 @@ class _PassiveTabBarWidgetState extends State } Widget getTab(TabItem tab, ScopedValues scopedValues) { - final String rawLabel = PropertyValueDelegate.getPropertyValue( - widget.node, 'tab-label-${tab.id}', - scopedValues: scopedValues) ?? + final String rawLabel = + PropertyValueDelegate.getPropertyValue( + widget.node, + 'tab-label-${tab.id}', + scopedValues: scopedValues, + ) ?? tab.label; String label = PropertyValueDelegate.substituteVariables( @@ -219,15 +237,18 @@ class _PassiveTabBarWidgetState extends State nullSubstitutionMode: widget.settings.nullSubstitutionMode, scopedValues: scopedValues, ); - final double effectiveIconSize = - min(tab.icon.size ?? 24, widget.node.basicBoxLocal.height); + final double effectiveIconSize = min( + tab.icon.size ?? 24, + widget.node.basicBoxLocal.height, + ); Widget? iconWidget = widget.node.properties.contentType.showIcon ? retrieveIconWidget( tab.icon.color != null || !tab.icon.isSvgImage ? tab.icon : tab.icon.copyWith(color: ColorRGBA.transparent), - effectiveIconSize) + effectiveIconSize, + ) : null; if (iconWidget == null) return Tab(text: label); @@ -268,8 +289,10 @@ class _PassiveTabBarWidgetState extends State color: widget.node.properties.indicatorColor.toFlutterColor(), width: widget.node.properties.indicatorWeight, ), - ).orNullIf(widget.node.properties.indicatorCornerRadius.isUniform && - widget.node.properties.indicatorCornerRadius == CornerRadius.zero); + ).orNullIf( + widget.node.properties.indicatorCornerRadius.isUniform && + widget.node.properties.indicatorCornerRadius == CornerRadius.zero, + ); case TabIndicatorStyle.filled: return ShapeDecoration( color: widget.node.properties.indicatorColor.toFlutterColor(), @@ -277,7 +300,8 @@ class _PassiveTabBarWidgetState extends State shape: widget.node.properties.indicatorShape, radius: widget.node.properties.indicatorCornerRadius, borderColor: widget.node.properties.indicatorColor, - borderWidth: widget.node.properties.indicatorStyle == + borderWidth: + widget.node.properties.indicatorStyle == TabIndicatorStyle.border ? widget.node.properties.indicatorWeight : 0, @@ -289,7 +313,8 @@ class _PassiveTabBarWidgetState extends State shape: widget.node.properties.indicatorShape, radius: widget.node.properties.indicatorCornerRadius, borderColor: widget.node.properties.indicatorColor, - borderWidth: widget.node.properties.indicatorStyle == + borderWidth: + widget.node.properties.indicatorStyle == TabIndicatorStyle.border ? widget.node.properties.indicatorWeight : 0, diff --git a/lib/src/transformers/node_transformers/passive_text_field_transformer.dart b/lib/src/transformers/node_transformers/passive_text_field_transformer.dart index 6a20b67a..e82c8ed5 100644 --- a/lib/src/transformers/node_transformers/passive_text_field_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_text_field_transformer.dart @@ -40,10 +40,7 @@ class PassiveTextFieldTransformer extends NodeWidgetTransformer { properties: props, edgePins: EdgePinsModel.standard, ); - return PassiveTextFieldWidget( - node: node, - settings: settings, - ); + return PassiveTextFieldWidget(node: node, settings: settings); } void onTap(BuildContext context, TextFieldNode node, String inputValue) { @@ -53,14 +50,26 @@ class PassiveTextFieldTransformer extends NodeWidgetTransformer { void onChanged(BuildContext context, TextFieldNode node, String inputValue) { NodeStateProvider.setState(context, inputValue); - FunctionsRepository.setNodeValue(context, - node: node, property: 'inputValue', value: inputValue); + FunctionsRepository.setNodeValue( + context, + node: node, + property: 'inputValue', + value: inputValue, + ); - FunctionsRepository.setPropertyVariable(context, - node: node, property: 'inputValue', value: inputValue); + FunctionsRepository.setPropertyVariable( + context, + node: node, + property: 'inputValue', + value: inputValue, + ); FunctionsRepository.triggerAction( - context, node: node, TriggerType.changed, value: inputValue); + context, + node: node, + TriggerType.changed, + value: inputValue, + ); } void onSubmitted( @@ -70,11 +79,19 @@ class PassiveTextFieldTransformer extends NodeWidgetTransformer { ) { NodeStateProvider.setState(context, inputValue); FunctionsRepository.triggerAction( - context, node: node, TriggerType.submitted, value: inputValue); + context, + node: node, + TriggerType.submitted, + value: inputValue, + ); } - void onIconTap(BuildContext context, TextFieldNode node, - List reactions, String inputValue) { + void onIconTap( + BuildContext context, + TextFieldNode node, + List reactions, + String inputValue, + ) { NodeStateProvider.setState(context, inputValue); FunctionsRepository.triggerAction( context, @@ -94,7 +111,7 @@ class PassiveTextFieldWidget extends StatefulWidget { final Function(BuildContext context, String value)? onChanged; final Function(BuildContext context, String value)? onSubmitted; final Function(BuildContext context, List reactions, String value)? - onIconTap; + onIconTap; final bool withAutofill; final AutovalidateMode? autovalidateMode; final FormFieldValidator? validator; @@ -120,8 +137,9 @@ class PassiveTextFieldWidget extends StatefulWidget { } class _PassiveTextFieldWidgetState extends State { - late final TextEditingController controller = - TextEditingController(text: getInitialText(widget.node.initialText)); + late final TextEditingController controller = TextEditingController( + text: getInitialText(widget.node.initialText), + ); final FocusNode focusNode = FocusNode(); @@ -164,11 +182,11 @@ class _PassiveTextFieldWidgetState extends State { scopedValues: scopedValues, ) : findIndexedValueIfApplied(scopedValues) ?? - PropertyValueDelegate.getPropertyValueFromNodeValues( - widget.node, - 'inputValue', - scopedValues: scopedValues, - ); + PropertyValueDelegate.getPropertyValueFromNodeValues( + widget.node, + 'inputValue', + scopedValues: scopedValues, + ); if (currentPropertyValue != null) { currentPropertyValue = PropertyValueDelegate.substituteVariables( currentPropertyValue, @@ -230,16 +248,18 @@ class _PassiveTextFieldWidgetState extends State { final TextInputType keyboardType = properties.keyboardType == TextInputTypeEnum.number - ? TextInputType.numberWithOptions( - decimal: properties.showDecimalKey, - signed: properties.showSignKey) - : properties.keyboardType.toFlutter(); + ? TextInputType.numberWithOptions( + decimal: properties.showDecimalKey, + signed: properties.showSignKey, + ) + : properties.keyboardType.toFlutter(); final TextInputValidatorModel validatorModel = properties.validator; final FormFieldValidator validator = widget.validator ?? validatorModel.validate; - final bool autofocus = !widget.settings.isPreview && + final bool autofocus = + !widget.settings.isPreview && (widget.autofocus ?? properties.autoFocus); Widget field = TextFormField( @@ -249,7 +269,8 @@ class _PassiveTextFieldWidgetState extends State { enableInteractiveSelection: properties.enableInteractiveSelection, enabled: enabled, controller: controller, - obscureText: widget.node.properties.maxLines == 1 && + obscureText: + widget.node.properties.maxLines == 1 && widget.node.isVerticalWrap && obscureText, readOnly: readOnly, @@ -267,24 +288,28 @@ class _PassiveTextFieldWidgetState extends State { cursorRadius: Radius.circular(properties.cursorRadius), maxLength: maxLength, validator: validator, - autovalidateMode: widget.autovalidateMode ?? + autovalidateMode: + widget.autovalidateMode ?? widget.node.properties.autovalidateMode.flutterAutovalidateMode, autofillHints: AutofillGroup.maybeOf(context) != null && !widget.settings.isPreview - ? widget.node.properties.autofillHints.map((hint) => hint.code) - : null, + ? widget.node.properties.autofillHints.map((hint) => hint.code) + : null, inputFormatters: [ if (maxLength != null) LengthLimitingTextInputFormatter(maxLength), ?properties.formatter.toFlutterFormatter(), ], - maxLines: - widget.node.properties.maxLines.orNullIf(!widget.node.isVerticalWrap), - minLines: - widget.node.properties.minLines.orNullIf(!widget.node.isVerticalWrap), + maxLines: widget.node.properties.maxLines.orNullIf( + !widget.node.isVerticalWrap, + ), + minLines: widget.node.properties.minLines.orNullIf( + !widget.node.isVerticalWrap, + ), expands: !widget.node.isVerticalWrap, obscuringCharacter: widget.node.properties.obscuringCharacter, style: TextUtils.retrieveTextStyleFromProp( - widget.node.properties.inputStyle), + widget.node.properties.inputStyle, + ), onTap: () => widget.onTap?.call(context, controller.text), onChanged: (value) => widget.onChanged?.call(context, value), onEditingComplete: () {}, @@ -307,10 +332,7 @@ class _PassiveTextFieldWidgetState extends State { field = IntrinsicHeight(child: field); } - field = AdaptiveNodeBox( - node: node, - child: field, - ); + field = AdaptiveNodeBox(node: node, child: field); return field; } @@ -423,8 +445,9 @@ class _PassiveTextFieldWidgetState extends State { ? null : retrieveIconWidget(decoration.icon, null), label: labelText, - floatingLabelStyle: - TextUtils.retrieveTextStyleFromProp(decoration.floatingLabelStyle), + floatingLabelStyle: TextUtils.retrieveTextStyleFromProp( + decoration.floatingLabelStyle, + ), helperText: helperText, helperStyle: TextUtils.retrieveTextStyleFromProp(decoration.helperStyle), helperMaxLines: decoration.helperMaxLines, @@ -438,24 +461,30 @@ class _PassiveTextFieldWidgetState extends State { isCollapsed: isCollapsed, isDense: isDense, contentPadding: node.padding.flutterEdgeInsets, - prefixIcon: !decoration.prefixIcon.icon.show || - decoration.prefixIcon.icon.isEmpty + prefixIcon: + !decoration.prefixIcon.icon.show || decoration.prefixIcon.icon.isEmpty ? null : _ReactiveIcon( onTap: () => widget.onIconTap?.call( - context, decoration.prefixIcon.reactions, controller.text), + context, + decoration.prefixIcon.reactions, + controller.text, + ), iconModel: decoration.prefixIcon, ), // prefixIconConstraints: // decoration.prefixIconConstraints.flutterConstraints, prefix: prefixText, prefixStyle: TextUtils.retrieveTextStyleFromProp(decoration.prefixStyle), - suffixIcon: !decoration.suffixIcon.icon.show || - decoration.suffixIcon.icon.isEmpty + suffixIcon: + !decoration.suffixIcon.icon.show || decoration.suffixIcon.icon.isEmpty ? null : _ReactiveIcon( onTap: () => widget.onIconTap?.call( - context, decoration.suffixIcon.reactions, controller.text), + context, + decoration.suffixIcon.reactions, + controller.text, + ), iconModel: decoration.suffixIcon, ), suffix: suffixText, @@ -464,8 +493,8 @@ class _PassiveTextFieldWidgetState extends State { // decoration.suffixIconConstraints.flutterConstraints, counter: decoration.showCounter ? decoration.counterText?.isNotEmpty == true - ? counterText - : null + ? counterText + : null : null, counterText: decoration.showCounter ? null : '', // counterText: decoration.showCounter @@ -473,8 +502,9 @@ class _PassiveTextFieldWidgetState extends State { // ? decoration.counterText // : null // : '', - counterStyle: - TextUtils.retrieveTextStyleFromProp(decoration.counterStyle), + counterStyle: TextUtils.retrieveTextStyleFromProp( + decoration.counterStyle, + ), filled: decoration.filled, fillColor: decoration.fillColor.toFlutterColor(), focusColor: decoration.focusColor.toFlutterColor(), @@ -576,10 +606,7 @@ class _ReactiveIcon extends StatelessWidget { final VoidCallback onTap; final ReactiveIconModel iconModel; - const _ReactiveIcon({ - required this.onTap, - required this.iconModel, - }); + const _ReactiveIcon({required this.onTap, required this.iconModel}); @override Widget build(BuildContext context) { @@ -593,10 +620,7 @@ class _ReactiveIcon extends StatelessWidget { return GestureDetector( onTap: onTap, - child: MouseRegion( - cursor: SystemMouseCursors.click, - child: icon, - ), + child: MouseRegion(cursor: SystemMouseCursors.click, child: icon), ); } } diff --git a/lib/src/transformers/node_transformers/passive_text_transformer.dart b/lib/src/transformers/node_transformers/passive_text_transformer.dart index 2d8077e8..732f5bb3 100644 --- a/lib/src/transformers/node_transformers/passive_text_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_text_transformer.dart @@ -75,9 +75,9 @@ class _PassiveTextWidgetState extends State { widget.node.textMixedProps.forEach((property) { String link = property.link; if (link.isNotEmpty) { - TapGestureRecognizer tapGestureRecognizer = - TapGestureRecognizer(debugOwner: link) - ..onTap = () => FunctionsRepository.openLink(link); + TapGestureRecognizer tapGestureRecognizer = TapGestureRecognizer( + debugOwner: link, + )..onTap = () => FunctionsRepository.openLink(link); tapGestureRecognizerRegistry[link] = tapGestureRecognizer; } }); @@ -87,7 +87,8 @@ class _PassiveTextWidgetState extends State { void disposeTapGestureRecognizers() { if (!widget.clickable) return; tapGestureRecognizerRegistry.forEach( - (link, tapGestureRecognizer) => tapGestureRecognizer.dispose()); + (link, tapGestureRecognizer) => tapGestureRecognizer.dispose(), + ); tapGestureRecognizerRegistry = {}; } diff --git a/lib/src/transformers/node_transformers/passive_variance_transformer.dart b/lib/src/transformers/node_transformers/passive_variance_transformer.dart index ca83a019..e95da501 100644 --- a/lib/src/transformers/node_transformers/passive_variance_transformer.dart +++ b/lib/src/transformers/node_transformers/passive_variance_transformer.dart @@ -39,13 +39,14 @@ class PassiveVarianceWidget extends StatelessWidget { if (settings.isPreview) return node.currentVariant.children; final String variantIdOrName = PropertyValueDelegate.getPropertyValue( - node, - 'currentVariantId', - scopedValues: scopedValues, - ) ?? - node.currentVariantId; + node, + 'currentVariantId', + scopedValues: scopedValues, + ) ?? + node.currentVariantId; - final Variant variant = node.variants.findById(variantIdOrName) ?? + final Variant variant = + node.variants.findById(variantIdOrName) ?? node.variants.findByName(variantIdOrName) ?? node.currentVariant; return variant.children; diff --git a/lib/src/transformers/node_transformers/passive_web_view_transformer_io.dart b/lib/src/transformers/node_transformers/passive_web_view_transformer_io.dart index 6afadbaa..2c1f2211 100644 --- a/lib/src/transformers/node_transformers/passive_web_view_transformer_io.dart +++ b/lib/src/transformers/node_transformers/passive_web_view_transformer_io.dart @@ -9,7 +9,6 @@ import 'package:webview_flutter/webview_flutter.dart'; import 'package:webview_flutter_android/webview_flutter_android.dart'; import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart'; - import '../../../codelessly_sdk.dart'; class PassiveWebViewTransformer extends NodeWidgetTransformer { @@ -41,10 +40,7 @@ class PassiveWebViewTransformer extends NodeWidgetTransformer { } Widget buildFromNode(WebViewNode node, WidgetBuildSettings settings) { - return PassiveWebViewWidget( - node: node, - settings: settings, - ); + return PassiveWebViewWidget(node: node, settings: settings); } void onChanged(List reactions) => reactions @@ -118,15 +114,17 @@ class WebViewPreviewWidget extends StatelessWidget { child: FractionallySizedBox( widthFactor: aspectRatio > 1 ? 0.2 : null, heightFactor: aspectRatio < 1 ? 0.2 : null, - child: LayoutBuilder(builder: (context, constraints) { - return IconTheme( - data: IconThemeData( - color: Colors.blue, - size: min(constraints.maxWidth, constraints.maxHeight), - ), - child: icon, - ); - }), + child: LayoutBuilder( + builder: (context, constraints) { + return IconTheme( + data: IconThemeData( + color: Colors.blue, + size: min(constraints.maxWidth, constraints.maxHeight), + ), + child: icon, + ); + }, + ), ), ), ); @@ -192,8 +190,10 @@ class _RawWebViewWidgetState extends State { if (_controller.platform is AndroidWebViewController) { (_controller.platform as AndroidWebViewController) - .setMediaPlaybackRequiresUserGesture(props.mediaAutoPlaybackPolicy != - WebViewMediaAutoPlaybackPolicy.alwaysPlayAllMedia); + .setMediaPlaybackRequiresUserGesture( + props.mediaAutoPlaybackPolicy != + WebViewMediaAutoPlaybackPolicy.alwaysPlayAllMedia, + ); } // Not yet supported on MacOS. Throws an exception. @@ -241,7 +241,8 @@ class _RawWebViewWidgetState extends State { switch (properties.pageSourceType) { case WebViewWebpageSourceType.url: return ( - source: PropertyValueDelegate.getVariableValueFromPath( + source: + PropertyValueDelegate.getVariableValueFromPath( properties.input, scopedValues: scopedValues, ) ?? @@ -259,13 +260,17 @@ class _RawWebViewWidgetState extends State { case WebViewType.googleMaps: return ( source: buildGoogleMapsURL( - props as GoogleMapsWebViewProperties, scopedValues), + props as GoogleMapsWebViewProperties, + scopedValues, + ), isAsset: false, ); case WebViewType.twitter: return ( - source: - buildTwitterURL(props as TwitterWebViewProperties, scopedValues), + source: buildTwitterURL( + props as TwitterWebViewProperties, + scopedValues, + ), isAsset: false, ); } @@ -289,9 +294,7 @@ class _RawWebViewWidgetState extends State { child = buildWebpageWebView(context, props as WebPageWebViewProperties); case WebViewType.googleMaps: if (!isPlatformSupportedForWebView || widget.settings.isPreview) { - return const WebViewPreviewWidget( - icon: Icon(Icons.map_outlined), - ); + return const WebViewPreviewWidget(icon: Icon(Icons.map_outlined)); } child = buildWebView(props as GoogleMapsWebViewProperties); @@ -299,9 +302,7 @@ class _RawWebViewWidgetState extends State { if (!isPlatformSupportedForWebView || widget.settings.isPreview) { return const WebViewPreviewWidget( icon: ImageIcon( - NetworkImage( - 'https://img.icons8.com/color/344/twitter--v2.png', - ), + NetworkImage('https://img.icons8.com/color/344/twitter--v2.png'), ), ); } @@ -313,43 +314,52 @@ class _RawWebViewWidgetState extends State { } Widget buildWebpageWebView( - BuildContext context, WebPageWebViewProperties properties) { + BuildContext context, + WebPageWebViewProperties properties, + ) { if (!isPlatformSupportedForWebView || widget.settings.isPreview) { - return const WebViewPreviewWidget( - icon: Icon(Icons.language_rounded), - ); + return const WebViewPreviewWidget(icon: Icon(Icons.language_rounded)); } return buildWebView(properties); } String buildGoogleMapsURL( - GoogleMapsWebViewProperties properties, ScopedValues scopedValues) { + GoogleMapsWebViewProperties properties, + ScopedValues scopedValues, + ) { final String? originalSrc = properties.src; final String? updatedSrc = originalSrc != null - ? PropertyValueDelegate.getVariableValueFromPath(originalSrc, - scopedValues: scopedValues) ?? - originalSrc + ? PropertyValueDelegate.getVariableValueFromPath( + originalSrc, + scopedValues: scopedValues, + ) ?? + originalSrc : null; return _buildHtmlContent(updatedSrc); } String _buildHtmlContent(String? src) { final String html = src?.replaceAll('\n', '') ?? 'about:blank'; - final String contentBase64 = - base64Encode(const Utf8Encoder().convert(html)); + final String contentBase64 = base64Encode( + const Utf8Encoder().convert(html), + ); final String content = 'data:text/html;base64,$contentBase64'; return content; } String buildTwitterURL( - TwitterWebViewProperties properties, ScopedValues scopedValues) { + TwitterWebViewProperties properties, + ScopedValues scopedValues, + ) { final String? originalSrc = properties.src; final String? updatedSrc = originalSrc != null - ? PropertyValueDelegate.getVariableValueFromPath(originalSrc, - scopedValues: scopedValues) ?? - originalSrc + ? PropertyValueDelegate.getVariableValueFromPath( + originalSrc, + scopedValues: scopedValues, + ) ?? + originalSrc : null; return _buildHtmlContent(updatedSrc); } @@ -361,20 +371,24 @@ class _RawWebViewWidgetState extends State { gestureRecognizers: { if (properties.controlVerticalScrollGesture == true) const Factory( - VerticalDragGestureRecognizer.new), + VerticalDragGestureRecognizer.new, + ), if (properties.controlHorizontalScrollGesture == true) const Factory( - HorizontalDragGestureRecognizer.new), + HorizontalDragGestureRecognizer.new, + ), if (properties.controlScaleGesture == true) const Factory(ScaleGestureRecognizer.new), if (properties.controlTapGesture == true) const Factory(TapGestureRecognizer.new), if (properties.controlLongPressGesture == true) const Factory( - LongPressGestureRecognizer.new), + LongPressGestureRecognizer.new, + ), if (properties.controlForcePressGesture == true) const Factory( - ForcePressGestureRecognizer.new), + ForcePressGestureRecognizer.new, + ), // const Factory( // TapAndPanGestureRecognizer.new), // const Factory(PanGestureRecognizer.new), diff --git a/lib/src/transformers/node_transformers/passive_web_view_transformer_web.dart b/lib/src/transformers/node_transformers/passive_web_view_transformer_web.dart index 6d1a6642..ce749a0d 100644 --- a/lib/src/transformers/node_transformers/passive_web_view_transformer_web.dart +++ b/lib/src/transformers/node_transformers/passive_web_view_transformer_web.dart @@ -38,10 +38,7 @@ class PassiveWebViewTransformer extends NodeWidgetTransformer { } Widget buildFromNode(WebViewNode node, WidgetBuildSettings settings) { - return PassiveWebViewWidget( - node: node, - settings: settings, - ); + return PassiveWebViewWidget(node: node, settings: settings); } void onChanged(List reactions) => reactions @@ -115,15 +112,17 @@ class WebViewPreviewWidget extends StatelessWidget { child: FractionallySizedBox( widthFactor: aspectRatio > 1 ? 0.2 : null, heightFactor: aspectRatio < 1 ? 0.2 : null, - child: LayoutBuilder(builder: (context, constraints) { - return IconTheme( - data: IconThemeData( - color: Colors.blue, - size: min(constraints.maxWidth, constraints.maxHeight), - ), - child: icon, - ); - }), + child: LayoutBuilder( + builder: (context, constraints) { + return IconTheme( + data: IconThemeData( + color: Colors.blue, + size: min(constraints.maxWidth, constraints.maxHeight), + ), + child: icon, + ); + }, + ), ), ), ); @@ -175,13 +174,17 @@ class _RawWebViewWidgetState extends State { case WebViewType.webpage: final WebPageWebViewProperties properties = props as WebPageWebViewProperties; - url = PropertyValueDelegate.getVariableValueFromPath( - properties.input, - scopedValues: scopedValues) ?? + url = + PropertyValueDelegate.getVariableValueFromPath( + properties.input, + scopedValues: scopedValues, + ) ?? properties.input; case WebViewType.googleMaps: url = buildGoogleMapsURL( - props as GoogleMapsWebViewProperties, scopedValues); + props as GoogleMapsWebViewProperties, + scopedValues, + ); case WebViewType.twitter: url = buildTwitterURL(props as TwitterWebViewProperties, scopedValues); } @@ -199,31 +202,40 @@ class _RawWebViewWidgetState extends State { } String buildGoogleMapsURL( - GoogleMapsWebViewProperties properties, ScopedValues scopedValues) { + GoogleMapsWebViewProperties properties, + ScopedValues scopedValues, + ) { final String? originalSrc = properties.src; final String? updatedSrc = originalSrc != null - ? PropertyValueDelegate.getVariableValueFromPath(originalSrc, - scopedValues: scopedValues) ?? - originalSrc + ? PropertyValueDelegate.getVariableValueFromPath( + originalSrc, + scopedValues: scopedValues, + ) ?? + originalSrc : null; return _buildHtmlContent(updatedSrc); } String buildTwitterURL( - TwitterWebViewProperties properties, ScopedValues scopedValues) { + TwitterWebViewProperties properties, + ScopedValues scopedValues, + ) { final String? originalSrc = properties.src; final String? updatedSrc = originalSrc != null - ? PropertyValueDelegate.getVariableValueFromPath(originalSrc, - scopedValues: scopedValues) ?? - originalSrc + ? PropertyValueDelegate.getVariableValueFromPath( + originalSrc, + scopedValues: scopedValues, + ) ?? + originalSrc : null; return _buildHtmlContent(updatedSrc); } String _buildHtmlContent(String? src) { final String html = src?.replaceAll('\n', '') ?? 'about:blank'; - final String contentBase64 = - base64Encode(const Utf8Encoder().convert(html)); + final String contentBase64 = base64Encode( + const Utf8Encoder().convert(html), + ); final String content = 'data:text/html;base64,$contentBase64'; return content; @@ -235,17 +247,14 @@ class _RawWebViewWidgetState extends State { if (widget.settings.isPreview) { switch (props.webviewType) { case WebViewType.webpage: - return const WebViewPreviewWidget( - icon: Icon(Icons.language_rounded), - ); + return const WebViewPreviewWidget(icon: Icon(Icons.language_rounded)); case WebViewType.googleMaps: - return const WebViewPreviewWidget( - icon: Icon(Icons.map_outlined), - ); + return const WebViewPreviewWidget(icon: Icon(Icons.map_outlined)); case WebViewType.twitter: return const WebViewPreviewWidget( - icon: ImageIcon(NetworkImage( - 'https://img.icons8.com/color/344/twitter--v2.png')), + icon: ImageIcon( + NetworkImage('https://img.icons8.com/color/344/twitter--v2.png'), + ), ); } } diff --git a/lib/src/transformers/passive_transformer_manager.dart b/lib/src/transformers/passive_transformer_manager.dart index bcdecd8f..5c2453d9 100644 --- a/lib/src/transformers/passive_transformer_manager.dart +++ b/lib/src/transformers/passive_transformer_manager.dart @@ -39,8 +39,10 @@ class PassiveNodeTransformerManager extends WidgetNodeTransformerManager { 'radio': PassiveRadioTransformer(getNode, this), 'icon': PassiveIconTransformer(getNode, this), 'spacer': PassiveSpacerTransformer(getNode, this), - 'floatingActionButton': - PassiveFloatingActionButtonTransformer(getNode, this), + 'floatingActionButton': PassiveFloatingActionButtonTransformer( + getNode, + this, + ), 'expansionTile': PassiveExpansionTileTransformer(getNode, this), 'accordion': PassiveAccordionTransformer(getNode, this), 'listTile': PassiveListTileTransformer(getNode, this), @@ -73,8 +75,8 @@ class PassiveNodeTransformerManager extends WidgetNodeTransformerManager { context, node: node, builder: (context) { - Widget widget = - getTransformerByNode(node).buildWidget(node, context, settings); + Widget widget = getTransformerByNode(node) + .buildWidget(node, context, settings); if (settings.withOpacity) { widget = applyWidgetOpacity(node, widget); @@ -122,7 +124,8 @@ class PassiveNodeTransformerManager extends WidgetNodeTransformerManager { required BaseNode node, required Widget Function(BuildContext context) builder, }) { - final bool enabled = PropertyValueDelegate.getPropertyValue( + final bool enabled = + PropertyValueDelegate.getPropertyValue( node, 'node_enabled', scopedValues: ScopedValues.of(context), @@ -131,8 +134,8 @@ class PassiveNodeTransformerManager extends WidgetNodeTransformerManager { if (!enabled) return const SizedBox.shrink(); - final CodelesslyContext codelesslyContext = - context.read(); + final CodelesslyContext codelesslyContext = context + .read(); // List of value notifiers that the widget listens to. This includes node's // local values and the variables attached to node properties. @@ -164,8 +167,8 @@ class PassiveNodeTransformerManager extends WidgetNodeTransformerManager { } // Get corresponding variable data from codelessly context. - final ValueNotifier? listenableVariable = - codelesslyContext.findVariableByName(match.name); + final ValueNotifier? listenableVariable = codelesslyContext + .findVariableByName(match.name); // Add variable to [listenables]. if (listenableVariable != null) listenables.add(listenableVariable); } @@ -191,8 +194,8 @@ class PassiveNodeTransformerManager extends WidgetNodeTransformerManager { } // Get corresponding variable data from codelessly context. - final ValueNotifier? listenableVariable = - codelesslyContext.findVariableByName(match.name); + final ValueNotifier? listenableVariable = codelesslyContext + .findVariableByName(match.name); // Add variable to [listenables]. if (listenableVariable != null) listenables.add(listenableVariable); } @@ -201,23 +204,27 @@ class PassiveNodeTransformerManager extends WidgetNodeTransformerManager { for (final BaseCondition condition in codelesslyContext.conditions.values) { if (!condition.hasNode(node.id)) continue; - final Set variableNames = - condition.getReactiveVariables().toSet(); + final Set variableNames = condition + .getReactiveVariables() + .toSet(); for (final name in variableNames) { if (predefinedListenableVariableNames.contains(name)) { switch (name) { case 'storage': final notifier = getStorageListenerFor( - VariableMatch.parse(name.wrapWithVariableSyntax())!, context); + VariableMatch.parse(name.wrapWithVariableSyntax())!, + context, + ); if (notifier != null) listenables.add(notifier); } continue; } // Get corresponding variable data from codelessly context. final ValueNotifier? listenableVariable = - codelesslyContext.variables.values - .firstWhereOrNull((notifier) => notifier.value.name == name); + codelesslyContext.variables.values.firstWhereOrNull( + (notifier) => notifier.value.name == name, + ); // Add variable to [listenables]. if (listenableVariable != null) listenables.add(listenableVariable); } @@ -227,17 +234,19 @@ class PassiveNodeTransformerManager extends WidgetNodeTransformerManager { key: ValueKey(node.id), node: node, codelesslyContext: codelesslyContext, - child: Builder(builder: (context) { - if (listenables.isNotEmpty) { - return ManagedListenableBuilder( - key: ValueKey(node.id), - listenables: listenables, - builder: (context) => builder(context), - ); - } else { - return builder(context); - } - }), + child: Builder( + builder: (context) { + if (listenables.isNotEmpty) { + return ManagedListenableBuilder( + key: ValueKey(node.id), + listenables: listenables, + builder: (context) => builder(context), + ); + } else { + return builder(context); + } + }, + ), ); } @@ -245,8 +254,9 @@ class PassiveNodeTransformerManager extends WidgetNodeTransformerManager { final localStorage = context.read()?.localDatabase; if (localStorage == null) return null; if (match.hasPath) { - final pathMatch = - VariableMatch.parse(match.path!.wrapWithVariableSyntax()); + final pathMatch = VariableMatch.parse( + match.path!.wrapWithVariableSyntax(), + ); if (pathMatch == null) return null; final key = pathMatch.name; return localStorage.getNotifier(key); @@ -287,8 +297,10 @@ class _ManagedListenableBuilderState extends State { @override void didUpdateWidget(covariant ManagedListenableBuilder oldWidget) { super.didUpdateWidget(oldWidget); - if (!const ListEquality() - .equals(oldWidget.listenables, widget.listenables)) { + if (!const ListEquality().equals( + oldWidget.listenables, + widget.listenables, + )) { // ValueNotifier objects are equatable by object identity, so we check // if those objects are the same or not. listenable?.removeListener(onChanged); @@ -337,14 +349,16 @@ class _NodeStateProviderWidgetState extends State @override void initState() { super.initState(); - widget.codelesslyContext.nodeRebuildNotifier - .addListener(onNodeNotification); + widget.codelesslyContext.nodeRebuildNotifier.addListener( + onNodeNotification, + ); } @override void dispose() { - widget.codelesslyContext.nodeRebuildNotifier - .removeListener(onNodeNotification); + widget.codelesslyContext.nodeRebuildNotifier.removeListener( + onNodeNotification, + ); super.dispose(); } diff --git a/lib/src/transformers/transformer_functions.dart b/lib/src/transformers/transformer_functions.dart index bf9c518a..3270ca0d 100644 --- a/lib/src/transformers/transformer_functions.dart +++ b/lib/src/transformers/transformer_functions.dart @@ -11,16 +11,20 @@ void convertNodeToButtonType(ButtonTypeEnum type, ButtonNode node) { switch (type) { case ButtonTypeEnum.elevated: final brightness = ThemeData.estimateBrightnessForColor( - node.properties.buttonColor.toFlutterColor()); + node.properties.buttonColor.toFlutterColor(), + ); final color = (brightness == Brightness.light ? Colors.black : Colors.white) .toColorRGBA(); node.properties - ..labelStyle = node.properties.labelStyle.copyWith(fills: [ - color.toPaint(node.properties.labelStyle.fills.firstOrNull?.id) - ]) - ..icon = node.properties.icon - .copyWith(color: color.toFlutterColor().toColorRGBA()) + ..labelStyle = node.properties.labelStyle.copyWith( + fills: [ + color.toPaint(node.properties.labelStyle.fills.firstOrNull?.id), + ], + ) + ..icon = node.properties.icon.copyWith( + color: color.toFlutterColor().toColorRGBA(), + ) ..buttonColor = node.properties.buttonColor.copyWith(a: 1) ..elevation = 8 ..gap = node.properties.icon.show ? 8 : 0 @@ -30,13 +34,16 @@ void convertNodeToButtonType(ButtonTypeEnum type, ButtonNode node) { case ButtonTypeEnum.text: node.properties ..buttonColor = node.properties.buttonColor.copyWith(a: 0.1) - ..labelStyle = node.properties.labelStyle.copyWith(fills: [ - node.properties.buttonColor - .copyWith(a: 1) - .toPaint(node.properties.labelStyle.fills.firstOrNull?.id) - ]) - ..icon = node.properties.icon - .copyWith(color: node.properties.buttonColor.copyWith(a: 1)) + ..labelStyle = node.properties.labelStyle.copyWith( + fills: [ + node.properties.buttonColor + .copyWith(a: 1) + .toPaint(node.properties.labelStyle.fills.firstOrNull?.id), + ], + ) + ..icon = node.properties.icon.copyWith( + color: node.properties.buttonColor.copyWith(a: 1), + ) ..elevation = 0 ..shape = CShapeBorder.roundedRectangle ..borderWidth = null @@ -44,27 +51,33 @@ void convertNodeToButtonType(ButtonTypeEnum type, ButtonNode node) { case ButtonTypeEnum.outlined: node.properties ..buttonColor = node.properties.buttonColor.copyWith(a: 1) - ..labelStyle = node.properties.labelStyle.copyWith(fills: [ - node.properties.buttonColor - .copyWith(a: 1) - .toPaint(node.properties.labelStyle.fills.firstOrNull?.id) - ]) - ..icon = node.properties.icon - .copyWith(color: node.properties.buttonColor.copyWith(a: 1)) + ..labelStyle = node.properties.labelStyle.copyWith( + fills: [ + node.properties.buttonColor + .copyWith(a: 1) + .toPaint(node.properties.labelStyle.fills.firstOrNull?.id), + ], + ) + ..icon = node.properties.icon.copyWith( + color: node.properties.buttonColor.copyWith(a: 1), + ) ..elevation = 0 ..shape = CShapeBorder.roundedRectangle ..borderWidth = 1 ..borderColor = null; case ButtonTypeEnum.filled: final brightness = ThemeData.estimateBrightnessForColor( - node.properties.buttonColor.toFlutterColor()); + node.properties.buttonColor.toFlutterColor(), + ); final color = (brightness == Brightness.light ? Colors.black : Colors.white) .toColorRGBA(); node.properties - ..labelStyle = node.properties.labelStyle.copyWith(fills: [ - color.toPaint(node.properties.labelStyle.fills.firstOrNull?.id) - ]) + ..labelStyle = node.properties.labelStyle.copyWith( + fills: [ + color.toPaint(node.properties.labelStyle.fills.firstOrNull?.id), + ], + ) ..icon = node.properties.icon.copyWith(color: color) ..buttonColor = node.properties.buttonColor.copyWith(a: 1) ..elevation = 0 @@ -75,13 +88,16 @@ void convertNodeToButtonType(ButtonTypeEnum type, ButtonNode node) { case ButtonTypeEnum.filledTonal: node.properties ..buttonColor = node.properties.buttonColor.copyWith(a: 0.2) - ..labelStyle = node.properties.labelStyle.copyWith(fills: [ - node.properties.buttonColor - .copyWith(a: 1) - .toPaint(node.properties.labelStyle.fills.firstOrNull?.id) - ]) - ..icon = node.properties.icon - .copyWith(color: node.properties.buttonColor.copyWith(a: 1)) + ..labelStyle = node.properties.labelStyle.copyWith( + fills: [ + node.properties.buttonColor + .copyWith(a: 1) + .toPaint(node.properties.labelStyle.fills.firstOrNull?.id), + ], + ) + ..icon = node.properties.icon.copyWith( + color: node.properties.buttonColor.copyWith(a: 1), + ) ..elevation = 0 ..gap = node.properties.icon.show ? 8 : 0 ..shape = CShapeBorder.stadium @@ -89,14 +105,17 @@ void convertNodeToButtonType(ButtonTypeEnum type, ButtonNode node) { ..borderWidth = null; case ButtonTypeEnum.icon: final brightness = ThemeData.estimateBrightnessForColor( - node.properties.buttonColor.toFlutterColor()); + node.properties.buttonColor.toFlutterColor(), + ); final color = (brightness == Brightness.light ? Colors.black : Colors.white) .toColorRGBA(); node.properties - ..labelStyle = node.properties.labelStyle.copyWith(fills: [ - color.toPaint(node.properties.labelStyle.fills.firstOrNull?.id) - ]) + ..labelStyle = node.properties.labelStyle.copyWith( + fills: [ + color.toPaint(node.properties.labelStyle.fills.firstOrNull?.id), + ], + ) ..icon = node.properties.icon.copyWith(color: color) ..buttonColor = node.properties.buttonColor.copyWith(a: 1) ..icon = node.properties.icon.copyWith(show: true) @@ -135,10 +154,7 @@ void transformNodeFromIconButton(ButtonNode node) { } double height = painter.height + (10 * 2); node.update( - middleBoxLocal: node.basicBoxLocal.copyWith( - width: width, - height: height, - ), + middleBoxLocal: node.basicBoxLocal.copyWith(width: width, height: height), padding: defaultButtonPadding, ); node.properties.icon = node.properties.icon.copyWith(size: painter.height); @@ -147,10 +163,7 @@ void transformNodeFromIconButton(ButtonNode node) { void transformNodeToIconButton(ButtonNode node) { node.update( - middleBoxLocal: node.basicBoxLocal.copyWith( - width: 48, - height: 48, - ), + middleBoxLocal: node.basicBoxLocal.copyWith(width: 48, height: 48), padding: EdgeInsetsModel.zero, ); node.properties.icon = node.properties.icon.copyWith(size: 20); @@ -160,25 +173,32 @@ void transformNodeToIconButton(ButtonNode node) { /// Flutter's ButtonStyle parameters do different things depending on the button /// type. So we have to manually change them. -ButtonStyle createMasterButtonStyle(ButtonNode node, - {double? elevation, required ScopedValues scopedValues}) { - final TextStyle textStyle = - TextUtils.retrieveTextStyleFromProp(node.properties.labelStyle); - final Color? labelColor = PropertyValueDelegate.getPropertyValue( +ButtonStyle createMasterButtonStyle( + ButtonNode node, { + double? elevation, + required ScopedValues scopedValues, +}) { + final TextStyle textStyle = TextUtils.retrieveTextStyleFromProp( + node.properties.labelStyle, + ); + final Color? labelColor = + PropertyValueDelegate.getPropertyValue( node, 'labelColor', scopedValues: scopedValues, )?.toFlutterColor() ?? TextUtils.retrievePropColor(node.properties.labelStyle); - final buttonColor = PropertyValueDelegate.getPropertyValue( + final buttonColor = + PropertyValueDelegate.getPropertyValue( node, 'buttonColor', scopedValues: scopedValues, ) ?? node.properties.buttonColor; - final shadowColor = PropertyValueDelegate.getPropertyValue( + final shadowColor = + PropertyValueDelegate.getPropertyValue( node, 'shadowColor', scopedValues: scopedValues, @@ -243,7 +263,8 @@ ButtonStyle createMasterButtonStyle(ButtonNode node, buttonColor.toFlutterColor().a < 0.7) { primary = buttonColor.toFlutterColor().withValues(alpha: 0.1); } else if (buttonColor.toFlutterColor().a == 0) { - final iconColor = PropertyValueDelegate.getPropertyValue( + final iconColor = + PropertyValueDelegate.getPropertyValue( node, 'iconColor', scopedValues: scopedValues, @@ -263,18 +284,20 @@ ButtonStyle createMasterButtonStyle(ButtonNode node, ); } return buttonStyle.copyWith( - elevation: - WidgetStateProperty.all(elevation ?? node.properties.elevation)); + elevation: WidgetStateProperty.all(elevation ?? node.properties.elevation), + ); } OutlinedBorder? getButtonShape(ButtonNode node, ScopedValues scopedValues) { - final buttonColor = PropertyValueDelegate.getPropertyValue( + final buttonColor = + PropertyValueDelegate.getPropertyValue( node, 'buttonColor', scopedValues: scopedValues, ) ?? node.properties.buttonColor; - final borderColor = PropertyValueDelegate.getPropertyValue( + final borderColor = + PropertyValueDelegate.getPropertyValue( node, 'borderColor', scopedValues: scopedValues, @@ -368,8 +391,7 @@ Widget retrieveNavBarItemIconWidget( MultiSourceIconModel icon, [ double? size, Color? themeColor, -]) => - retrieveIconWidget(icon, size, themeColor) ?? const SizedBox.shrink(); +]) => retrieveIconWidget(icon, size, themeColor) ?? const SizedBox.shrink(); String buildYoutubeEmbedUrl({ required EmbeddedYoutubeVideoProperties properties, @@ -457,14 +479,15 @@ String getVideoUrl(EmbeddedVideoSource source, String baseUrl) { } } -OutlinedBorder? getShapeFromMixin(ShapeBorderMixin mixin, - {bool onlyShape = false}) => - getShape( - shape: mixin.shape, - radius: mixin.cornerRadius, - borderColor: onlyShape ? null : mixin.borderColor, - borderWidth: onlyShape ? null : mixin.borderWidth, - ); +OutlinedBorder? getShapeFromMixin( + ShapeBorderMixin mixin, { + bool onlyShape = false, +}) => getShape( + shape: mixin.shape, + radius: mixin.cornerRadius, + borderColor: onlyShape ? null : mixin.borderColor, + borderWidth: onlyShape ? null : mixin.borderWidth, +); OutlinedBorder getShape({ required CShapeBorder shape, @@ -474,11 +497,8 @@ OutlinedBorder getShape({ }) { final BorderSide side = borderColor != null && borderWidth != null && borderWidth > 0 - ? BorderSide( - color: borderColor.toFlutterColor(), - width: borderWidth, - ) - : BorderSide.none; + ? BorderSide(color: borderColor.toFlutterColor(), width: borderWidth) + : BorderSide.none; switch (shape) { case CShapeBorder.rectangle: @@ -527,8 +547,12 @@ OutlinedBorder getShape({ /// This method must be called before calling [convertAlignmentToPosition] /// and [convertPositionToAlignment] to get the correct childWidth and /// childHeight for these methods. -SizeC getEffectiveChildSizeForImage(BaseNode node, PaintModel paint, - {double? scaleX, double? scaleY}) { +SizeC getEffectiveChildSizeForImage( + BaseNode node, + PaintModel paint, { + double? scaleX, + double? scaleY, +}) { final double modifiedScaleX = (scaleX ?? paint.scaleX).abs(); final double modifiedScaleY = (scaleY ?? paint.scaleY).abs(); @@ -597,7 +621,8 @@ bool isCustomPositionRequiredForImage(PaintModel paint, SizeC nodeSize) { if (paint.sourceWidth == null || paint.sourceHeight == null) return false; if (paint.isNonUniformScale) return true; if (paint.fit == Fit.fitWidth || paint.fit == Fit.fitHeight) { - final diff = (paint.sourceWidth! / paint.sourceHeight!) - + final diff = + (paint.sourceWidth! / paint.sourceHeight!) - (nodeSize.width / nodeSize.height); if (diff.closeTo(0)) return true; } @@ -619,13 +644,13 @@ Widget wrapWithPaddedScrollable({ width: (node.horizontalFit == SizeFit.shrinkWrap) ? null : (node.horizontalFit == SizeFit.expanded) - ? double.infinity - : node.basicBoxLocal.width, + ? double.infinity + : node.basicBoxLocal.width, height: (node.verticalFit == SizeFit.shrinkWrap) ? null : (node.verticalFit == SizeFit.expanded) - ? double.infinity - : node.basicBoxLocal.height, + ? double.infinity + : node.basicBoxLocal.height, child: SingleChildScrollView( scrollDirection: node.scrollDirection.flutterAxis, reverse: node.reverse, @@ -642,5 +667,6 @@ Widget wrapWithPaddedScrollable({ bool get isPlatformSupportedForWebView => kIsWeb || - PassiveEmbeddedVideoWidget.supportedPlatforms - .contains(defaultTargetPlatform); + PassiveEmbeddedVideoWidget.supportedPlatforms.contains( + defaultTargetPlatform, + ); diff --git a/lib/src/transformers/utils/blend_mask.dart b/lib/src/transformers/utils/blend_mask.dart index 268ba9f2..cdfd1d51 100644 --- a/lib/src/transformers/utils/blend_mask.dart +++ b/lib/src/transformers/utils/blend_mask.dart @@ -31,11 +31,7 @@ class BlendMask extends SingleChildRenderObjectWidget { /// Creates a widget that blends its child using the given [blendMode]. /// /// The [blendMode] argument must not be null. - const BlendMask({ - this.blendMode = BlendMode.srcOver, - super.key, - super.child, - }); + const BlendMask({this.blendMode = BlendMode.srcOver, super.key, super.child}); @override RenderObject createRenderObject(BuildContext context) { @@ -79,10 +75,10 @@ class RenderBlendMask extends RenderProxyBox { /// /// The [blendMode] must not be null. RenderBlendMask(BlendMode blendMode) - : _blendMode = blendMode, - blender = Paint() - ..blendMode = blendMode - ..color = Colors.white; + : _blendMode = blendMode, + blender = Paint() + ..blendMode = blendMode + ..color = Colors.white; @override void paint(PaintingContext context, Offset offset) { diff --git a/lib/src/transformers/utils/condition_evaluator.dart b/lib/src/transformers/utils/condition_evaluator.dart index 348b4ff4..f5bbba45 100644 --- a/lib/src/transformers/utils/condition_evaluator.dart +++ b/lib/src/transformers/utils/condition_evaluator.dart @@ -59,15 +59,17 @@ class ConditionEvaluator @override Object? visitVariablePart(VariablePart part) { - final value = - part.valueString.splitMapJoinRegex(variablePathRegex, onMatch: (match) { - final Object? value = PropertyValueDelegate.getVariableValueFromPath( - match[0]!, - scopedValues: scopedValues, - ); - - return value?.typedValue() ?? 'null'; - }); + final value = part.valueString.splitMapJoinRegex( + variablePathRegex, + onMatch: (match) { + final Object? value = PropertyValueDelegate.getVariableValueFromPath( + match[0]!, + scopedValues: scopedValues, + ); + + return value?.typedValue() ?? 'null'; + }, + ); return _visitRawValue(value); } @@ -108,10 +110,9 @@ class ConditionEvaluator if (left == null || right == null) return false; // This is required since we have loose type checking. - return left - .toString() - .toLowerCase() - .compareTo(right.toString().toLowerCase()) > + return left.toString().toLowerCase().compareTo( + right.toString().toLowerCase(), + ) > 0; } @@ -122,10 +123,9 @@ class ConditionEvaluator if (left == null || right == null) return false; // This is required since we have loose type checking. - return left - .toString() - .toLowerCase() - .compareTo(right.toString().toLowerCase()) < + return left.toString().toLowerCase().compareTo( + right.toString().toLowerCase(), + ) < 0; } @@ -136,10 +136,9 @@ class ConditionEvaluator if (left == null || right == null) return false; // This is required since we have loose type checking. - return left - .toString() - .toLowerCase() - .compareTo(right.toString().toLowerCase()) >= + return left.toString().toLowerCase().compareTo( + right.toString().toLowerCase(), + ) >= 0; } @@ -150,10 +149,9 @@ class ConditionEvaluator if (left == null || right == null) return false; // This is required since we have loose type checking. - return left - .toString() - .toLowerCase() - .compareTo(right.toString().toLowerCase()) <= + return left.toString().toLowerCase().compareTo( + right.toString().toLowerCase(), + ) <= 0; } @@ -273,8 +271,10 @@ class ConditionPrinter final StringBuffer _buffer = StringBuffer(); void printCondition(BaseCondition condition) { - DebugLogger.instance - .printInfo('\n${prettify(condition)}', name: 'ConditionPrinter'); + DebugLogger.instance.printInfo( + '\n${prettify(condition)}', + name: 'ConditionPrinter', + ); } String prettify(BaseCondition condition) { @@ -320,8 +320,9 @@ class ConditionPrinter void visitExpression(Expression expression) { _buffer.write('( '); expression.leftPart.accept(this); - _buffer - .write(' ${expression.operator.sign ?? expression.operator.sentence} '); + _buffer.write( + ' ${expression.operator.sign ?? expression.operator.sentence} ', + ); expression.rightPart.accept(this); _buffer.write(' )'); } @@ -347,7 +348,8 @@ class ConditionPrinter @override void visitSetValueAction(SetValueAction action) { _buffer.writeln( - '\tSet Value of ${action.values.first.name} to ${valueModelToString(action.values.first)}'); + '\tSet Value of ${action.values.first.name} to ${valueModelToString(action.values.first)}', + ); } String valueModelToString(ValueModel model) { @@ -364,7 +366,8 @@ class ConditionPrinter @override void visitSetVariableAction(SetVariableAction action) { _buffer.writeln( - '\tSet Variable ${action.variable.name} to ${action.variable.value}'); + '\tSet Variable ${action.variable.name} to ${action.variable.value}', + ); } @override diff --git a/lib/src/transformers/utils/condition_visitors.dart b/lib/src/transformers/utils/condition_visitors.dart index fcefe139..88cb9db0 100644 --- a/lib/src/transformers/utils/condition_visitors.dart +++ b/lib/src/transformers/utils/condition_visitors.dart @@ -7,9 +7,7 @@ class ConditionVariablesVisitor ExpressionVisitor>, ExpressionPartVisitor>, ActionVisitor> { - const ConditionVariablesVisitor({ - this.excludedVariableNames = const {}, - }); + const ConditionVariablesVisitor({this.excludedVariableNames = const {}}); final Set excludedVariableNames; @@ -17,8 +15,9 @@ class ConditionVariablesVisitor Set visitCondition(Condition condition) { return { ...condition.expression.accept>(this) ?? {}, - ...condition.actions - .expand((action) => action.accept>(this) ?? {}), + ...condition.actions.expand( + (action) => action.accept>(this) ?? {}, + ), }; } @@ -117,8 +116,8 @@ class ConditionVariablesVisitor @override Set? visitLoadFromCloudStorageAction( - LoadFromCloudStorageAction action) => - {}; + LoadFromCloudStorageAction action, + ) => {}; } /// A visitor that returns the list of node IDs used in a condition's actions. @@ -134,18 +133,18 @@ class ConditionNodesVisitor @override Set? visitCondition(Condition condition) => { - ...condition.actions - .map((action) => action.accept(this)) - .nonNulls - .toSet(), - }; + ...condition.actions + .map((action) => action.accept(this)) + .nonNulls + .toSet(), + }; @override Set? visitConditionGroup(ConditionGroup condition) => { - ...condition.ifCondition.actions, - ...condition.elseIfConditions.expand((condition) => condition.actions), - ...condition.elseCondition?.actions ?? [], - }.map((action) => action.accept(this)).nonNulls.toSet(); + ...condition.ifCondition.actions, + ...condition.elseIfConditions.expand((condition) => condition.actions), + ...condition.elseCondition?.actions ?? [], + }.map((action) => action.accept(this)).nonNulls.toSet(); @override Set? visitElseCondition(ElseCondition condition) { diff --git a/lib/src/transformers/utils/dash_path.dart b/lib/src/transformers/utils/dash_path.dart index 13314d25..4b496a53 100644 --- a/lib/src/transformers/utils/dash_path.dart +++ b/lib/src/transformers/utils/dash_path.dart @@ -2,8 +2,7 @@ import 'dart:ui'; /// Converts a [path] to a dashed [Path] with a given [length] and [gap]. /// The [distance] parameter can be used to offset the dash pattern. -Path dashPath(Path path, double length, - [double? gap, double? distance = 0]) { +Path dashPath(Path path, double length, [double? gap, double? distance = 0]) { gap ??= length; PathMetrics pathMetrics = path.computeMetrics(); Path dest = Path(); diff --git a/lib/src/transformers/utils/node_state_provider.dart b/lib/src/transformers/utils/node_state_provider.dart index 72aa7354..9cf6b80c 100644 --- a/lib/src/transformers/utils/node_state_provider.dart +++ b/lib/src/transformers/utils/node_state_provider.dart @@ -43,8 +43,8 @@ class NodeStateProvider extends InheritedWidget { /// /// If there is no [NodeStateProvider] ancestor, returns null. static NodeStateProvider? maybeOf(BuildContext context) { - final NodeStateProvider? provider = - context.dependOnInheritedWidgetOfExactType(); + final NodeStateProvider? provider = context + .dependOnInheritedWidgetOfExactType(); if (provider == null) return null; return provider; } diff --git a/lib/src/transformers/utils/placeholder_painter.dart b/lib/src/transformers/utils/placeholder_painter.dart index 24a1f678..61285e30 100644 --- a/lib/src/transformers/utils/placeholder_painter.dart +++ b/lib/src/transformers/utils/placeholder_painter.dart @@ -40,14 +40,15 @@ class PlaceholderPainter extends CustomPainter { outerLeft = 0; } - final Rect outerRect = - Rect.fromLTWH(outerLeft, outerTop, outerWidth, outerHeight); - - canvas.drawRect( - outerRect, - bgPaint, + final Rect outerRect = Rect.fromLTWH( + outerLeft, + outerTop, + outerWidth, + outerHeight, ); + canvas.drawRect(outerRect, bgPaint); + gap = 4; double innerWidth = size.width - gap * 4; double innerHeight = size.height - gap * 4; @@ -62,8 +63,12 @@ class PlaceholderPainter extends CustomPainter { // innerTop = outerTop; // } - final Rect innerRect = - Rect.fromLTWH(innerLeft, innerTop, innerWidth, innerHeight); + final Rect innerRect = Rect.fromLTWH( + innerLeft, + innerTop, + innerWidth, + innerHeight, + ); canvas.drawRect(innerRect, bgPaint); @@ -101,27 +106,15 @@ class PlaceholderPainter extends CustomPainter { // Scale the text to always fit inside the box. final double textScale = min( 1.0, - min( - maxWidth / textWidth, - maxHeight / textHeight, - ), + min(maxWidth / textWidth, maxHeight / textHeight), ); canvas.scale(textScale); - canvas.translate( - -textPainter.width / 2, - -textPainter.height / 2, - ); + canvas.translate(-textPainter.width / 2, -textPainter.height / 2); - textPainter.paint( - canvas, - Offset.zero, - ); + textPainter.paint(canvas, Offset.zero); - canvas.translate( - textPainter.width / 2, - textPainter.height / 2, - ); + canvas.translate(textPainter.width / 2, textPainter.height / 2); canvas.scale(1 / textScale); canvas.translate(-size.width / 2, -size.height / 2); diff --git a/lib/src/transformers/utils/property_value_delegate.dart b/lib/src/transformers/utils/property_value_delegate.dart index 612ef571..31463cc2 100644 --- a/lib/src/transformers/utils/property_value_delegate.dart +++ b/lib/src/transformers/utils/property_value_delegate.dart @@ -101,8 +101,8 @@ class PropertyValueDelegate { if (codelesslyContext == null) return null; - final BaseCondition? condition = - codelesslyContext.conditions.findByNodeProperty(node.id, property); + final BaseCondition? condition = codelesslyContext.conditions + .findByNodeProperty(node.id, property); // if (condition != null) { // print('----------------------------------------------------------------'); @@ -214,8 +214,9 @@ class PropertyValueDelegate { if (variable == null) return null; - VariableType accessorType = - match.hasAccessor ? VariableType.fromObjectType(value) : variable.type; + VariableType accessorType = match.hasAccessor + ? VariableType.fromObjectType(value) + : variable.type; List accessors = variable.name.startsWith('api') ? getApiResponseAccessorsForType(VariableType.map) : getAccessorsForType(accessorType); @@ -224,8 +225,9 @@ class PropertyValueDelegate { if (value == null) return null; String accessor = segments.removeAt(0); - final Accessor? matchedAccessor = - accessors.firstWhereOrNull((element) => element.name == accessor); + final Accessor? matchedAccessor = accessors.firstWhereOrNull( + (element) => element.name == accessor, + ); if (matchedAccessor == null) { // If the accessor is not found, it means that the path is invalid. But @@ -258,8 +260,10 @@ class PropertyValueDelegate { if (match == null) return null; if (match.isPredefinedVariable) { - final Object? value = - retrievePredefinedVariableValue(match, scopedValues); + final Object? value = retrievePredefinedVariableValue( + match, + scopedValues, + ); return value; } @@ -268,29 +272,34 @@ class PropertyValueDelegate { if (match.hasAccessor) { if (variable.type.isText) { final characters = variable.getValue().toString().characters.toList(); - final (Object? value, _) = - substituteJsonPath(match.fullPath, {match.name: characters}); + final (Object? value, _) = substituteJsonPath(match.fullPath, { + match.name: characters, + }); return value; } if (!variable.type.isList) return null; - final (Object? value, _) = - substituteJsonPath(match.fullPath, {match.name: variable.getValue()}); + final (Object? value, _) = substituteJsonPath(match.fullPath, { + match.name: variable.getValue(), + }); return value; } if (!match.hasPath) return variable.getValue(); - final variableProps = - getVariableProperties(variable.getValue(), variable.type); + final variableProps = getVariableProperties( + variable.getValue(), + variable.type, + ); final Object? variableValue = variable.getValue(); final Map values = { ...variableProps, if (variable.type.isMap && variableValue is Map) ...variableValue, }; - final (Object? value, _) = - substituteJsonPath(match.fullPath, {match.name: values}); + final (Object? value, _) = substituteJsonPath(match.fullPath, { + match.name: values, + }); return value; } @@ -312,19 +321,22 @@ class PropertyValueDelegate { // substitute path with screen. if (variableValue == null) return null; - final VariableType variableType = - VariableType.fromObjectType(variableValue); + final VariableType variableType = VariableType.fromObjectType( + variableValue, + ); if (match.hasAccessor) { if (variableType.isText) { final characters = variableValue.toString().characters.toList(); - final (Object? value, _) = - substituteJsonPath(match.fullPath, {match.name: characters}); + final (Object? value, _) = substituteJsonPath(match.fullPath, { + match.name: characters, + }); return value; } if (!variableType.isList) return null; - final (Object? value, _) = - substituteJsonPath(match.text, {match.name: variableValue}); + final (Object? value, _) = substituteJsonPath(match.text, { + match.name: variableValue, + }); return value; } @@ -336,8 +348,9 @@ class PropertyValueDelegate { if (variableType.isMap && variableValue is Map) ...Map.from(variableValue), }; - final (Object? value, _) = - substituteJsonPath(match.text, {match.name: values}); + final (Object? value, _) = substituteJsonPath(match.text, { + match.name: values, + }); return value; } @@ -377,8 +390,9 @@ class PropertyValueDelegate { if (nodeValues == null) return null; - final model = - nodeValues.firstWhereOrNull((element) => element.name == property); + final model = nodeValues.firstWhereOrNull( + (element) => element.name == property, + ); if (model == null) return null; @@ -391,7 +405,9 @@ class PropertyValueDelegate { /// - data.name: will be replaced with data['name']. /// static (Object?, JsonPointer?) substituteJsonPath( - String text, Map data) { + String text, + Map data, + ) { // If the text represents a JSON path, get the relevant value from [data] map. if (data.isEmpty) return (null, null); @@ -429,8 +445,9 @@ class PropertyValueDelegate { final match = VariableMatch.parse(token.wrapWithVariableSyntax()); if (match != null && match.hasAccessor) { if (traversedValue is List) { - (traversedValue, _) = - substituteJsonPath(token, {match.name: traversedValue}); + (traversedValue, _) = substituteJsonPath(token, { + match.name: traversedValue, + }); } else { traversedValue = []; } @@ -448,11 +465,14 @@ class PropertyValueDelegate { if (match != null && match.hasAccessor) { if (traversedValue is List) { - final index = - match.accessor!.replaceAll('[', '').replaceAll(']', '').toInt(); + final index = match.accessor! + .replaceAll('[', '') + .replaceAll(']', '') + .toInt(); if (index != null) { - traversedValue[index] = - substituteJsonPath(leafKey, {match.name: value}).$1; + traversedValue[index] = substituteJsonPath(leafKey, { + match.name: value, + }).$1; } } } else if (traversedValue is Map) { @@ -466,69 +486,69 @@ class PropertyValueDelegate { @internal @experimental static Map getVariableProperties( - Object? value, VariableType type) { + Object? value, + VariableType type, + ) { return switch (value) { String() => { - 'length': value.length, - 'isEmpty': value.isEmpty, - 'isNotEmpty': value.isNotEmpty, - 'isBlank': value.trim().isEmpty, - 'upcase': value.toUpperCase(), - 'downcase': value.toLowerCase(), - 'capitalize': value.capitalized, - }, + 'length': value.length, + 'isEmpty': value.isEmpty, + 'isNotEmpty': value.isNotEmpty, + 'isBlank': value.trim().isEmpty, + 'upcase': value.toUpperCase(), + 'downcase': value.toLowerCase(), + 'capitalize': value.capitalized, + }, int() => { - 'isEven': value.isEven, - 'isOdd': value.isOdd, - 'isFinite': value.isFinite, - 'isInfinite': value.isInfinite, - 'isNegative': value.isNegative, - 'isPositive': !value.isNegative, - }, + 'isEven': value.isEven, + 'isOdd': value.isOdd, + 'isFinite': value.isFinite, + 'isInfinite': value.isInfinite, + 'isNegative': value.isNegative, + 'isPositive': !value.isNegative, + }, double() => { - 'isFinite': value.isFinite, - 'isInfinite': value.isInfinite, - 'isNegative': value.isNegative, - 'isPositive': !value.isNegative, - 'isNaN': value.isNaN, - 'whole': value.toInt(), - 'floor': value.floor(), - 'ceil': value.ceil(), - }, - bool() => { - 'toggled': !value, - }, + 'isFinite': value.isFinite, + 'isInfinite': value.isInfinite, + 'isNegative': value.isNegative, + 'isPositive': !value.isNegative, + 'isNaN': value.isNaN, + 'whole': value.toInt(), + 'floor': value.floor(), + 'ceil': value.ceil(), + }, + bool() => {'toggled': !value}, Map() => { - 'length': value.length, - 'isEmpty': value.isEmpty, - 'isNotEmpty': value.isNotEmpty, - 'keys': value.keys.toList(), - 'values': value.values.toList(), - }, + 'length': value.length, + 'isEmpty': value.isEmpty, + 'isNotEmpty': value.isNotEmpty, + 'keys': value.keys.toList(), + 'values': value.values.toList(), + }, List() => { - 'length': value.length, - 'isEmpty': value.isEmpty, - 'isNotEmpty': value.isNotEmpty - }, + 'length': value.length, + 'isEmpty': value.isEmpty, + 'isNotEmpty': value.isNotEmpty, + }, ColorRGBA() => { - 'red': value.r, - 'green': value.g, - 'blue': value.b, - 'alpha': value.a, - 'opacity': value.a / 255, - }, + 'red': value.r, + 'green': value.g, + 'blue': value.b, + 'alpha': value.a, + 'opacity': value.a / 255, + }, ColorRGB() => { - 'red': value.r, - 'green': value.g, - 'blue': value.b, - 'alpha': 255, - 'opacity': 1, - }, + 'red': value.r, + 'green': value.g, + 'blue': value.b, + 'alpha': 255, + 'opacity': 1, + }, _ when type.isText => { - 'isBlank': true, - 'isEmpty': true, - 'isNotEmpty': false, - }, + 'isBlank': true, + 'isEmpty': true, + 'isNotEmpty': false, + }, _ => {}, }; } @@ -783,108 +803,108 @@ List getAccessorsForType(VariableType type) { /// Defines accessors for an api variable based on the type of date it holds. List getApiResponseAccessorsForType(VariableType dataType) => [ - Accessor( - name: 'url', - type: VariableType.text, - getValue: (value) => value.toMap()?['url'], - ), - Accessor( - name: 'status', - type: VariableType.text, - getValue: (value) => value.toMap()?['status']?.toString(), - ), - Accessor( - name: 'statusCode', - type: VariableType.integer, - getValue: (value) => value.toMap()?['statusCode']?.toInt(), - ), - Accessor( - name: 'isIdle', - type: VariableType.boolean, - getValue: (value) => value.toMap()?['isIdle']?.toBool(), - ), - Accessor( - name: 'isLoading', - type: VariableType.boolean, - getValue: (value) => (value.toMap()?['isLoading']).toBool(), - ), - Accessor( - name: 'isError', - type: VariableType.boolean, - getValue: (value) => (value.toMap()?['isError']).toBool(), - ), - Accessor( - name: 'isSuccess', - type: VariableType.boolean, - getValue: (value) => (value.toMap()?['isSuccess']).toBool(), - ), - Accessor( - name: 'hasData', - type: VariableType.boolean, - getValue: (value) => (value.toMap()?['hasData']).toBool(), - ), - Accessor( - name: 'data', - type: dataType, - getValue: (value) => value.toMap()?['data'], - ), - Accessor( - name: 'error', - type: dataType, - getValue: (value) => value.toMap()?['error'], - ), - Accessor( - name: 'headers', - type: VariableType.map, - getValue: (value) => value.toMap()?['headers']?.toMap(), - ), - ]; + Accessor( + name: 'url', + type: VariableType.text, + getValue: (value) => value.toMap()?['url'], + ), + Accessor( + name: 'status', + type: VariableType.text, + getValue: (value) => value.toMap()?['status']?.toString(), + ), + Accessor( + name: 'statusCode', + type: VariableType.integer, + getValue: (value) => value.toMap()?['statusCode']?.toInt(), + ), + Accessor( + name: 'isIdle', + type: VariableType.boolean, + getValue: (value) => value.toMap()?['isIdle']?.toBool(), + ), + Accessor( + name: 'isLoading', + type: VariableType.boolean, + getValue: (value) => (value.toMap()?['isLoading']).toBool(), + ), + Accessor( + name: 'isError', + type: VariableType.boolean, + getValue: (value) => (value.toMap()?['isError']).toBool(), + ), + Accessor( + name: 'isSuccess', + type: VariableType.boolean, + getValue: (value) => (value.toMap()?['isSuccess']).toBool(), + ), + Accessor( + name: 'hasData', + type: VariableType.boolean, + getValue: (value) => (value.toMap()?['hasData']).toBool(), + ), + Accessor( + name: 'data', + type: dataType, + getValue: (value) => value.toMap()?['data'], + ), + Accessor( + name: 'error', + type: dataType, + getValue: (value) => value.toMap()?['error'], + ), + Accessor( + name: 'headers', + type: VariableType.map, + getValue: (value) => value.toMap()?['headers']?.toMap(), + ), +]; /// Defines accessors for a cloud data variable based on the type of date it holds. List getCloudDataVariableAccessorsForType(VariableType dataType) => [ - Accessor( - name: 'url', - type: VariableType.text, - getValue: (value) => value.toMap()?['url'], - ), - Accessor( - name: 'status', - type: VariableType.text, - getValue: (value) => value.toMap()?['status']?.toString(), - ), - Accessor( - name: 'isIdle', - type: VariableType.boolean, - getValue: (value) => value.toMap()?['isIdle']?.toBool(), - ), - Accessor( - name: 'isLoading', - type: VariableType.boolean, - getValue: (value) => value.toMap()?['isLoading']?.toBool(), - ), - Accessor( - name: 'isError', - type: VariableType.boolean, - getValue: (value) => value.toMap()?['isError']?.toBool(), - ), - Accessor( - name: 'isSuccess', - type: VariableType.boolean, - getValue: (value) => value.toMap()?['isSuccess']?.toBool(), - ), - Accessor( - name: 'hasData', - type: VariableType.boolean, - getValue: (value) => value.toMap()?['hasData']?.toBool(), - ), - Accessor( - name: 'data', - type: dataType, - getValue: (value) => value.toMap()?['data'], - ), - Accessor( - name: 'error', - type: dataType, - getValue: (value) => value.toMap()?['error'], - ), - ]; + Accessor( + name: 'url', + type: VariableType.text, + getValue: (value) => value.toMap()?['url'], + ), + Accessor( + name: 'status', + type: VariableType.text, + getValue: (value) => value.toMap()?['status']?.toString(), + ), + Accessor( + name: 'isIdle', + type: VariableType.boolean, + getValue: (value) => value.toMap()?['isIdle']?.toBool(), + ), + Accessor( + name: 'isLoading', + type: VariableType.boolean, + getValue: (value) => value.toMap()?['isLoading']?.toBool(), + ), + Accessor( + name: 'isError', + type: VariableType.boolean, + getValue: (value) => value.toMap()?['isError']?.toBool(), + ), + Accessor( + name: 'isSuccess', + type: VariableType.boolean, + getValue: (value) => value.toMap()?['isSuccess']?.toBool(), + ), + Accessor( + name: 'hasData', + type: VariableType.boolean, + getValue: (value) => value.toMap()?['hasData']?.toBool(), + ), + Accessor( + name: 'data', + type: dataType, + getValue: (value) => value.toMap()?['data'], + ), + Accessor( + name: 'error', + type: dataType, + getValue: (value) => value.toMap()?['error'], + ), +]; diff --git a/lib/src/transformers/utils/scoped_values.dart b/lib/src/transformers/utils/scoped_values.dart index d081987d..86cb822f 100644 --- a/lib/src/transformers/utils/scoped_values.dart +++ b/lib/src/transformers/utils/scoped_values.dart @@ -38,31 +38,31 @@ class ScopedValues { /// Variable name -> Variable. Map get variables => { - ...codelesslyContext?.variableNamesMap() ?? const {}, - for (final variable in _variablesOverrides ?? const []) - variable.id: variable, - }; + ...codelesslyContext?.variableNamesMap() ?? const {}, + for (final variable in _variablesOverrides ?? const []) + variable.id: variable, + }; /// A map of data that is passed to loaded layouts for nodes to replace their /// values with. Map get data => { - ...codelesslyContext?.data ?? const {}, - ..._dataOverrides ?? const {}, - }; + ...codelesslyContext?.data ?? const {}, + ..._dataOverrides ?? const {}, + }; Map>> get nodeValues => codelesslyContext?.nodeValues ?? {}; /// Creates a new scoped values instance with empty values. ScopedValues.empty() - : _codelesslyContextRef = null, - _localStorageRef = null, - _cloudDatabaseRef = null, - _variablesOverrides = null, - _dataOverrides = null, - indexedItem = null, - nodeState = null, - routeParams = {}; + : _codelesslyContextRef = null, + _localStorageRef = null, + _cloudDatabaseRef = null, + _variablesOverrides = null, + _dataOverrides = null, + indexedItem = null, + nodeState = null, + routeParams = {}; /// Creates a new scoped values instance from a given context. ScopedValues.of( @@ -73,13 +73,14 @@ class ScopedValues { _variablesOverrides = variablesOverrides; _dataOverrides = dataOverrides; final codelesslyContext = context.read(); - _codelesslyContextRef = - codelesslyContext == null ? null : WeakReference(codelesslyContext); + _codelesslyContextRef = codelesslyContext == null + ? null + : WeakReference(codelesslyContext); indexedItem = IndexedItemProvider.maybeOf(context); nodeState = NodeStateProvider.maybeOf(context)?.state; routeParams = ModalRoute.of(context)?.settings.arguments as Map? ?? - {}; + {}; final instance = context.read(); if (instance != null) { _localStorageRef = WeakReference(instance.localDatabase); diff --git a/lib/src/transformers/utils/stroke_painter.dart b/lib/src/transformers/utils/stroke_painter.dart index 1234d4ac..c2f5c460 100644 --- a/lib/src/transformers/utils/stroke_painter.dart +++ b/lib/src/transformers/utils/stroke_painter.dart @@ -71,21 +71,21 @@ class StrokePainter extends CustomPainter { this.strokeAlign = StrokeAlignC.inside, this.strokeCap = StrokeCapEnum.square, this.borderRadius = BorderRadius.zero, - }) : dashArray = LoopingList(dashPattern), - border = Border( - left: strokeSide == StrokeSide.left - ? BorderSide(color: color, width: strokeWidth) - : BorderSide.none, - right: strokeSide == StrokeSide.right - ? BorderSide(color: color, width: strokeWidth) - : BorderSide.none, - bottom: strokeSide == StrokeSide.bottom - ? BorderSide(color: color, width: strokeWidth) - : BorderSide.none, - top: strokeSide == StrokeSide.top - ? BorderSide(color: color, width: strokeWidth) - : BorderSide.none, - ); + }) : dashArray = LoopingList(dashPattern), + border = Border( + left: strokeSide == StrokeSide.left + ? BorderSide(color: color, width: strokeWidth) + : BorderSide.none, + right: strokeSide == StrokeSide.right + ? BorderSide(color: color, width: strokeWidth) + : BorderSide.none, + bottom: strokeSide == StrokeSide.bottom + ? BorderSide(color: color, width: strokeWidth) + : BorderSide.none, + top: strokeSide == StrokeSide.top + ? BorderSide(color: color, width: strokeWidth) + : BorderSide.none, + ); @override void paint(Canvas canvas, Size size) { @@ -127,9 +127,10 @@ class StrokePainter extends CustomPainter { if (boxShape == BoxShape.circle) { final double circleDiameter = min(size.width, size.height); final Rect basicRect = Rect.fromCenter( - center: Offset(size.width / 2, size.height / 2), - width: circleDiameter, - height: circleDiameter); + center: Offset(size.width / 2, size.height / 2), + width: circleDiameter, + height: circleDiameter, + ); path.addOval(basicRect); } else { final Rect basicRect = Rect.fromLTWH(0, 0, size.width, size.height); @@ -174,14 +175,14 @@ class StrokePainter extends CustomPainter { // the vector's direction between (-y, x) or (y, -x), flipping // the direction for our convenience. final Offset parallelTangentVector = tangent.vector; - final Offset normal = Offset( + final Offset normal = + Offset( parallelTangentVector.dy * isInside * isCenter, parallelTangentVector.dx * isOutside, ) * // Multiply by the stroke thickness to figure out the distance // needed from the start position to the stroke. strokeWidth * - // If it's a center stroke, we need to divide the normal by 2. // It will be rendered twice, once inside, once outside, // achieving the center stroke effect. @@ -227,8 +228,10 @@ class StrokePainter extends CustomPainter { realSize = Size(size.width + strokeWidth, size.height + strokeWidth); case StrokeAlignC.outside: topLeft = Offset(-strokeWidth, -strokeWidth); - realSize = - Size(size.width + strokeWidth * 2, size.height + strokeWidth * 2); + realSize = Size( + size.width + strokeWidth * 2, + size.height + strokeWidth * 2, + ); case StrokeAlignC.inside: break; } @@ -245,9 +248,10 @@ class StrokePainter extends CustomPainter { if (boxShape == BoxShape.circle) { final double circleDiameter = min(size.width, size.height); final Rect basicRect = Rect.fromCenter( - center: Offset(size.width / 2, size.height / 2), - width: circleDiameter, - height: circleDiameter); + center: Offset(size.width / 2, size.height / 2), + width: circleDiameter, + height: circleDiameter, + ); innerPath.addOval(basicRect); outerPath.addOval(basicRect.inflate(strokeWidth)); } else { @@ -273,26 +277,31 @@ class StrokePainter extends CustomPainter { // // We get a really nice and seamless outside border transition this // way. - outerPath.addRRect((borderRadius.add( - BorderRadius.only( - topRight: Radius.circular( - strokeWidth * (min(borderRadius.topRight.x, 10) / 10)), - topLeft: Radius.circular( - strokeWidth * (min(borderRadius.topLeft.x, 10) / 10)), - bottomLeft: Radius.circular( - strokeWidth * (min(borderRadius.bottomLeft.x, 10) / 10)), - bottomRight: Radius.circular( - strokeWidth * (min(borderRadius.bottomRight.x, 10) / 10)), + outerPath.addRRect( + (borderRadius.add( + BorderRadius.only( + topRight: Radius.circular( + strokeWidth * (min(borderRadius.topRight.x, 10) / 10), + ), + topLeft: Radius.circular( + strokeWidth * (min(borderRadius.topLeft.x, 10) / 10), + ), + bottomLeft: Radius.circular( + strokeWidth * (min(borderRadius.bottomLeft.x, 10) / 10), + ), + bottomRight: Radius.circular( + strokeWidth * (min(borderRadius.bottomRight.x, 10) / 10), + ), + ), + ) as BorderRadius).toRRect( + Rect.fromLTWH( + topLeft.dx, + topLeft.dy, + realSize.width, + realSize.height, + ), ), - ) as BorderRadius) - .toRRect( - Rect.fromLTWH( - topLeft.dx, - topLeft.dy, - realSize.width, - realSize.height, - ), - )); + ); } // This is where the magic happens. We want to fill the area @@ -303,13 +312,14 @@ class StrokePainter extends CustomPainter { dest.addPath(innerPath, Offset.zero); dest.addPath(outerPath, Offset.zero); - canvas.drawPath( - dest..close(), - paint..style = PaintingStyle.fill, - ); + canvas.drawPath(dest..close(), paint..style = PaintingStyle.fill); } else { final Rect rect = Rect.fromLTWH( - topLeft.dx, topLeft.dy, realSize.width, realSize.height); + topLeft.dx, + topLeft.dy, + realSize.width, + realSize.height, + ); // For any other border, we can just use Flutter's built in painter. // No special handling required here. diff --git a/lib/src/transformers/utils/text_utils.dart b/lib/src/transformers/utils/text_utils.dart index 9c8d35f8..6a287ac3 100644 --- a/lib/src/transformers/utils/text_utils.dart +++ b/lib/src/transformers/utils/text_utils.dart @@ -78,21 +78,21 @@ class TextUtils { final TextStyle style = switch (hasMissingFont) { false => retrieveTextStyle( - fontSize: fontSize, - fontName: fontName, - textDecoration: textDecoration, - lineHeight: lineHeight, - letterSpacing: letterSpacing, - color: color, - effects: effects ?? (node is BlendMixin ? node.effects : const []), - ), + fontSize: fontSize, + fontName: fontName, + textDecoration: textDecoration, + lineHeight: lineHeight, + letterSpacing: letterSpacing, + color: color, + effects: effects ?? (node is BlendMixin ? node.effects : const []), + ), true => TextStyle( - color: Colors.red, - fontSize: fontSize, - fontWeight: FontWeight.bold, - decorationColor: Colors.yellow, - decoration: TextDecoration.underline, - ), + color: Colors.red, + fontSize: fontSize, + fontWeight: FontWeight.bold, + decorationColor: Colors.yellow, + decoration: TextDecoration.underline, + ), }; // Replace with fx symbol if required. @@ -291,38 +291,33 @@ class TextUtils { static double retrieveLetterSpacing( LetterSpacing letterSpacing, double fontSize, - ) => - switch (letterSpacing.unit) { - LetterSpacingUnitEnum.pixels => letterSpacing.value, - LetterSpacingUnitEnum.percent => (letterSpacing.value * fontSize) / 100, - }; + ) => switch (letterSpacing.unit) { + LetterSpacingUnitEnum.pixels => letterSpacing.value, + LetterSpacingUnitEnum.percent => (letterSpacing.value * fontSize) / 100, + }; /// Convert LineHeight from Figma values into Flutter. - static double? retrieveLineHeight( - LineHeight lineHeight, - double fontSize, - ) => + static double? retrieveLineHeight(LineHeight lineHeight, double fontSize) => lineHeight.value == null || lineHeight.value! <= 0 - ? null - : switch (lineHeight.unit) { - LineHeightUnitEnum.pixels => lineHeight.value! / fontSize, - LineHeightUnitEnum.percent => lineHeight.value! / 100, - LineHeightUnitEnum.auto => null, - }; + ? null + : switch (lineHeight.unit) { + LineHeightUnitEnum.pixels => lineHeight.value! / fontSize, + LineHeightUnitEnum.percent => lineHeight.value! / 100, + LineHeightUnitEnum.auto => null, + }; static TextStyle retrieveTextStyleFromProp( TextProp prop, { List effects = const [], - }) => - retrieveTextStyle( - fontSize: prop.fontSize, - fontName: prop.fontName, - textDecoration: prop.textDecoration, - lineHeight: prop.lineHeight, - letterSpacing: prop.letterSpacing, - color: retrievePropColor(prop), - effects: effects, - ); + }) => retrieveTextStyle( + fontSize: prop.fontSize, + fontName: prop.fontName, + textDecoration: prop.textDecoration, + lineHeight: prop.lineHeight, + letterSpacing: prop.letterSpacing, + color: retrievePropColor(prop), + effects: effects, + ); /// Sets default values. static TextStyle retrieveTextStyle({ @@ -345,8 +340,8 @@ class TextUtils { final FontWeight? fontWeightProp = fontName?.flutterFontWeight; final FontStyle? fontStyle = fontName != null ? (fontName.style.toLowerCase().contains('italic')) - ? FontStyle.italic - : FontStyle.normal + ? FontStyle.italic + : FontStyle.normal : null; // Not supported yet but good to have. @@ -377,8 +372,9 @@ class TextUtils { shadows: shadows, ); } else { - final String? fontFamily = - fontName == null ? null : getFontFamilyNameAndVariant(fontName); + final String? fontFamily = fontName == null + ? null + : getFontFamilyNameAndVariant(fontName); // Enable this for fonts debugging. // print('Transformer is using fontFamily: $fontFamily'); diff --git a/lib/src/transformers/utils/variable_span.dart b/lib/src/transformers/utils/variable_span.dart index 1d92458b..859846cc 100644 --- a/lib/src/transformers/utils/variable_span.dart +++ b/lib/src/transformers/utils/variable_span.dart @@ -15,7 +15,7 @@ class VariableSpan extends TextSpan { super.onEnter, super.onExit, }) : super( - text: 'fx', - style: style?.merge(GoogleFonts.unna(fontStyle: FontStyle.italic)), - ); + text: 'fx', + style: style?.merge(GoogleFonts.unna(fontStyle: FontStyle.italic)), + ); } diff --git a/lib/src/transformers/widget_node_transformer_manager.dart b/lib/src/transformers/widget_node_transformer_manager.dart index 8ad06de2..268434f8 100644 --- a/lib/src/transformers/widget_node_transformer_manager.dart +++ b/lib/src/transformers/widget_node_transformer_manager.dart @@ -22,8 +22,14 @@ typedef LayoutRetrieverBuilder = Widget Function( /// [ActiveNodeTransformerManager]. /// /// Helps separate the functions that are common between them into one place. -abstract class WidgetNodeTransformerManager extends NodeTransformerManager< - Widget, BuildContext, WidgetBuildSettings, NodeWidgetTransformer> { +abstract class WidgetNodeTransformerManager + extends + NodeTransformerManager< + Widget, + BuildContext, + WidgetBuildSettings, + NodeWidgetTransformer + > { /// Returns a widget that decides how to load nested layouts of a rendered /// node. final LayoutRetrieverBuilder layoutRetrievalBuilder; @@ -47,7 +53,8 @@ abstract class WidgetNodeTransformerManager extends NodeTransformerManager< Widget widget, { required bool maintainState, }) { - final bool visible = PropertyValueDelegate.getPropertyValue( + final bool visible = + PropertyValueDelegate.getPropertyValue( node, 'visible', scopedValues: ScopedValues.of(context), @@ -72,13 +79,17 @@ abstract class WidgetNodeTransformerManager extends NodeTransformerManager< /// Takes into account margin and padding to figure out the origin of /// the rotation. Widget applyWidgetRotation( - BuildContext context, BaseNode node, Widget widget) { + BuildContext context, + BaseNode node, + Widget widget, + ) { final CodelesslyContext payload = context.read(); // Get node's values. final List nodeValues = payload.nodeValues[node.id]?.value ?? []; // Get local rotation value if it exists, else use node's rotation value. - final int rotationDegrees = nodeValues + final int rotationDegrees = + nodeValues .firstWhereOrNull((value) => value.name == 'rotationDegrees') ?.value .typedValue() ?? @@ -99,10 +110,7 @@ abstract class WidgetNodeTransformerManager extends NodeTransformerManager< /// Convenience method to handle widget margins. Widget applyWidgetMargins(BaseNode node, Widget widget) { if (node.margin == EdgeInsetsModel.zero) return widget; - return Padding( - padding: node.margin.flutterEdgeInsets, - child: widget, - ); + return Padding(padding: node.margin.flutterEdgeInsets, child: widget); } /// Convenience method to handle widget constraints. @@ -132,18 +140,21 @@ abstract class WidgetNodeTransformerManager extends NodeTransformerManager< // because then it has fills that obscure the inkwell effect. if (node is DefaultShapeNode && inkWell != null) return widget; - final List onClickReactions = (node as ReactionMixin) - .reactions - .where((reaction) => - reaction.trigger.type == TriggerType.click && - reaction.action.enabled) + final List onClickReactions = (node as ReactionMixin).reactions + .where( + (reaction) => + reaction.trigger.type == TriggerType.click && + reaction.action.enabled, + ) .toList(); final List onLongPressReactions = (node as ReactionMixin) .reactions - .where((reaction) => - reaction.trigger.type == TriggerType.longPress && - reaction.action.enabled) + .where( + (reaction) => + reaction.trigger.type == TriggerType.longPress && + reaction.action.enabled, + ) .toList(); if (inkWell != null) { @@ -163,7 +174,8 @@ abstract class WidgetNodeTransformerManager extends NodeTransformerManager< borderRadius: getBorderRadius(node), overlayColor: inkWell.overlayColor != null ? WidgetStatePropertyAll( - inkWell.overlayColor!.toFlutterColor()) + inkWell.overlayColor!.toFlutterColor(), + ) : null, splashColor: inkWell.splashColor?.toFlutterColor(), highlightColor: inkWell.highlightColor?.toFlutterColor(), @@ -202,8 +214,12 @@ abstract class WidgetNodeTransformerManager extends NodeTransformerManager< if (node is CornerMixin) { if (node.cornerRadius.linked && node.cornerRadius.type == RadiusType.elliptical) { - return BorderRadius.all(Radius.elliptical( - node.basicBoxLocal.width, node.basicBoxLocal.height)); + return BorderRadius.all( + Radius.elliptical( + node.basicBoxLocal.width, + node.basicBoxLocal.height, + ), + ); } return node.cornerRadius.borderRadius; } else { @@ -324,13 +340,13 @@ class AdaptiveSizeFitBox extends StatelessWidget { width: (horizontalFit == SizeFit.shrinkWrap) ? null : (horizontalFit == SizeFit.expanded) - ? double.infinity - : size.width, + ? double.infinity + : size.width, height: (verticalFit == SizeFit.shrinkWrap) ? null : (verticalFit == SizeFit.expanded) - ? double.infinity - : size.height, + ? double.infinity + : size.height, child: child, ); } @@ -376,9 +392,10 @@ class SvgIcon extends StatelessWidget { final Color iconColor = switch (icon) { _ when color != null => color!, _ when iconTheme.color != null => iconTheme.color!, - _ => Theme.of(context).brightness == Brightness.light - ? Colors.black - : Colors.white, + _ => + Theme.of(context).brightness == Brightness.light + ? Colors.black + : Colors.white, }; final double dimension = size ?? Theme.of(context).iconTheme.size ?? 24; @@ -396,9 +413,11 @@ class SvgIcon extends StatelessWidget { opticalSize: opticalSize, ); if (url == null) { - log('No icon source | name: ${icon.name} | type: ${icon.type} | ' - 'codepoint: ${icon.codepoint} | fontFamily: ${icon.fontFamily} | ' - 'fontPackage: ${icon.fontPackage}'); + log( + 'No icon source | name: ${icon.name} | type: ${icon.type} | ' + 'codepoint: ${icon.codepoint} | fontFamily: ${icon.fontFamily} | ' + 'fontPackage: ${icon.fontPackage}', + ); return SizedBox.square(dimension: dimension); } @@ -429,8 +448,10 @@ class SvgIconImage extends StatelessWidget { this.color, this.size, this.fit = BoxFit.contain, - }) : assert(url != null || bytes != null, - 'Either url or bytes must be provided'); + }) : assert( + url != null || bytes != null, + 'Either url or bytes must be provided', + ); @override Widget build(BuildContext context) { @@ -446,9 +467,10 @@ class SvgIconImage extends StatelessWidget { final Color iconColor = switch (url) { _ when color != null => color!, _ when iconTheme.color != null => iconTheme.color!, - _ => Theme.of(context).brightness == Brightness.light - ? Colors.black - : Colors.white, + _ => + Theme.of(context).brightness == Brightness.light + ? Colors.black + : Colors.white, }; return SizedBox.square( diff --git a/lib/src/ui/codelessly_context.dart b/lib/src/ui/codelessly_context.dart index 74f96941..48c8c976 100644 --- a/lib/src/ui/codelessly_context.dart +++ b/lib/src/ui/codelessly_context.dart @@ -72,16 +72,16 @@ class CodelesslyContext with ChangeNotifier, Equatable { required this.variables, required this.conditions, required this.layoutID, - }) : _externalComponentBuilders = externalComponentBuilders ?? {}; + }) : _externalComponentBuilders = externalComponentBuilders ?? {}; /// Creates a [CodelesslyContext] with empty an empty map of each property. CodelesslyContext.empty({String? layoutID}) - : _data = {}, - functions = {}, - _externalComponentBuilders = {}, - nodeValues = {}, - variables = {}, - conditions = {}; + : _data = {}, + functions = {}, + _externalComponentBuilders = {}, + nodeValues = {}, + variables = {}, + conditions = {}; @override void dispose() { @@ -150,11 +150,14 @@ class CodelesslyContext with ChangeNotifier, Equatable { // Populate node values with node's values, not action's values. if (connectedNode != null) { addToNodeValues( - connectedNode, - connectedNode.propertyVariables - .where((property) => - action.values.any((value) => property.name == value.name)) - .toList()); + connectedNode, + connectedNode.propertyVariables + .where( + (property) => + action.values.any((value) => property.name == value.name), + ) + .toList(), + ); } case ActionType.setVariant: final action = actionModel as SetVariantAction; @@ -167,7 +170,7 @@ class CodelesslyContext with ChangeNotifier, Equatable { StringValue( name: 'currentVariantId', value: connectedNode.currentVariantId, - ) + ), ]); } default: @@ -184,8 +187,9 @@ class CodelesslyContext with ChangeNotifier, Equatable { final List newValues = []; // Filter out and populate new values. for (final ValueModel value in values) { - if (!currentValues - .any((currentValue) => currentValue.name == value.name)) { + if (!currentValues.any( + (currentValue) => currentValue.name == value.name, + )) { newValues.add(value); } } @@ -216,7 +220,9 @@ class CodelesslyContext with ChangeNotifier, Equatable { // If the value is the same, then the underlying value notifier will not // notify listeners, so we need to return false. if (variable.value.value == newValue) { - log('[CodelesslyContext] Variable with name $name already has the value $newValue.'); + log( + '[CodelesslyContext] Variable with name $name already has the value $newValue.', + ); return false; } variable.value = variable.value.copyWith(value: newValue); diff --git a/lib/src/ui/codelessly_dialog_widget.dart b/lib/src/ui/codelessly_dialog_widget.dart index 002b13d4..ee771c26 100644 --- a/lib/src/ui/codelessly_dialog_widget.dart +++ b/lib/src/ui/codelessly_dialog_widget.dart @@ -38,8 +38,6 @@ class CodelesslyDialogWidget extends StatelessWidget { ); } - return Center( - child: builder(context), - ); + return Center(child: builder(context)); } } diff --git a/lib/src/ui/codelessly_error_screen.dart b/lib/src/ui/codelessly_error_screen.dart index 09e5128b..f4361c57 100644 --- a/lib/src/ui/codelessly_error_screen.dart +++ b/lib/src/ui/codelessly_error_screen.dart @@ -45,10 +45,7 @@ class _CodelesslyErrorScreenState extends State { child: Column( mainAxisSize: MainAxisSize.min, children: [ - Text( - 'Uh oh!', - style: Theme.of(context).textTheme.titleLarge, - ), + Text('Uh oh!', style: Theme.of(context).textTheme.titleLarge), const SizedBox(height: 16), if (widget.errors.length > 1) ...[ Row( @@ -81,8 +78,10 @@ class _CodelesslyErrorScreenState extends State { const SizedBox(height: 16), Container( padding: const EdgeInsets.symmetric(horizontal: 8), - constraints: - const BoxConstraints(maxWidth: 500, maxHeight: 500), + constraints: const BoxConstraints( + maxWidth: 500, + maxHeight: 500, + ), child: SingleChildScrollView( primary: false, padding: EdgeInsets.zero, @@ -145,7 +144,7 @@ class _CodelesslyErrorScreenState extends State { detailsVisible = !detailsVisible; }); }, - ) + ), ], ), const SizedBox(height: 8), @@ -175,7 +174,7 @@ class _CodelesslyErrorScreenState extends State { ], ), ), - ] + ], ], ), ), diff --git a/lib/src/ui/codelessly_widget.dart b/lib/src/ui/codelessly_widget.dart index fcac0c2f..a842ce19 100644 --- a/lib/src/ui/codelessly_widget.dart +++ b/lib/src/ui/codelessly_widget.dart @@ -185,38 +185,38 @@ class CodelesslyWidget extends StatefulWidget { // Additional parameters. this.codelesslyContext, - }) : data = {...data}, - functions = {...functions}, - externalComponentBuilders = {...externalComponentBuilders}, - assert( - ((config ?? - (codelessly ?? Codelessly.instance).config ?? - (controller?.config)) != - null), - controller != null - ? 'You must provide a [config] inside of your [controller] or configure the provided codelessly instance with one.' - : 'You must provide a [config] inside of your CodelesslyWidget or through the Codelessly instance.', - ), - assert( - ((layoutID != null) != (controller != null)) || - ((config ?? - (codelessly ?? Codelessly.instance).config ?? - (controller?.config)))! - .slug != - null, - 'You must provide either a [layoutID] or a [controller]. One must be specified, and both cannot be specified at the same time.' - '\nIf you provided neither, then a slug must be configured in the config.', - ), - assert( - (publishSource == null && controller == null) || - (publishSource != null) != (controller != null), - 'You must provide either an [publishSource] or a [controller]. One must be specified, and both cannot be specified at the same time.', - ), - assert( - (codelessly == null && controller == null) || - (codelessly != null) != (controller != null), - 'You must provide either a [codelessly] or a [controller]. One may be specified, and both cannot be specified at the same time.', - ); + }) : data = {...data}, + functions = {...functions}, + externalComponentBuilders = {...externalComponentBuilders}, + assert( + ((config ?? + (codelessly ?? Codelessly.instance).config ?? + (controller?.config)) != + null), + controller != null + ? 'You must provide a [config] inside of your [controller] or configure the provided codelessly instance with one.' + : 'You must provide a [config] inside of your CodelesslyWidget or through the Codelessly instance.', + ), + assert( + ((layoutID != null) != (controller != null)) || + ((config ?? + (codelessly ?? Codelessly.instance).config ?? + (controller?.config)))! + .slug != + null, + 'You must provide either a [layoutID] or a [controller]. One must be specified, and both cannot be specified at the same time.' + '\nIf you provided neither, then a slug must be configured in the config.', + ), + assert( + (publishSource == null && controller == null) || + (publishSource != null) != (controller != null), + 'You must provide either an [publishSource] or a [controller]. One must be specified, and both cannot be specified at the same time.', + ), + assert( + (codelessly == null && controller == null) || + (codelessly != null) != (controller != null), + 'You must provide either a [codelessly] or a [controller]. One may be specified, and both cannot be specified at the same time.', + ); @override State createState() => _CodelesslyWidgetState(); @@ -286,15 +286,15 @@ class _CodelesslyWidgetState extends State { _effectiveController.initialize(); } - _errorSubscription = ErrorLogger.instance.errorStream.listen( - (ErrorLog error) { - if (error.layoutID == _effectiveController.layoutID) { - setState(() { - _lastError = error; - }); - } - }, - ); + _errorSubscription = ErrorLogger.instance.errorStream.listen(( + ErrorLog error, + ) { + if (error.layoutID == _effectiveController.layoutID) { + setState(() { + _lastError = error; + }); + } + }); if (widget.codelesslyContext != null) { DebugLogger.instance.printInfo( @@ -303,7 +303,8 @@ class _CodelesslyWidgetState extends State { ); } - codelesslyContext = widget.codelesslyContext ?? + codelesslyContext = + widget.codelesslyContext ?? CodelesslyContext( data: widget.data, functions: widget.functions, @@ -338,19 +339,26 @@ class _CodelesslyWidgetState extends State { effectiveLayoutID != null) { // Get the canvas ID from layout group for the current screen size. final newCanvasID = _getCanvasIDForLayoutGroup( - effectiveLayoutID, _effectiveController.publishModel!, screenSize); + effectiveLayoutID, + _effectiveController.publishModel!, + screenSize, + ); if (newCanvasID != canvasID) { // Breakpoint changed. Everything needs to be reloaded. canvasID = newCanvasID; final breakpoint = _effectiveController - .publishModel!.layouts[effectiveLayoutID!]?.breakpoints + .publishModel! + .layouts[effectiveLayoutID!] + ?.breakpoints .firstWhereOrNull((breakpoint) => breakpoint.nodeId == canvasID); // Notify breakpoints listeners. if (breakpoint != null) { - _effectiveController.effectiveCodelessly - .notifyBreakpointsListeners(context, breakpoint); + _effectiveController.effectiveCodelessly.notifyBreakpointsListeners( + context, + breakpoint, + ); } } } @@ -379,8 +387,9 @@ class _CodelesslyWidgetState extends State { SchedulerBinding.instance.addPostFrameCallback((timeStamp) { if (!mounted) return; for (final notifier in codelesslyContext.variables.values) { - notifier.value = - notifier.value.copyWith(value: widget.data[notifier.value.name]); + notifier.value = notifier.value.copyWith( + value: widget.data[notifier.value.name], + ); } }); } @@ -452,7 +461,10 @@ class _CodelesslyWidgetState extends State { /// Returns null if the layout group does not have a canvas for the current /// screen size or if the layout group does not exist. String? _getCanvasIDForLayoutGroup( - String? layoutID, SDKPublishModel model, Size screenSize) { + String? layoutID, + SDKPublishModel model, + Size screenSize, + ) { if (layoutID != null && model.layouts.containsKey(layoutID)) { final SDKPublishLayout? layout = model.layouts[layoutID]; if (layout != null) { @@ -518,15 +530,11 @@ class _CodelesslyWidgetState extends State { if (effectiveLayoutID == null) { final errorLog = ErrorLog( timestamp: DateTime.now(), - message: - 'A layoutID was not specified and the model does not have an entry layoutID specified.', + message: 'A layoutID was not specified and the model does not have an entry layoutID specified.', type: 'layout_not_initialized', ); - return _effectiveErrorBuilder?.call( - context, - errorLog, - ) ?? + return _effectiveErrorBuilder?.call(context, errorLog) ?? CodelesslyErrorScreen( errors: [errorLog], publishSource: _effectiveController.publishSource, @@ -636,9 +644,11 @@ class _CodelesslyWidgetState extends State { return MultiProvider( providers: [ ChangeNotifierProvider.value( - value: codelesslyContext), + value: codelesslyContext, + ), ChangeNotifierProvider.value( - value: _effectiveController), + value: _effectiveController, + ), Provider.value(value: codelessly), ], child: StreamBuilder( @@ -710,10 +720,10 @@ class _NavigationBuilderState extends State<_NavigationBuilder> { SchedulerBinding.instance.addPostFrameCallback((timeStamp) { if (!context.mounted) return; context.read().notifyNavigationListeners( - context, - layoutId: widget.layoutId, - canvasId: widget.canvasId, - ); + context, + layoutId: widget.layoutId, + canvasId: widget.canvasId, + ); }); } diff --git a/lib/src/ui/codelessly_widget_controller.dart b/lib/src/ui/codelessly_widget_controller.dart index 65cffcbc..72a8bb70 100644 --- a/lib/src/ui/codelessly_widget_controller.dart +++ b/lib/src/ui/codelessly_widget_controller.dart @@ -153,11 +153,11 @@ class CodelesslyWidgetController extends ChangeNotifier { this.previewDataManager, this.templateDataManager, this.cacheManager, - }) : assert( - (config ?? (codelessly ?? Codelessly.instance).config) != null, - 'A [config] must be provided. Please provide one either in the constructor of this controller, or in the passed Codelessly instance.', - ), - config = config ?? (codelessly ?? Codelessly.instance).config! { + }) : assert( + (config ?? (codelessly ?? Codelessly.instance).config) != null, + 'A [config] must be provided. Please provide one either in the constructor of this controller, or in the passed Codelessly instance.', + ), + config = config ?? (codelessly ?? Codelessly.instance).config! { // If this controller is configured with its own Codelessly config but // wants to use the global instance, and the global instance is already // configured with a different config, then we need to create a custom @@ -227,8 +227,10 @@ class CodelesslyWidgetController extends ChangeNotifier { /// Listens to the SDK's status. If the SDK is done, then we can start /// listening to the data manager's status for layout updates. void initialize({String? layoutID}) { - DebugLogger.instance - .printFunction('initialize(layoutID: $layoutID)', name: name); + DebugLogger.instance.printFunction( + 'initialize(layoutID: $layoutID)', + name: name, + ); if (layoutID != null) { this.layoutID = layoutID; } @@ -255,8 +257,10 @@ class CodelesslyWidgetController extends ChangeNotifier { // here. if (isGlobalInstance || createdIntrinsicCodelessly) { if (status is CEmpty) { - DebugLogger.instance - .printInfo('Codelessly SDK is idle, configuring...', name: name); + DebugLogger.instance.printInfo( + 'Codelessly SDK is idle, configuring...', + name: name, + ); effectiveCodelessly.configure( config: config, authManager: authManager, @@ -269,8 +273,9 @@ class CodelesslyWidgetController extends ChangeNotifier { status = effectiveCodelessly.status; if (status is CConfigured) { DebugLogger.instance.printInfo( - 'Codelessly SDK is configured, initializing...', - name: name); + 'Codelessly SDK is configured, initializing...', + name: name, + ); effectiveCodelessly.initialize(); } } @@ -283,79 +288,93 @@ class CodelesslyWidgetController extends ChangeNotifier { stackTrace: str, ); - effectiveCodelessly.status = - CStatus.error('Failed to initialize widget controller'); + effectiveCodelessly.status = CStatus.error( + 'Failed to initialize widget controller', + ); } // First event. if (effectiveCodelessly.status case CLoaded() || CLoading()) { - if (effectiveCodelessly.status - case CLoading(state: CLoadingState state)) { + if (effectiveCodelessly.status case CLoading( + state: CLoadingState state, + )) { // Listen to data manager after it has been created. If it hasn't been // created yet, Firebase may still be initializing. DebugLogger.instance.printInfo( - '[${this.layoutID}]: Codelessly SDK is loading with step $state.', - name: name); + '[${this.layoutID}]: Codelessly SDK is loading with step $state.', + name: name, + ); if (state.hasPassed(CLoadingState.createdManagers)) { DebugLogger.instance.printInfo( - '[${this.layoutID}]: Checking layout because it passed the created managers step.', - name: name); + '[${this.layoutID}]: Checking layout because it passed the created managers step.', + name: name, + ); _checkLayout(); } else { DebugLogger.instance.printInfo( - '[${this.layoutID}]: Waiting for data manager to be created. Skipping for now.', - name: name); + '[${this.layoutID}]: Waiting for data manager to be created. Skipping for now.', + name: name, + ); } } else { DebugLogger.instance.printInfo( - '[${this.layoutID}]: Codelessly SDK is already loaded, checking layout.', - name: name); + '[${this.layoutID}]: Codelessly SDK is already loaded, checking layout.', + name: name, + ); _checkLayout(); } } DebugLogger.instance.printInfo( - '[${this.layoutID}]: Listening to sdk status stream.', - name: name); + '[${this.layoutID}]: Listening to sdk status stream.', + name: name, + ); DebugLogger.instance.printInfo( - '[${this.layoutID}]: Initial sdk status is: ${effectiveCodelessly.status}', - name: name); + '[${this.layoutID}]: Initial sdk status is: ${effectiveCodelessly.status}', + name: name, + ); _sdkStatusListener?.cancel(); _sdkStatusListener = effectiveCodelessly.statusStream.listen((status) { DebugLogger.instance.printInfo( - '[${this.layoutID}]: (Listener) SDK status changed to $status.', - name: name); + '[${this.layoutID}]: (Listener) SDK status changed to $status.', + name: name, + ); if (status case CLoaded() || CLoading()) { if (status case CLoading(state: CLoadingState state)) { DebugLogger.instance.printInfo( - '[${this.layoutID}]: (Listener) Codelessly SDK is loading with step $state.', - name: name); + '[${this.layoutID}]: (Listener) Codelessly SDK is loading with step $state.', + name: name, + ); // Listen to data manager after it has been created. If it hasn't been // created yet, Firebase may still be initializing. if (state.hasPassed(CLoadingState.createdManagers)) { DebugLogger.instance.printInfo( - '[${this.layoutID}]: (Listener) Checking layout because it passed the created managers step.', - name: name); + '[${this.layoutID}]: (Listener) Checking layout because it passed the created managers step.', + name: name, + ); _checkLayout(); } else { DebugLogger.instance.printInfo( - '[${this.layoutID}]: (Listener) Waiting for data manager to be created. Skipping for now.', - name: name); + '[${this.layoutID}]: (Listener) Waiting for data manager to be created. Skipping for now.', + name: name, + ); } } else { DebugLogger.instance.printInfo( - '[${this.layoutID}]: (Listener) Codelessly SDK is already loaded, checking layout.', - name: name); + '[${this.layoutID}]: (Listener) Codelessly SDK is already loaded, checking layout.', + name: name, + ); _checkLayout(); } } else { DebugLogger.instance.printInfo( - '[${this.layoutID}]: (Listener) Codelessly SDK is not loaded, skipping layout check.', - name: name); + '[${this.layoutID}]: (Listener) Codelessly SDK is not loaded, skipping layout check.', + name: name, + ); } }); } @@ -383,39 +402,42 @@ class CodelesslyWidgetController extends ChangeNotifier { stackTrace: str, ); - effectiveCodelessly.status = - CStatus.error('Failed to initialize data manager'); + effectiveCodelessly.status = CStatus.error( + 'Failed to initialize data manager', + ); }); } - // If the config has a slug specified and the data manager doesn't have // a publish model yet, then we need to fetch the publish model from the // data manager. else if (config.slug != null && dataManager.publishModel == null) { DebugLogger.instance.printInfo( - '[$layoutID]: (Check) A slug is specified and publish model is null.', - name: name); + '[$layoutID]: (Check) A slug is specified and publish model is null.', + name: name, + ); DebugLogger.instance.printInfo( - '[$layoutID]: (Check) Fetching complete publish bundle from data manager.', - name: name); + '[$layoutID]: (Check) Fetching complete publish bundle from data manager.', + name: name, + ); dataManager .fetchCompletePublishBundle( - slug: config.slug!, - source: publishSource, - ) + slug: config.slug!, + source: publishSource, + ) .catchError((error, str) { - ErrorLogger.instance.captureException( - error, - message: 'Failed to fetch publish bundle', - type: 'publish_bundle_fetch_failed', - layoutID: layoutID, - stackTrace: str, - ); - - effectiveCodelessly.status = - CStatus.error('Failed to fetch publish bundle'); - return false; - }); + ErrorLogger.instance.captureException( + error, + message: 'Failed to fetch publish bundle', + type: 'publish_bundle_fetch_failed', + layoutID: layoutID, + stackTrace: str, + ); + + effectiveCodelessly.status = CStatus.error( + 'Failed to fetch publish bundle', + ); + return false; + }); } // DataManager is initialized or downloading a publish bundle. If the // layoutID is not null, then we need to signal to the data manager that we @@ -425,39 +447,44 @@ class CodelesslyWidgetController extends ChangeNotifier { // taking care of this layout and we just need to tell it to prioritize it. else if (layoutID != null) { DebugLogger.instance.printInfo( - '[$layoutID]: (Check) Queuing layout [$layoutID] in data manager.', - name: name); + '[$layoutID]: (Check) Queuing layout [$layoutID] in data manager.', + name: name, + ); DebugLogger.instance.printInfo( - '[$layoutID]: (Check) Using publish source $publishSource.', - name: name); + '[$layoutID]: (Check) Using publish source $publishSource.', + name: name, + ); dataManager .queueLayout(layoutID: layoutID!, prioritize: true) .catchError((error, str) { - ErrorLogger.instance.captureException( - error, - message: 'Failed to queue layout', - type: 'layout_queue_failed', - layoutID: layoutID, - stackTrace: str, - ); - - // Set error status - effectiveCodelessly.status = CStatus.error('Failed to queue layout'); - }); + ErrorLogger.instance.captureException( + error, + message: 'Failed to queue layout', + type: 'layout_queue_failed', + layoutID: layoutID, + stackTrace: str, + ); + + // Set error status + effectiveCodelessly.status = CStatus.error( + 'Failed to queue layout', + ); + }); } - // At this point in the execution, layoutID is null, slug is not specified. // Preloading must be true, so this controller can only wait... else { if (layoutID != null) { DebugLogger.instance.printInfo( - '[$layoutID]: (Check) LayoutID specified, but preload is set to ${config.preload}, skipping to let data manager to download everything', - name: name); + '[$layoutID]: (Check) LayoutID specified, but preload is set to ${config.preload}, skipping to let data manager to download everything', + name: name, + ); } else { DebugLogger.instance.printInfo( - '[$layoutID]: (Check) LayoutID is null, skipping to let data manager to download everything.', - name: name); + '[$layoutID]: (Check) LayoutID is null, skipping to let data manager to download everything.', + name: name, + ); } } } catch (e, str) { diff --git a/lib/src/ui/draggable_scroll_configuration.dart b/lib/src/ui/draggable_scroll_configuration.dart index 89cb0c4a..5a272639 100644 --- a/lib/src/ui/draggable_scroll_configuration.dart +++ b/lib/src/ui/draggable_scroll_configuration.dart @@ -4,10 +4,7 @@ import 'package:flutter/material.dart'; class DraggableScrollConfiguration extends StatelessWidget { final Widget child; - const DraggableScrollConfiguration({ - super.key, - required this.child, - }); + const DraggableScrollConfiguration({super.key, required this.child}); @override Widget build(BuildContext context) { diff --git a/lib/src/ui/layout_builder.dart b/lib/src/ui/layout_builder.dart index 1e416896..fe4da449 100644 --- a/lib/src/ui/layout_builder.dart +++ b/lib/src/ui/layout_builder.dart @@ -57,9 +57,7 @@ class _CodelesslyLayoutRetrieverState extends State { return SizedBox( width: widget.bounds.width, height: widget.bounds.height, - child: CodelesslyWidget( - controller: controller, - ), + child: CodelesslyWidget(controller: controller), ); } } @@ -115,17 +113,22 @@ class _CodelesslyLayoutBuilderState extends State { /// delegates the node lookup to it. late final PassiveNodeTransformerManager transformerManager = PassiveNodeTransformerManager( - nodeRegistry.getNodeByID, - (context, bounds, pageID, layoutID, canvasID) => - widget.layoutRetrievalBuilder - ?.call(context, bounds, pageID, layoutID, canvasID) ?? - CodelesslyLayoutRetriever( - controller: widget.controller, - pageID: pageID, - layoutID: layoutID, - bounds: bounds, - ), - ); + nodeRegistry.getNodeByID, + (context, bounds, pageID, layoutID, canvasID) => + widget.layoutRetrievalBuilder?.call( + context, + bounds, + pageID, + layoutID, + canvasID, + ) ?? + CodelesslyLayoutRetriever( + controller: widget.controller, + pageID: pageID, + layoutID: layoutID, + bounds: bounds, + ), + ); CanvasNode get canvasNode => widget.layout.canvases[widget.canvasId]![widget.canvasId] as CanvasNode; @@ -158,8 +161,8 @@ class _CodelesslyLayoutBuilderState extends State { /// Loads the layout into the [nodeRegistry] and updates the /// [CodelesslyContext] with the node values. void loadLayout(BuildContext context) { - final CodelesslyContext codelesslyContext = - context.read(); + final CodelesslyContext codelesslyContext = context + .read(); nodeRegistry.clear(); nodeRegistry.setNodes(widget.layout.canvases[widget.canvasId]!); final List allNodes = nodeRegistry.getNodes().values.toList(); @@ -173,8 +176,8 @@ class _CodelesslyLayoutBuilderState extends State { .toList(); for (final node in nodesWithInternalValue) { final ValueModel? value = node.propertyVariables.firstWhereOrNull( - (property) => - property.name == 'value' || property.name == 'inputValue'); + (property) => property.name == 'value' || property.name == 'inputValue', + ); if (value == null) continue; codelesslyContext.addToNodeValues(node, [value]); } @@ -187,7 +190,9 @@ class _CodelesslyLayoutBuilderState extends State { for (final Reaction reaction in (node as ReactionMixin).reactions) { final ActionModel actionModel = reaction.action; codelesslyContext.handleActionConnections( - actionModel, nodeRegistry.getNodes()); + actionModel, + nodeRegistry.getNodes(), + ); } } @@ -198,7 +203,9 @@ class _CodelesslyLayoutBuilderState extends State { for (final Reaction reaction in reactiveChild.reactions) { final ActionModel actionModel = reaction.action; codelesslyContext.handleActionConnections( - actionModel, nodeRegistry.getNodes()); + actionModel, + nodeRegistry.getNodes(), + ); } } } @@ -207,23 +214,25 @@ class _CodelesslyLayoutBuilderState extends State { final Map variablesMap = publishModel.variables[widget.layout.id]?.variables[widget.canvasId] ?? - {}; + {}; // Clear all the variables that are not api variables. We persist api // variables across canvases from the same layout group so that the // api request is not made again. - codelesslyContext.variables - .removeWhere((key, value) => !value.value.isApiVariable); + codelesslyContext.variables.removeWhere( + (key, value) => !value.value.isApiVariable, + ); for (final variable in variablesMap.values) { // Override default values of variables with values provided in data. - final notifier = Observable(variable.copyWith( - value: codelesslyContext.data[variable.name], - )); + final notifier = Observable( + variable.copyWith(value: codelesslyContext.data[variable.name]), + ); codelesslyContext.variables[variable.id] = notifier; } - final conditions = publishModel - .conditions[widget.layout.id]?.conditions[widget.canvasId] ?? + final conditions = + publishModel.conditions[widget.layout.id]?.conditions[widget + .canvasId] ?? {}; codelesslyContext.conditions.clear(); codelesslyContext.conditions.addAll(conditions); @@ -248,14 +257,15 @@ class _CodelesslyLayoutBuilderState extends State { .where((action) => action.enabled && action.apiId != null) .toList(); - final CodelesslyContext codelesslyContext = - context.read(); + final CodelesslyContext codelesslyContext = context + .read(); final apisMap = context.read().dataManager.publishModel!.apis; for (final api in apisMap.values) { - final ApiCallAction? canvasAction = - apiCallActions.firstWhereOrNull((action) => action.apiId == api.id); + final ApiCallAction? canvasAction = apiCallActions.firstWhereOrNull( + (action) => action.apiId == api.id, + ); final variableName = apiNameToVariableName(api.name); @@ -263,17 +273,17 @@ class _CodelesslyLayoutBuilderState extends State { ? ApiResponseVariableUtils.loading(api.url) : ApiResponseVariableUtils.idle(api.url); - final existingVariable = - codelesslyContext.variableNamesMap()[variableName]; + final existingVariable = codelesslyContext + .variableNamesMap()[variableName]; final RuntimeVariableData variable = existingVariable is RuntimeVariableData - ? existingVariable - : RuntimeVariableData( - name: variableName, - type: VariableType.map, - value: data, - ); + ? existingVariable + : RuntimeVariableData( + name: variableName, + type: VariableType.map, + value: data, + ); // Override default values of variables with values provided in data. final notifier = Observable(variable); @@ -296,11 +306,11 @@ class _CodelesslyLayoutBuilderState extends State { (_) {}, onError: (Object e, StackTrace stackTrace) => ErrorLogger.instance.captureException( - e, - message: 'onLoad API call failed', - type: 'api_call_failed', - stackTrace: stackTrace, - ), + e, + message: 'onLoad API call failed', + type: 'api_call_failed', + stackTrace: stackTrace, + ), ), ); } @@ -316,14 +326,15 @@ class _CodelesslyLayoutBuilderState extends State { .where((action) => action.enabled && action.variable != null) .toList(); - final CodelesslyContext codelesslyContext = - context.read(); + final CodelesslyContext codelesslyContext = context + .read(); for (final action in loadFromStorageActions) { final data = CloudDatabaseVariableUtils.idle(); - final variable = - codelesslyContext.findVariableByName(action.variable!.name); + final variable = codelesslyContext.findVariableByName( + action.variable!.name, + ); if (variable == null) { DebugLogger.instance.printInfo( @@ -345,8 +356,9 @@ class _CodelesslyLayoutBuilderState extends State { data: mediaQuery.copyWith( textScaler: config.maxTextScaleFactor <= 1 ? TextScaler.noScaling - : mediaQuery.textScaler - .clamp(maxScaleFactor: config.maxTextScaleFactor), + : mediaQuery.textScaler.clamp( + maxScaleFactor: config.maxTextScaleFactor, + ), ), child: Theme( data: config.useHostTheme ? Theme.of(context) : ThemeData(), diff --git a/lib/src/utils/debouncer.dart b/lib/src/utils/debouncer.dart index 6e9f7148..78f059b6 100644 --- a/lib/src/utils/debouncer.dart +++ b/lib/src/utils/debouncer.dart @@ -11,14 +11,14 @@ final class DeBouncer { /// Allows to create an instance with optional [Duration] with /// immediateFirstRun set to false. See [immediateFirstRun] for more details. DeBouncer([Duration? duration]) - : duration = duration ?? const Duration(milliseconds: 300), - immediateFirstRun = false; + : duration = duration ?? const Duration(milliseconds: 300), + immediateFirstRun = false; /// Allows to create an instance with optional [Duration] with /// immediateFirstRun set to true. See [immediateFirstRun] for more details. DeBouncer.immediate([Duration? duration]) - : duration = duration ?? const Duration(milliseconds: 300), - immediateFirstRun = true; + : duration = duration ?? const Duration(milliseconds: 300), + immediateFirstRun = true; /// de-bounce period. Default is 300 milliseconds. /// It will ignore any calls to [run] until [duration] has passed since the @@ -191,6 +191,7 @@ final DeBouncer debouncer = DeBouncer(); /// /// This will run the action immediately for the first call and then it will /// wait for 300 milliseconds to run the next call if there's any. -Future debounce(DeBounceAction action, - {bool immediateFirstRun = false}) => - debouncer.run(action, immediateFirstRun: immediateFirstRun); +Future debounce( + DeBounceAction action, { + bool immediateFirstRun = false, +}) => debouncer.run(action, immediateFirstRun: immediateFirstRun); diff --git a/lib/src/utils/extensions.dart b/lib/src/utils/extensions.dart index 2f52c070..b072c4d1 100644 --- a/lib/src/utils/extensions.dart +++ b/lib/src/utils/extensions.dart @@ -94,7 +94,8 @@ extension M3NavigationDestinationLabelBehaviorHelper return NavigationDestinationLabelBehavior.onlyShowSelected; case MaterialNavigationBarLabelBehavior.onlyShowUnselected: throw UnsupportedError( - 'MaterialNavigationBarLabelBehavior.onlyShowUnselected is not supported for material 2.'); + 'MaterialNavigationBarLabelBehavior.onlyShowUnselected is not supported for material 2.', + ); } } } @@ -193,8 +194,10 @@ extension BaseNodeHelper on BaseNode { } } - RectC rectFromBoundary(NodeBoundaryType type, - {required PositioningSpace space}) { + RectC rectFromBoundary( + NodeBoundaryType type, { + required PositioningSpace space, + }) { switch (space) { case PositioningSpace.local: return localRectFromBoundary(type); @@ -203,8 +206,10 @@ extension BaseNodeHelper on BaseNode { } } - Vec posFromBoundary(NodeBoundaryType type, - {required PositioningSpace space}) { + Vec posFromBoundary( + NodeBoundaryType type, { + required PositioningSpace space, + }) { switch (space) { case PositioningSpace.global: return globalPosFromBoundary(type); @@ -240,7 +245,7 @@ extension ScrollPhysicsHelper on ScrollPhysicsC { ScrollPhysicsC.rangeMaintainingScrollPhysics => const RangeMaintainingScrollPhysics(), ScrollPhysicsC.neverScrollableScrollPhysics => - const NeverScrollableScrollPhysics() + const NeverScrollableScrollPhysics(), }; if (shouldAlwaysScroll) { return AlwaysScrollableScrollPhysics(parent: parent); @@ -294,11 +299,11 @@ extension RowColumnTypeHelper on RowColumnType { extension CornerRadiusHelper on CornerRadius { flutter.BorderRadius get borderRadius => flutter.BorderRadius.only( - topLeft: Radius.elliptical(tl.x, tl.y), - topRight: Radius.elliptical(tr.x, tr.y), - bottomLeft: Radius.elliptical(bl.x, bl.y), - bottomRight: Radius.elliptical(br.x, br.y), - ); + topLeft: Radius.elliptical(tl.x, tl.y), + topRight: Radius.elliptical(tr.x, tr.y), + bottomLeft: Radius.elliptical(bl.x, bl.y), + bottomRight: Radius.elliptical(br.x, br.y), + ); } extension ColorHelper on flutter.Color { @@ -331,20 +336,20 @@ extension ColorHelper on flutter.Color { String get hex => toARGB32().toRadixString(16).padLeft(8, '0').toUpperCase(); PaintModel toPaint([String? id]) => PaintModel.solid( - id: id ?? generateId(), - visible: true, - opacity: a, - color: toColorRGB(), - ); + id: id ?? generateId(), + visible: true, + opacity: a, + color: toColorRGB(), + ); Effect toShadow() => Effect( - id: generateId(), - type: EffectType.dropShadow, - color: colorToRGBA(this), - offset: const Vec(0, 4), - radius: 8, - spread: 0, - ); + id: generateId(), + type: EffectType.dropShadow, + color: colorToRGBA(this), + offset: const Vec(0, 4), + radius: 8, + spread: 0, + ); } extension BrightnessModelHelper on BrightnessModel { @@ -372,10 +377,11 @@ extension ColorRGBAHelper on ColorRGBA { ColorRGB toRGB() => toFlutterColor().colorRGB; PaintModel toPaint([String? id]) => PaintModel.solid( - id: id ?? generateId(), - visible: true, - opacity: toFlutterColor().a, - color: toRGB()); + id: id ?? generateId(), + visible: true, + opacity: toFlutterColor().a, + color: toRGB(), + ); } extension PaintModelExt on PaintModel { @@ -396,7 +402,11 @@ extension PaintModelExt on PaintModel { extension ColorRGBHelper on ColorRGB { flutter.Color get flutterColor => flutter.Color.fromRGBO( - (r * 255).toInt(), (g * 255).toInt(), (b * 255).toInt(), 1); + (r * 255).toInt(), + (g * 255).toInt(), + (b * 255).toInt(), + 1, + ); Color toFlutterColor({required double opacity}) { var ir = (r * 255).toInt(); @@ -412,11 +422,11 @@ extension ColorRGBHelper on ColorRGB { extension BoxConstraintsModelHelper on BoxConstraintsModel { flutter.BoxConstraints get flutterConstraints => flutter.BoxConstraints( - minWidth: minWidth ?? 0.0, - maxWidth: maxWidth ?? double.infinity, - minHeight: minHeight ?? 0.0, - maxHeight: maxHeight ?? double.infinity, - ); + minWidth: minWidth ?? 0.0, + maxWidth: maxWidth ?? double.infinity, + minHeight: minHeight ?? 0.0, + maxHeight: maxHeight ?? double.infinity, + ); } extension AlignmentDataHelper on AlignmentData { @@ -741,14 +751,14 @@ extension FitsExtension on BaseNode { edgePins.inner(parent).isBothExpanded || isBothExpanded; bool isWrapOnAxis(AxisC axis) => switch (axis) { - AxisC.horizontal => isHorizontalWrap, - AxisC.vertical => isVerticalWrap, - }; + AxisC.horizontal => isHorizontalWrap, + AxisC.vertical => isVerticalWrap, + }; bool doesFlexOnAxis(AxisC axis) => switch (axis) { - AxisC.horizontal => isHorizontalExpanded || isHorizontalFlexible, - AxisC.vertical => isVerticalExpanded || isVerticalFlexible, - }; + AxisC.horizontal => isHorizontalExpanded || isHorizontalFlexible, + AxisC.vertical => isVerticalExpanded || isVerticalFlexible, + }; } extension RowColumnMixinExtensions on RowColumnMixin { @@ -781,11 +791,11 @@ extension RowColumnMixinExtensions on RowColumnMixin { extension EdgePinsExtensions on EdgePinsModel { EdgePinsModel inner(BaseNode? parent) => EdgePinsModel( - left: effectiveLeft(parent), - top: effectiveTop(parent), - right: effectiveRight(parent), - bottom: effectiveBottom(parent), - ); + left: effectiveLeft(parent), + top: effectiveTop(parent), + right: effectiveRight(parent), + bottom: effectiveBottom(parent), + ); double? effectiveLeft(BaseNode? parent) { if (parent == null || left == null) return left; @@ -853,11 +863,11 @@ extension FitExtension on Fit { extension BoxFitExt on BoxFit { bool get supportsAlignment => { - BoxFit.fitHeight, - BoxFit.fitWidth, - BoxFit.scaleDown, - BoxFit.none, - }.contains(this); + BoxFit.fitHeight, + BoxFit.fitWidth, + BoxFit.scaleDown, + BoxFit.none, + }.contains(this); } extension AlignmentModelExtensions on AlignmentModel { @@ -984,8 +994,10 @@ extension FontNameExtensions on FontName { // Deprecated // Convert 'extra-bold' or 'extra bold' into 'extrabold'. - final String style = - this.style.toLowerCase().replaceAll('-', '').replaceAll(' ', ''); + final String style = this.style + .toLowerCase() + .replaceAll('-', '') + .replaceAll(' ', ''); // String might contain 'italic', which is why it uses [contains] for the // comparison. @@ -1166,8 +1178,10 @@ extension IconModelExt on IconModel { final IconModel icon = this; if (icon is MaterialIcon) { // e.g. https://fonts.gstatic.com/s/i/materialiconsoutlined/home/v16/24px.svg - final String style = - icon.style.styleName.toLowerCase().replaceAll(' ', ''); + final String style = icon.style.styleName.toLowerCase().replaceAll( + ' ', + '', + ); return 'https://fonts.gstatic.com/s/i/materialicons$style/' '${icon.name.toLowerCase()}/v${icon.version ?? 1}/24px.svg'; } @@ -1185,8 +1199,9 @@ extension IconModelExt on IconModel { /// The path segment naming a Material Symbols SVG variant, e.g. /// `wght700grad200fill1`, or `default` when every axis is at its default. String materialSymbolVariant({double? fill, double? weight, double? grade}) { - final int w = - weight == null ? 400 : ((weight / 100).round() * 100).clamp(100, 700); + final int w = weight == null + ? 400 + : ((weight / 100).round() * 100).clamp(100, 700); final int g = grade == null ? 0 : _nearest(grade, const [-25, 0, 200]); final bool filled = (fill ?? 0) >= 0.5; final String variant = [ @@ -1197,9 +1212,8 @@ String materialSymbolVariant({double? fill, double? weight, double? grade}) { return variant.isEmpty ? 'default' : variant; } -int _nearest(double value, List options) => options.reduce( - (a, b) => (a - value).abs() <= (b - value).abs() ? a : b, - ); +int _nearest(double value, List options) => + options.reduce((a, b) => (a - value).abs() <= (b - value).abs() ? a : b); extension MaterialIconExt on MaterialIcon { String? get flutterID => flutterIconNames[styledName]; @@ -1288,10 +1302,7 @@ extension VisualDensityModelExt on VisualDensityModel { vertical: VisualDensity.maximumDensity, ); case VisualDensityType.custom: - return VisualDensity( - horizontal: horizontal, - vertical: vertical, - ); + return VisualDensity(horizontal: horizontal, vertical: vertical); } } } @@ -1335,7 +1346,7 @@ extension StringExt on String { String get capitalized => characters.isEmpty ? this : characters.first.toUpperCase() + - characters.skip(1).string.toLowerCase(); + characters.skip(1).string.toLowerCase(); /// Whether the string contains all the valid variable paths inside it. /// This will also evaluate the path inside the ${} to be valid. @@ -1343,9 +1354,9 @@ extension StringExt on String { String unwrapVariablePath() => isValidVariablePath ? variablePathRegex - .allMatches(this) - .map((match) => match.namedGroup('value')) - .join(' ') + .allMatches(this) + .map((match) => match.namedGroup('value')) + .join(' ') : this; /// Whether the string contains a variable path that is not checked for @@ -1481,11 +1492,13 @@ extension StringExt on String { /// 2. [https://www.youtube.com/embed/] /// 3. [https://youtube.com/embed/] RegExp youtubeShareUrlRegex = RegExp( - 'https://(www.)?(youtu.be|((youtube.com)(?:/embed)))/(?[a-zA-Z_0-9]+)'); + 'https://(www.)?(youtu.be|((youtube.com)(?:/embed)))/(?[a-zA-Z_0-9]+)', +); /// Can match: [https://www.youtube.com/watch?v=] RegExp youtubeWatchUrlRegex = RegExp( - '^https://(www.)?(youtube.com/watch)?.*v=(?[a-zA-Z_0-9]+)'); + '^https://(www.)?(youtube.com/watch)?.*v=(?[a-zA-Z_0-9]+)', +); RegExp vimeoUrlRegex = RegExp('^https://vimeo.com/(?[a-zA-Z0-9]+)'); @@ -1553,8 +1566,9 @@ extension BuildContextExt on BuildContext { T? getNodeValue(String nodeId, String key) { return nodeValues(nodeId) - .firstWhereOrNull((value) => value.name == key) - ?.value as T?; + .firstWhereOrNull((value) => value.name == key) + ?.value + as T?; } } @@ -1620,15 +1634,19 @@ extension ActionModelExt on ActionModel { final NavigationAction action = this as NavigationAction; strings.addAll([action.destinationId]); for (final params in action.params.entries) { - strings - .addAll([params.key, if (params.value is String) params.value]); + strings.addAll([ + params.key, + if (params.value is String) params.value, + ]); } case ActionType.showDialog: final ShowDialogAction action = this as ShowDialogAction; strings.addAll([action.destinationId]); for (final params in action.params.entries) { - strings - .addAll([params.key, if (params.value is String) params.value]); + strings.addAll([ + params.key, + if (params.value is String) params.value, + ]); } case ActionType.link: final LinkAction action = this as LinkAction; @@ -1648,10 +1666,13 @@ extension ActionModelExt on ActionModel { } case ActionType.setValue: final SetValueAction action = this as SetValueAction; - strings.addAll(action.values - .map( - (value) => value.value is String ? value.value as String : null) - .nonNulls); + strings.addAll( + action.values + .map( + (value) => value.value is String ? value.value as String : null, + ) + .nonNulls, + ); case ActionType.setVariable: final SetVariableAction action = this as SetVariableAction; strings.addAll([action.index, action.mapKey, action.newValue]); @@ -1666,8 +1687,12 @@ extension ActionModelExt on ActionModel { strings.addAll(action.parameters.values); case ActionType.setStorage: final SetStorageAction action = this as SetStorageAction; - strings - .addAll([action.key, action.index, action.mapKey, action.newValue]); + strings.addAll([ + action.key, + action.index, + action.mapKey, + action.newValue, + ]); case ActionType.setCloudStorage: final SetCloudStorageAction action = this as SetCloudStorageAction; switch (action.subAction) { @@ -1679,10 +1704,7 @@ extension ActionModelExt on ActionModel { if (!action.useRawValue) action.newValue, ]); case RemoveDocumentSubAction action: - strings.addAll([ - action.path, - action.documentId, - ]); + strings.addAll([action.path, action.documentId]); case UpdateDocumentSubAction action: strings.addAll([ action.path, @@ -1698,20 +1720,26 @@ extension ActionModelExt on ActionModel { final LoadFromCloudStorageAction action = this as LoadFromCloudStorageAction; strings.addAll([action.path, action.documentId]); - strings.addAll(action.whereFilters - .map((filter) => filter.getUsedVariableNames()) - .expand((e) => e)); - strings.addAll(action.orderByFilters - .map((filter) => filter.getUsedVariableNames()) - .expand((e) => e)); + strings.addAll( + action.whereFilters + .map((filter) => filter.getUsedVariableNames()) + .expand((e) => e), + ); + strings.addAll( + action.orderByFilters + .map((filter) => filter.getUsedVariableNames()) + .expand((e) => e), + ); } return strings .map((text) => VariableMatch.parseAll(text)) .expand((matches) => matches) .map((match) => match.name) - .where((name) => - includePredefined || !predefinedVariableNames.contains(name)) + .where( + (name) => + includePredefined || !predefinedVariableNames.contains(name), + ) .toSet(); } } @@ -1734,18 +1762,21 @@ extension BaseConditionExt on BaseCondition { final condition = this; if (condition is Condition) { final condition = this as Condition; - return condition.actions - .any((action) => action.hasProperty(nodeId, name)); + return condition.actions.any( + (action) => action.hasProperty(nodeId, name), + ); } if (condition is ConditionGroup) { return condition.ifCondition.hasProperty(nodeId, name) || condition.elseCondition?.hasProperty(nodeId, name) == true || - condition.elseIfConditions - .any((condition) => condition.hasProperty(nodeId, name)); + condition.elseIfConditions.any( + (condition) => condition.hasProperty(nodeId, name), + ); } if (condition is ElseCondition) { - return condition.actions - .any((action) => action.hasProperty(nodeId, name)); + return condition.actions.any( + (action) => action.hasProperty(nodeId, name), + ); } return false; } @@ -1759,8 +1790,9 @@ extension BaseConditionExt on BaseCondition { if (condition is ConditionGroup) { return condition.ifCondition.hasNode(nodeId) || condition.elseCondition?.hasNode(nodeId) == true || - condition.elseIfConditions - .any((condition) => condition.hasNode(nodeId)); + condition.elseIfConditions.any( + (condition) => condition.hasNode(nodeId), + ); } if (condition is ElseCondition) { return condition.actions.any((action) => action.hasNode(nodeId)); @@ -1776,15 +1808,17 @@ extension BaseConditionExt on BaseCondition { /// Returns all the variables used in this condition except the predefined /// variables. Returns names of the variables. Set getReactiveVariables() => - accept>(ConditionVariablesVisitor( + accept>( + ConditionVariablesVisitor( excludedVariableNames: {...predefinedVariableNames} - ..removeAll(predefinedListenableVariableNames))) ?? + ..removeAll(predefinedListenableVariableNames), + ), + ) ?? {}; /// [variablesOverrides] is a map of variable name to variable value. - R? evaluate(ScopedValues scopedValues) => accept( - ConditionEvaluator(scopedValues: scopedValues), - ); + R? evaluate(ScopedValues scopedValues) => + accept(ConditionEvaluator(scopedValues: scopedValues)); void prettyPrint() => ConditionPrinter().printCondition(this); @@ -1795,29 +1829,32 @@ extension BaseConditionExt on BaseCondition { final condition = this; return switch (condition) { ElseCondition() => condition.copyWith( - actions: condition.actions - .map((action) => action.withUpdatedNodeId(oldNodeId, newNodeId)) - .toList()), + actions: condition.actions + .map((action) => action.withUpdatedNodeId(oldNodeId, newNodeId)) + .toList(), + ), Condition() => condition.copyWith( - actions: condition.actions - .map((action) => action.withUpdatedNodeId(oldNodeId, newNodeId)) - .toList()), + actions: condition.actions + .map((action) => action.withUpdatedNodeId(oldNodeId, newNodeId)) + .toList(), + ), ConditionGroup() => condition.copyWith( - elseCondition: condition.elseCondition?.withUpdatedNodeId( - oldNodeId, - newNodeId, - ) as ElseCondition?, - elseIfConditions: condition.elseIfConditions - .map((condition) => condition.withUpdatedNodeId( - oldNodeId, - newNodeId, - ) as Condition) - .toList(), - ifCondition: condition.ifCondition.withUpdatedNodeId( - oldNodeId, - newNodeId, - ) as Condition, - ), + elseCondition: condition.elseCondition?.withUpdatedNodeId( + oldNodeId, + newNodeId, + ) as ElseCondition?, + elseIfConditions: condition.elseIfConditions + .map( + (condition) => + condition.withUpdatedNodeId(oldNodeId, newNodeId) + as Condition, + ) + .toList(), + ifCondition: condition.ifCondition.withUpdatedNodeId( + oldNodeId, + newNodeId, + ) as Condition, + ), }; } } @@ -1866,8 +1903,9 @@ extension ConditionsMapExt on Map { extension CanvasConditionsExt on CanvasConditions { bool hasCondition(String nodeId, String name) { - return conditions.values - .any((condition) => condition.hasProperty(nodeId, name)); + return conditions.values.any( + (condition) => condition.hasProperty(nodeId, name), + ); } } @@ -1893,8 +1931,8 @@ extension VariantIterable on Iterable { extension IterableExt on Iterable { Map mapBy(K Function(V) key) => { - for (final value in this) key(value): value, - }; + for (final value in this) key(value): value, + }; int count(V item) => where((e) => e == item).length; } @@ -1910,25 +1948,23 @@ extension TabBarIndicatorSizeExtensions on TabBarIndicatorSizeEnum { extension ListTileTitleAlignmentCExtensions on ListTileTitleAlignmentC { ListTileTitleAlignment toFlutter() => switch (this) { - ListTileTitleAlignmentC.threeLine => ListTileTitleAlignment.threeLine, - ListTileTitleAlignmentC.titleHeight => - ListTileTitleAlignment.titleHeight, - ListTileTitleAlignmentC.top => ListTileTitleAlignment.top, - ListTileTitleAlignmentC.center => ListTileTitleAlignment.center, - ListTileTitleAlignmentC.bottom => ListTileTitleAlignment.bottom, - }; + ListTileTitleAlignmentC.threeLine => ListTileTitleAlignment.threeLine, + ListTileTitleAlignmentC.titleHeight => ListTileTitleAlignment.titleHeight, + ListTileTitleAlignmentC.top => ListTileTitleAlignment.top, + ListTileTitleAlignmentC.center => ListTileTitleAlignment.center, + ListTileTitleAlignmentC.bottom => ListTileTitleAlignment.bottom, + }; } extension TabAlignmentCExtensions on TabAlignmentC { /// Returns null for alignments that Flutter's [TabBar] rejects for the /// given scrollability, so it falls back to its default. TabAlignment? toFlutter({required bool isScrollable}) => switch (this) { - TabAlignmentC.start => isScrollable ? TabAlignment.start : null, - TabAlignmentC.startOffset => - isScrollable ? TabAlignment.startOffset : null, - TabAlignmentC.fill => isScrollable ? null : TabAlignment.fill, - TabAlignmentC.center => TabAlignment.center, - }; + TabAlignmentC.start => isScrollable ? TabAlignment.start : null, + TabAlignmentC.startOffset => isScrollable ? TabAlignment.startOffset : null, + TabAlignmentC.fill => isScrollable ? null : TabAlignment.fill, + TabAlignmentC.center => TabAlignment.center, + }; } extension TextAlignVerticalEnumExt on TextAlignVerticalEnum { @@ -1989,44 +2025,44 @@ extension BaseNodeExt on BaseNode { extension StrokeCapEnumExt on StrokeCapEnum { StrokeCap get flutterStrokeCap => switch (this) { - StrokeCapEnum.round => StrokeCap.round, - StrokeCapEnum.none => StrokeCap.butt, - StrokeCapEnum.square => StrokeCap.square, - }; + StrokeCapEnum.round => StrokeCap.round, + StrokeCapEnum.none => StrokeCap.butt, + StrokeCapEnum.square => StrokeCap.square, + }; } extension BlendModeCExt on BlendModeC { BlendMode get flutterBlendMode => switch (this) { - BlendModeC.src => BlendMode.src, - BlendModeC.srcOver => BlendMode.srcOver, - BlendModeC.srcATop => BlendMode.srcATop, - BlendModeC.srcIn => BlendMode.srcIn, - BlendModeC.srcOut => BlendMode.srcOut, - BlendModeC.dst => BlendMode.dst, - BlendModeC.dstOver => BlendMode.dstOver, - BlendModeC.dstIn => BlendMode.dstIn, - BlendModeC.dstOut => BlendMode.dstOut, - BlendModeC.dstATop => BlendMode.dstATop, - BlendModeC.clear => BlendMode.clear, - BlendModeC.multiply => BlendMode.multiply, - BlendModeC.screen => BlendMode.screen, - BlendModeC.overlay => BlendMode.overlay, - BlendModeC.darken => BlendMode.darken, - BlendModeC.lighten => BlendMode.lighten, - BlendModeC.colorDodge => BlendMode.colorDodge, - BlendModeC.colorBurn => BlendMode.colorBurn, - BlendModeC.hardLight => BlendMode.hardLight, - BlendModeC.softLight => BlendMode.softLight, - BlendModeC.difference => BlendMode.difference, - BlendModeC.exclusion => BlendMode.exclusion, - BlendModeC.hue => BlendMode.hue, - BlendModeC.saturation => BlendMode.saturation, - BlendModeC.color => BlendMode.color, - BlendModeC.luminosity => BlendMode.luminosity, - BlendModeC.xor => BlendMode.xor, - BlendModeC.plus => BlendMode.plus, - BlendModeC.modulate => BlendMode.modulate, - }; + BlendModeC.src => BlendMode.src, + BlendModeC.srcOver => BlendMode.srcOver, + BlendModeC.srcATop => BlendMode.srcATop, + BlendModeC.srcIn => BlendMode.srcIn, + BlendModeC.srcOut => BlendMode.srcOut, + BlendModeC.dst => BlendMode.dst, + BlendModeC.dstOver => BlendMode.dstOver, + BlendModeC.dstIn => BlendMode.dstIn, + BlendModeC.dstOut => BlendMode.dstOut, + BlendModeC.dstATop => BlendMode.dstATop, + BlendModeC.clear => BlendMode.clear, + BlendModeC.multiply => BlendMode.multiply, + BlendModeC.screen => BlendMode.screen, + BlendModeC.overlay => BlendMode.overlay, + BlendModeC.darken => BlendMode.darken, + BlendModeC.lighten => BlendMode.lighten, + BlendModeC.colorDodge => BlendMode.colorDodge, + BlendModeC.colorBurn => BlendMode.colorBurn, + BlendModeC.hardLight => BlendMode.hardLight, + BlendModeC.softLight => BlendMode.softLight, + BlendModeC.difference => BlendMode.difference, + BlendModeC.exclusion => BlendMode.exclusion, + BlendModeC.hue => BlendMode.hue, + BlendModeC.saturation => BlendMode.saturation, + BlendModeC.color => BlendMode.color, + BlendModeC.luminosity => BlendMode.luminosity, + BlendModeC.xor => BlendMode.xor, + BlendModeC.plus => BlendMode.plus, + BlendModeC.modulate => BlendMode.modulate, + }; } extension GenericExt on T? { @@ -2040,17 +2076,21 @@ extension GenericExt on T? { extension BreakpointExt on Iterable { /// Retrieves the breakpoint applicable for the given width. - Breakpoint? findForWidth(double width) => firstWhereOrNull((breakpoint) => - width >= breakpoint.lowerBound && width < breakpoint.upperBound); + Breakpoint? findForWidth(double width) => firstWhereOrNull( + (breakpoint) => + width >= breakpoint.lowerBound && width < breakpoint.upperBound, + ); } extension LayoutGroupsExt on Map> { /// Finds the layout group and breakpoint for the given canvas id. ({String? layoutGroupId, Breakpoint? breakpoint}) findByCanvasId( - String canvasId) { + String canvasId, + ) { for (final MapEntry(key: groupId, value: breakpoints) in entries) { - final breakpoint = breakpoints - .firstWhereOrNull((breakpoint) => breakpoint.nodeId == canvasId); + final breakpoint = breakpoints.firstWhereOrNull( + (breakpoint) => breakpoint.nodeId == canvasId, + ); if (breakpoint != null) { return (layoutGroupId: groupId, breakpoint: breakpoint); } @@ -2059,8 +2099,12 @@ extension LayoutGroupsExt on Map> { } Map toJson() { - return map((key, value) => - MapEntry(key, value.map((breakpoint) => breakpoint.toJson()).toList())); + return map( + (key, value) => MapEntry( + key, + value.map((breakpoint) => breakpoint.toJson()).toList(), + ), + ); } } @@ -2094,30 +2138,29 @@ extension TextFieldFormatterExt on TextInputFormatterModel { extension AutovalidateModeCExt on AutovalidateModeC { AutovalidateMode get flutterAutovalidateMode => switch (this) { - AutovalidateModeC.disabled => AutovalidateMode.disabled, - AutovalidateModeC.always => AutovalidateMode.always, - AutovalidateModeC.onUserInteraction => - AutovalidateMode.onUserInteraction, - AutovalidateModeC.onUnfocus => AutovalidateMode.onUnfocus, - AutovalidateModeC.onUserInteractionIfError => - AutovalidateMode.onUserInteractionIfError, - }; + AutovalidateModeC.disabled => AutovalidateMode.disabled, + AutovalidateModeC.always => AutovalidateMode.always, + AutovalidateModeC.onUserInteraction => AutovalidateMode.onUserInteraction, + AutovalidateModeC.onUnfocus => AutovalidateMode.onUnfocus, + AutovalidateModeC.onUserInteractionIfError => + AutovalidateMode.onUserInteractionIfError, + }; } extension TextInputActionCExt on TextInputActionC { TextInputAction get flutterTextInputAction => switch (this) { - TextInputActionC.none => TextInputAction.none, - TextInputActionC.unspecified => TextInputAction.unspecified, - TextInputActionC.done => TextInputAction.done, - TextInputActionC.go => TextInputAction.go, - TextInputActionC.search => TextInputAction.search, - TextInputActionC.send => TextInputAction.send, - TextInputActionC.next => TextInputAction.next, - TextInputActionC.previous => TextInputAction.previous, - TextInputActionC.continueAction => TextInputAction.continueAction, - TextInputActionC.join => TextInputAction.join, - TextInputActionC.route => TextInputAction.route, - TextInputActionC.emergencyCall => TextInputAction.emergencyCall, - TextInputActionC.newline => TextInputAction.newline, - }; + TextInputActionC.none => TextInputAction.none, + TextInputActionC.unspecified => TextInputAction.unspecified, + TextInputActionC.done => TextInputAction.done, + TextInputActionC.go => TextInputAction.go, + TextInputActionC.search => TextInputAction.search, + TextInputActionC.send => TextInputAction.send, + TextInputActionC.next => TextInputAction.next, + TextInputActionC.previous => TextInputAction.previous, + TextInputActionC.continueAction => TextInputAction.continueAction, + TextInputActionC.join => TextInputAction.join, + TextInputActionC.route => TextInputAction.route, + TextInputActionC.emergencyCall => TextInputAction.emergencyCall, + TextInputActionC.newline => TextInputAction.newline, + }; } diff --git a/lib/src/utils/flutter_icons_data_map.dart b/lib/src/utils/flutter_icons_data_map.dart index 5bf396d7..15e679e5 100644 --- a/lib/src/utils/flutter_icons_data_map.dart +++ b/lib/src/utils/flutter_icons_data_map.dart @@ -8823,5 +8823,5 @@ const Map flutterIconsDataMap = { 'zoom_out_map': 0xe6ff, 'zoom_out_map_sharp': 0xedf0, 'zoom_out_map_rounded': 0xf02cf, - 'zoom_out_map_outlined': 0xf4dd + 'zoom_out_map_outlined': 0xf4dd, }; diff --git a/lib/src/utils/flutter_icons_names_map.dart b/lib/src/utils/flutter_icons_names_map.dart index 008e72a6..36572f4b 100644 --- a/lib/src/utils/flutter_icons_names_map.dart +++ b/lib/src/utils/flutter_icons_names_map.dart @@ -8866,5 +8866,5 @@ const Map flutterIconNames = { "zoom_out_rounded": "zoom_out_rounded", "zoom_in_rounded": "zoom_in_rounded", "zoom_out_sharp": "zoom_out_sharp", - "zoom_in_sharp": "zoom_in_sharp" + "zoom_in_sharp": "zoom_in_sharp", }; diff --git a/lib/src/utils/font_parser.dart b/lib/src/utils/font_parser.dart index d95e9e49..ebb3f534 100644 --- a/lib/src/utils/font_parser.dart +++ b/lib/src/utils/font_parser.dart @@ -29,8 +29,10 @@ int _computeMatch(FontVariantModel a, FontVariantModel b) { /// /// The [FontVariantModel] with the lowest score is returned. // ignore: unused_element -FontVariantModel _closestMatch(FontVariantModel sourceVariant, - Iterable variantsToCompare) { +FontVariantModel _closestMatch( + FontVariantModel sourceVariant, + Iterable variantsToCompare, +) { int? bestScore; late FontVariantModel bestMatch; for (final variantToCompare in variantsToCompare) { @@ -50,8 +52,9 @@ String getFontFamilyNameAndVariant( }) { FontWeightNumeric weight = fontName.weight ?? FontWeightNumeric.w400; String family = fontName.family; - String style = - fontName.style.toLowerCase().contains('italic') ? 'Italic' : 'Normal'; + String style = fontName.style.toLowerCase().contains('italic') + ? 'Italic' + : 'Normal'; if (familyModel != null) { /// If [familyModel] is not null, then this is running from the editor. In @@ -73,11 +76,7 @@ String getFontFamilyNameAndVariant( } // If [familyModel] is null, then this is running from the SDK. In this case, // we don't need to derive the font family name since it is already processed. - return deriveFontFamily( - family: family, - style: style, - weight: weight, - ); + return deriveFontFamily(family: family, style: style, weight: weight); } String deriveFontFamily({ @@ -236,7 +235,7 @@ class FontParser { 'wwsFamilyName', 'wwsSubfamilyName', 'lightPalette', - 'darkPalette' + 'darkPalette', ]; var offset0 = offset; @@ -274,7 +273,8 @@ class FontParser { str = bin.readASCII(data, soff, slen); } else { throw Exception( - 'unknown encoding $encodingID, platformID: $platformID'); + 'unknown encoding $encodingID, platformID: $platformID', + ); } var tid = 'p$platformID,${languageID.toRadixString(16)}'; diff --git a/lib/src/utils/image_builder.dart b/lib/src/utils/image_builder.dart index 207b0cde..da306588 100644 --- a/lib/src/utils/image_builder.dart +++ b/lib/src/utils/image_builder.dart @@ -42,8 +42,10 @@ class UltimateImageBuilder extends StatefulWidget { this.node, this.blendMode, this.useInk = false, - }) : assert(url != null || bytes != null || paint != null, - 'url or bytes or paint must be provided') + }) : assert( + url != null || bytes != null || paint != null, + 'url or bytes or paint must be provided', + ) /*assert( paint == null || (width != null && height != null && node != null), 'width, height and node must be provided when paint is provided')*/ @@ -64,10 +66,13 @@ class UltimateImageBuilder extends StatefulWidget { properties.add(EnumProperty('repeat', repeat)); properties.add(ColorProperty('color', color)); properties.add( - ObjectFlagProperty.has('errorBuilder', errorBuilder)); - properties.add(ObjectFlagProperty< - Widget Function(BuildContext context, Widget? child)>.has( - 'loadingBuilder', loadingBuilder)); + ObjectFlagProperty.has('errorBuilder', errorBuilder), + ); + properties.add( + ObjectFlagProperty< + Widget Function(BuildContext context, Widget? child) + >.has('loadingBuilder', loadingBuilder), + ); properties.add(DiagnosticsProperty('paint', paint)); properties.add(DiagnosticsProperty('node', node)); properties.add(EnumProperty('blendMode', blendMode)); @@ -103,7 +108,8 @@ class _UltimateImageBuilderState extends State { widget.color!, widget.blendMode ?? widget.paint?.blendMode.flutterBlendMode ?? - BlendMode.srcIn) + BlendMode.srcIn, + ) : null; double? get width { @@ -156,9 +162,13 @@ class _UltimateImageBuilderState extends State { return; } if (isCustomPositionRequiredForImage( - widget.paint!, SizeC(widget.width!, widget.height!))) { - effectiveChildSize = - getEffectiveChildSizeForImage(widget.node!, widget.paint!); + widget.paint!, + SizeC(widget.width!, widget.height!), + )) { + effectiveChildSize = getEffectiveChildSizeForImage( + widget.node!, + widget.paint!, + ); position = convertAlignmentToPosition( parentWidth: widget.node!.basicBoxGlobal.width, @@ -185,7 +195,7 @@ class _UltimateImageBuilderState extends State { width: effectiveChildSize!.width, height: effectiveChildSize!.height, child: buildImage(withAlignment: false), - ) + ), ], ); } @@ -202,7 +212,8 @@ class _UltimateImageBuilderState extends State { /// TODO: Decorate the rest of the image widgets, not just CachedNetworkImage. Widget buildFromUrl({required bool withAlignment}) { - final String url = widget.url ?? + final String url = + widget.url ?? widget.paint!.croppedImageURL ?? widget.paint!.downloadUrl!; @@ -253,11 +264,13 @@ class _UltimateImageBuilderState extends State { colorBlendMode: colorBlendMode, loadingBuilder: widget.loadingBuilder != null ? (context, child, loadingProgress) => - widget.loadingBuilder!(context, child) + widget.loadingBuilder!(context, child) : null, errorBuilder: (context, error, stackTrace) { - DebugLogger.instance - .printInfo('Image Loading Error: $error', name: 'ImageBuilder'); + DebugLogger.instance.printInfo( + 'Image Loading Error: $error', + name: 'ImageBuilder', + ); return (widget.errorBuilder ?? _defaultErrorBuilder)(context); }, ); @@ -320,9 +333,7 @@ class _UltimateImageBuilderState extends State { builder: (context, constraints) { if (constraints.maxHeight < 64 || constraints.maxWidth < 64) { return const FittedBox( - child: Center( - child: Icon(Icons.image_outlined), - ), + child: Center(child: Icon(Icons.image_outlined)), ); } return FractionallySizedBox( @@ -339,29 +350,26 @@ class _UltimateImageBuilderState extends State { mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ - const Icon( - Icons.image_outlined, - size: 100, - ), + const Icon(Icons.image_outlined, size: 100), Text( 'Variable Image', textAlign: TextAlign.center, style: Theme.of(context).textTheme.bodyMedium?.merge( - GoogleFonts.sourceCodePro( - fontSize: 24, - fontWeight: FontWeight.w600, - ), - ), + GoogleFonts.sourceCodePro( + fontSize: 24, + fontWeight: FontWeight.w600, + ), + ), ), Text( path, textAlign: TextAlign.center, style: Theme.of(context).textTheme.bodyMedium?.merge( - GoogleFonts.sourceCodePro( - color: Colors.greenAccent.shade700, - fontSize: 24, - ), - ), + GoogleFonts.sourceCodePro( + color: Colors.greenAccent.shade700, + fontSize: 24, + ), + ), ), ], ), diff --git a/lib/src/utils/rgb_to_color.dart b/lib/src/utils/rgb_to_color.dart index 4143dafe..979e52e7 100644 --- a/lib/src/utils/rgb_to_color.dart +++ b/lib/src/utils/rgb_to_color.dart @@ -32,8 +32,11 @@ Color retrieveFillColor(GeometryMixin node) { } /// Converts the strokes of a given [node] to a single [Color] object. -Color retrieveStrokeColor(GeometryMixin node, - [int index = 0, bool all = false]) { +Color retrieveStrokeColor( + GeometryMixin node, [ + int index = 0, + bool all = false, +]) { final strokes = node.strokes.visible(); if (strokes.isEmpty) { // Some methods expect a non-null color. @@ -59,9 +62,11 @@ Color retrieveStrokeColor(GeometryMixin node, if (stroke == null) { final List gradients = strokes - .where((element) => - element.gradientStops != null && - element.gradientStops!.isNotEmpty) + .where( + (element) => + element.gradientStops != null && + element.gradientStops!.isNotEmpty, + ) .toList(); if (gradients.isNotEmpty) { @@ -94,7 +99,9 @@ Color retrieveStrokeColor(GeometryMixin node, /// Converts a [PaintModel] to a [Color] object. Color retrieveColorFromPaint(PaintModel paint) { - assert(paint.type == PaintType.solid, - 'retrieveColorFromPaint() only accepts Paints that are PaintType.SOLID'); + assert( + paint.type == PaintType.solid, + 'retrieveColorFromPaint() only accepts Paints that are PaintType.SOLID', + ); return paint.toFlutterColor()!; } diff --git a/lib/src/utils/typed_image_bytes.dart b/lib/src/utils/typed_image_bytes.dart index 7c96cd40..a8cd75ee 100644 --- a/lib/src/utils/typed_image_bytes.dart +++ b/lib/src/utils/typed_image_bytes.dart @@ -25,13 +25,13 @@ enum ResourceType { bool get isStandardImage => isPng || isJpg || isWebp; factory ResourceType.fromExtension(String type) => switch (type) { - 'svg' || '.svg' => ResourceType.svg, - 'png' || '.png' => ResourceType.png, - 'jpg' || '.jpg' || 'jpeg' || '.jpeg' => ResourceType.jpg, - 'gif' || '.gif' => ResourceType.gif, - 'webp' || '.webp' => ResourceType.webp, - _ => ResourceType.unknown, - }; + 'svg' || '.svg' => ResourceType.svg, + 'png' || '.png' => ResourceType.png, + 'jpg' || '.jpg' || 'jpeg' || '.jpeg' => ResourceType.jpg, + 'gif' || '.gif' => ResourceType.gif, + 'webp' || '.webp' => ResourceType.webp, + _ => ResourceType.unknown, + }; factory ResourceType.fromUrl(String url) => ResourceType.fromExtension(path.extension(Uri.parse(url).path)); diff --git a/lib/src/utils/utils.dart b/lib/src/utils/utils.dart index bf71fd82..5d913ec1 100644 --- a/lib/src/utils/utils.dart +++ b/lib/src/utils/utils.dart @@ -150,11 +150,15 @@ class VariableMatch with Equatable { } /// Supported image file types that can be dropped on the canvas. -final RegExp supportedAssetTypesRegex = - RegExp('jpg|jpeg|png|webp|gif|svg', caseSensitive: false); +final RegExp supportedAssetTypesRegex = RegExp( + 'jpg|jpeg|png|webp|gif|svg', + caseSensitive: false, +); -final RegExp staticImageTypesRegex = - RegExp('jpg|jpeg|png|webp', caseSensitive: false); +final RegExp staticImageTypesRegex = RegExp( + 'jpg|jpeg|png|webp', + caseSensitive: false, +); /// Returns the most common value in a list. /// Returns null for ties. @@ -228,27 +232,33 @@ BaseNode? getTallestNode(Iterable siblings) { /// largest child by multiplying its width and height as a sort of best /// approximation. BaseNode? getLargestNodeForWrappingStack( - BaseNode parent, List sibling) { + BaseNode parent, + List sibling, +) { if (sibling.isEmpty) return null; // Only horizontal wrap. if (parent.isHorizontalWrap && !parent.isVerticalWrap) { return sibling.reduce( - (a, b) => a.outerBoxLocal.width > b.outerBoxLocal.width ? a : b); + (a, b) => a.outerBoxLocal.width > b.outerBoxLocal.width ? a : b, + ); } // Only vertical wrap, compare heights. if (parent.isVerticalWrap && !parent.isHorizontalWrap) { return sibling.reduce( - (a, b) => a.outerBoxLocal.height > b.outerBoxLocal.height ? a : b); + (a, b) => a.outerBoxLocal.height > b.outerBoxLocal.height ? a : b, + ); } // Both wrap. - return sibling.reduce((a, b) => - a.outerBoxLocal.height * a.outerBoxLocal.width > - b.outerBoxLocal.height * b.outerBoxLocal.width - ? a - : b); + return sibling.reduce( + (a, b) => + a.outerBoxLocal.height * a.outerBoxLocal.width > + b.outerBoxLocal.height * b.outerBoxLocal.width + ? a + : b, + ); } AlignmentModel retrieveCommonStackAlignment( @@ -313,12 +323,14 @@ String apiNameToVariableName(String name) { if (name.startsWith(RegExp(r'\d+'))) { name = name.replaceFirst(RegExp(r'\d+'), '').trim(); } - name = - name.replaceAll(RegExp(r'\s+'), '_').replaceAll(RegExp(r'\W'), '').trim(); + name = name + .replaceAll(RegExp(r'\s+'), '_') + .replaceAll(RegExp(r'\W'), '') + .trim(); final tokens = name.split(RegExp(r'_+')); final sanitizedName = [ SDKConstants.apiVariablePrefix, - ...tokens.map((e) => e.capitalized) + ...tokens.map((e) => e.capitalized), ].join(''); return sanitizedName; } @@ -340,62 +352,60 @@ class ApiResponseVariableUtils { ApiResponseVariableUtils._(); static Map idle(String url) => { - 'url': url, - 'isIdle': true, - 'isLoading': false, - 'isError': false, - 'isSuccess': false, - 'status': 'idle', - }; + 'url': url, + 'isIdle': true, + 'isLoading': false, + 'isError': false, + 'isSuccess': false, + 'status': 'idle', + }; static Map loading(String url, {Object? data}) => { - 'url': url, - 'isIdle': false, - 'isLoading': true, - 'isError': false, - 'isSuccess': false, - 'status': 'loading', - 'data': data, - 'hasData': data != null, - }; + 'url': url, + 'isIdle': false, + 'isLoading': true, + 'isError': false, + 'isSuccess': false, + 'status': 'loading', + 'data': data, + 'hasData': data != null, + }; static Map error( String url, Object? error, { Object? data, Map? headers, - }) => - { - 'url': url, - 'isIdle': false, - 'isLoading': false, - 'isError': true, - 'isSuccess': false, - 'error': error is Map || error is List ? error : error.toString(), - 'status': 'error', - 'headers': headers, - 'hasData': data != null, - 'data': data is Map || data is List ? data : data.toString(), - }; + }) => { + 'url': url, + 'isIdle': false, + 'isLoading': false, + 'isError': true, + 'isSuccess': false, + 'error': error is Map || error is List ? error : error.toString(), + 'status': 'error', + 'headers': headers, + 'hasData': data != null, + 'data': data is Map || data is List ? data : data.toString(), + }; static Map success( String url, Object? data, { int statusCode = 200, Map? headers, - }) => - { - 'url': url, - 'isIdle': false, - 'isLoading': false, - 'isError': false, - 'isSuccess': true, - 'data': data, - 'status': 'error', - 'headers': headers, - 'statusCode': statusCode, - 'hasData': data != null, - }; + }) => { + 'url': url, + 'isIdle': false, + 'isLoading': false, + 'isError': false, + 'isSuccess': true, + 'data': data, + 'status': 'error', + 'headers': headers, + 'statusCode': statusCode, + 'hasData': data != null, + }; static Map fromResponse(http.Response response, {Object? existingData}) { final bool isSuccess = @@ -421,15 +431,15 @@ class ApiResponseVariableUtils { }; } -// static Object? sanitizeValueForJson(Object? value) { -// return switch (value) { -// String() || int() || double() || bool() => value, -// List() => value.map(sanitizeValueForJson).toList(), -// Map() => value.map((key, value) => -// MapEntry(key.toString(), sanitizeValueForJson(value))), -// _ => value?.toString(), -// }; -// } + // static Object? sanitizeValueForJson(Object? value) { + // return switch (value) { + // String() || int() || double() || bool() => value, + // List() => value.map(sanitizeValueForJson).toList(), + // Map() => value.map((key, value) => + // MapEntry(key.toString(), sanitizeValueForJson(value))), + // _ => value?.toString(), + // }; + // } } /// Helper utility class to easily update different statuses for cloud storage variable @@ -437,52 +447,44 @@ class CloudDatabaseVariableUtils { CloudDatabaseVariableUtils._(); static Map idle() => { - 'isIdle': true, - 'isLoading': false, - 'isError': false, - 'isSuccess': false, - 'status': 'idle', - }; + 'isIdle': true, + 'isLoading': false, + 'isError': false, + 'isSuccess': false, + 'status': 'idle', + }; static Map loading({Object? data}) => { - 'isIdle': false, - 'isLoading': true, - 'isError': false, - 'isSuccess': false, - 'status': 'loading', - 'data': data, - 'hasData': data != null, - }; - - static Map error( - Object? error, { - Object? data, - }) => - { - 'isIdle': false, - 'isLoading': false, - 'isError': true, - 'isSuccess': false, - 'error': error is Map || error is List ? error : error.toString(), - 'status': 'error', - 'hasData': data != null, - 'data': data is Map || data is List ? data : data.toString(), - }; - - static Map success( - Object? data, { - String? docId, - }) => - { - 'id': docId, - 'isIdle': false, - 'isLoading': false, - 'isError': false, - 'isSuccess': true, - 'data': data, - 'status': 'error', - 'hasData': data != null, - }; + 'isIdle': false, + 'isLoading': true, + 'isError': false, + 'isSuccess': false, + 'status': 'loading', + 'data': data, + 'hasData': data != null, + }; + + static Map error(Object? error, {Object? data}) => { + 'isIdle': false, + 'isLoading': false, + 'isError': true, + 'isSuccess': false, + 'error': error is Map || error is List ? error : error.toString(), + 'status': 'error', + 'hasData': data != null, + 'data': data is Map || data is List ? data : data.toString(), + }; + + static Map success(Object? data, {String? docId}) => { + 'id': docId, + 'isIdle': false, + 'isLoading': false, + 'isError': false, + 'isSuccess': true, + 'data': data, + 'status': 'error', + 'hasData': data != null, + }; } /// Constructs a [Query] on top of given [Query] reference using given [whereFilters] @@ -515,7 +517,8 @@ Query> constructQueryFromRef( scopedValues: scopedValues, nullSubstitutionMode: nullSubstitutionMode, ); - final Object value = PropertyValueDelegate.substituteVariables( + final Object value = + PropertyValueDelegate.substituteVariables( whereFilter.value, scopedValues: scopedValues, nullSubstitutionMode: nullSubstitutionMode, @@ -523,46 +526,37 @@ Query> constructQueryFromRef( ''; query = switch (whereFilter.operator) { - WhereQueryOperator.equal => query.where( - field, - isEqualTo: value, - ), - WhereQueryOperator.notEqual => query.where( - field, - isNotEqualTo: value, - ), + WhereQueryOperator.equal => query.where(field, isEqualTo: value), + WhereQueryOperator.notEqual => query.where(field, isNotEqualTo: value), WhereQueryOperator.arrayContains => query.where( - field, - arrayContains: value, - ), + field, + arrayContains: value, + ), WhereQueryOperator.greaterThan => query.where( - field, - isGreaterThan: value, - ), + field, + isGreaterThan: value, + ), WhereQueryOperator.greaterThanOrEqual => query.where( - field, - isGreaterThanOrEqualTo: value, - ), - WhereQueryOperator.lessThan => query.where( - field, - isLessThan: value, - ), + field, + isGreaterThanOrEqualTo: value, + ), + WhereQueryOperator.lessThan => query.where(field, isLessThan: value), WhereQueryOperator.lessThanOrEqual => query.where( - field, - isLessThanOrEqualTo: value, - ), + field, + isLessThanOrEqualTo: value, + ), WhereQueryOperator.arrayContainsAny => query.where( - field, - arrayContainsAny: value.typedValue(), - ), + field, + arrayContainsAny: value.typedValue(), + ), WhereQueryOperator.inArray => query.where( - field, - whereIn: value.typedValue(), - ), + field, + whereIn: value.typedValue(), + ), WhereQueryOperator.notInArray => query.where( - field, - whereNotIn: value.typedValue(), - ), + field, + whereNotIn: value.typedValue(), + ), }; } From 04fce8f8d9e7fa41522bf5920a6ac424b446ecb6 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 13 Oct 2026 03:47:23 +0000 Subject: [PATCH 22/22] Note WebAssembly compatibility in the changelog Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01A67XoQYsLVvoMtiKcXXUxY --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 344b8009..93ee31fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,7 +38,9 @@ - PageView, TabBar and Accordion edge cases that threw are fixed; Accordion respects `isExpanded` and `enabled`, and TabBar `showDivider`. - Malformed links and failed SVG downloads no longer throw. -- The web view works in WebAssembly builds. +- The web view works in WebAssembly builds, and embedded video no longer + pulls native web view plugins into web builds; the package is fully + WebAssembly-compatible. - The auth token and auth response body are no longer logged, and library code logs through `DebugLogger` instead of `print`.