From 235adbc2d22b2c07fa16144d49c05fbb6be91b1d Mon Sep 17 00:00:00 2001 From: CTLalit <144685420+CTLalit@users.noreply.github.com> Date: Mon, 25 May 2026 16:40:09 +0530 Subject: [PATCH 01/12] SDK-5832: Entrance animations via AnimationModifier (Flutter) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AnimationModifier: StatefulWidget with SingleTickerProviderStateMixin - Resolves AnimationType to FadeTransition, SlideTransition, ScaleTransition combinations: fadeIn → Fade; slideIn* → Slide; scaleIn → Scale; fadeScaleIn → Fade+Scale; fadeSlideIn → Fade+Slide - Slide begin offsets per direction: left=(-1,0), right=(1,0), top=(0,-1), bottom=(0,1) - Easing resolved to Flutter Curves; spring → Curves.elasticOut - Delay via Future.delayed before controller.forward() - NativeDisplayRenderer: wraps every built node in AnimationModifier when node.animation is non-null and type != none - Tests: 7 tests covering all major animation type combinations and completion Co-Authored-By: Claude Opus 4.7 (1M context) --- .../lib/src/renderer/animation_modifier.dart | 122 ++++++++++++++++++ .../src/renderer/native_display_renderer.dart | 9 +- .../renderer/animation_modifier_test.dart | 93 +++++++++++++ 3 files changed, 223 insertions(+), 1 deletion(-) create mode 100644 flutter/lib/src/renderer/animation_modifier.dart create mode 100644 flutter/test/renderer/animation_modifier_test.dart diff --git a/flutter/lib/src/renderer/animation_modifier.dart b/flutter/lib/src/renderer/animation_modifier.dart new file mode 100644 index 0000000..aece5ae --- /dev/null +++ b/flutter/lib/src/renderer/animation_modifier.dart @@ -0,0 +1,122 @@ +import 'package:flutter/widgets.dart'; + +import '../models/enums.dart'; +import '../models/node_config.dart'; + +class AnimationModifier extends StatefulWidget { + final NDAnimation animation; + final Widget child; + + const AnimationModifier({ + super.key, + required this.animation, + required this.child, + }); + + @override + State createState() => _AnimationModifierState(); +} + +class _AnimationModifierState extends State + with SingleTickerProviderStateMixin { + late AnimationController _controller; + late Animation _opacity; + late Animation _slide; + late Animation _scale; + + @override + void initState() { + super.initState(); + final anim = widget.animation; + _controller = AnimationController( + vsync: this, + duration: Duration(milliseconds: anim.duration), + ); + + final curve = _resolveCurve(anim.easing); + final curved = CurvedAnimation(parent: _controller, curve: curve); + + _opacity = Tween(begin: 0, end: 1).animate(curved); + _slide = Tween(begin: _slideBegin(anim.type), end: Offset.zero).animate(curved); + _scale = Tween(begin: 0.8, end: 1).animate(curved); + + if (anim.delay > 0) { + Future.delayed(Duration(milliseconds: anim.delay), () { + if (mounted) _controller.forward(); + }); + } else { + _controller.forward(); + } + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + Curve _resolveCurve(Easing easing) => switch (easing) { + Easing.linear => Curves.linear, + Easing.easeIn => Curves.easeIn, + Easing.easeOut => Curves.easeOut, + Easing.easeInOut => Curves.easeInOut, + Easing.easeInBack => Curves.easeInBack, + Easing.easeOutBack => Curves.easeOutBack, + Easing.spring => Curves.elasticOut, + }; + + Offset _slideBegin(AnimationType type) => switch (type) { + AnimationType.slideInLeft => const Offset(-1, 0), + AnimationType.slideInRight => const Offset(1, 0), + AnimationType.slideInTop => const Offset(0, -1), + AnimationType.slideInBottom => const Offset(0, 1), + AnimationType.fadeSlideIn => const Offset(0, 0.2), + _ => Offset.zero, + }; + + bool get _hasFade => switch (widget.animation.type) { + AnimationType.none => false, + AnimationType.fadeIn => true, + AnimationType.slideInLeft => false, + AnimationType.slideInRight => false, + AnimationType.slideInTop => false, + AnimationType.slideInBottom => false, + AnimationType.scaleIn => false, + AnimationType.fadeScaleIn => true, + AnimationType.fadeSlideIn => true, + }; + + bool get _hasSlide => switch (widget.animation.type) { + AnimationType.slideInLeft => true, + AnimationType.slideInRight => true, + AnimationType.slideInTop => true, + AnimationType.slideInBottom => true, + AnimationType.fadeSlideIn => true, + _ => false, + }; + + bool get _hasScale => switch (widget.animation.type) { + AnimationType.scaleIn => true, + AnimationType.fadeScaleIn => true, + _ => false, + }; + + @override + Widget build(BuildContext context) { + if (widget.animation.type == AnimationType.none) return widget.child; + + Widget result = widget.child; + + if (_hasSlide) { + result = SlideTransition(position: _slide, child: result); + } + if (_hasScale) { + result = ScaleTransition(scale: _scale, child: result); + } + if (_hasFade) { + result = FadeTransition(opacity: _opacity, child: result); + } + + return result; + } +} diff --git a/flutter/lib/src/renderer/native_display_renderer.dart b/flutter/lib/src/renderer/native_display_renderer.dart index e6c7e21..9458315 100644 --- a/flutter/lib/src/renderer/native_display_renderer.dart +++ b/flutter/lib/src/renderer/native_display_renderer.dart @@ -3,6 +3,7 @@ import '../evaluator/variable_evaluator.dart'; import '../models/enums.dart'; import '../models/native_display_node.dart'; import '../models/style.dart'; +import 'animation_modifier.dart'; import 'containers/box_container.dart'; import 'containers/gallery_renderer.dart'; import 'containers/horizontal_container.dart'; @@ -37,10 +38,16 @@ class NativeDisplayRenderer extends StatelessWidget { final resolvedStyles = ResolvedStylesScope.of(context); final style = resolvedStyles[node.id] ?? Style.empty; - return switch (node) { + Widget built = switch (node) { NativeDisplayContainer c => _buildContainer(c, style), NativeDisplayElement e => _buildElement(context, e, style), }; + + final anim = node.animation; + if (anim != null && anim.type != AnimationType.none) { + built = AnimationModifier(animation: anim, child: built); + } + return built; } Widget _buildContainer(NativeDisplayContainer node, Style style) { diff --git a/flutter/test/renderer/animation_modifier_test.dart b/flutter/test/renderer/animation_modifier_test.dart new file mode 100644 index 0000000..e3f55e4 --- /dev/null +++ b/flutter/test/renderer/animation_modifier_test.dart @@ -0,0 +1,93 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:clevertap_native_display/src/renderer/animation_modifier.dart'; +import 'package:clevertap_native_display/src/models/node_config.dart'; +import 'package:clevertap_native_display/src/models/enums.dart'; + +Widget wrap(Widget w) => Directionality( + textDirection: TextDirection.ltr, + child: w, + ); + +NDAnimation makeAnim(AnimationType type, {int duration = 300, int delay = 0}) => + NDAnimation(type: type, duration: duration, delay: delay); + +void main() { + group('AnimationModifier', () { + testWidgets('none type returns child directly without transitions', (tester) async { + const child = SizedBox(key: ValueKey('child'), width: 50, height: 50); + + await tester.pumpWidget(wrap(AnimationModifier( + animation: makeAnim(AnimationType.none), + child: child, + ))); + + expect(find.byKey(const ValueKey('child')), findsOneWidget); + expect(find.byType(FadeTransition), findsNothing); + expect(find.byType(SlideTransition), findsNothing); + expect(find.byType(ScaleTransition), findsNothing); + }); + + testWidgets('fadeIn type wraps in FadeTransition', (tester) async { + await tester.pumpWidget(wrap(AnimationModifier( + animation: makeAnim(AnimationType.fadeIn), + child: const SizedBox(width: 50, height: 50), + ))); + + expect(find.byType(FadeTransition), findsOneWidget); + expect(find.byType(SlideTransition), findsNothing); + expect(find.byType(ScaleTransition), findsNothing); + }); + + testWidgets('slideInLeft type wraps in SlideTransition', (tester) async { + await tester.pumpWidget(wrap(AnimationModifier( + animation: makeAnim(AnimationType.slideInLeft), + child: const SizedBox(width: 50, height: 50), + ))); + + expect(find.byType(SlideTransition), findsOneWidget); + expect(find.byType(FadeTransition), findsNothing); + }); + + testWidgets('scaleIn type wraps in ScaleTransition', (tester) async { + await tester.pumpWidget(wrap(AnimationModifier( + animation: makeAnim(AnimationType.scaleIn), + child: const SizedBox(width: 50, height: 50), + ))); + + expect(find.byType(ScaleTransition), findsOneWidget); + expect(find.byType(FadeTransition), findsNothing); + }); + + testWidgets('fadeScaleIn wraps in both FadeTransition and ScaleTransition', (tester) async { + await tester.pumpWidget(wrap(AnimationModifier( + animation: makeAnim(AnimationType.fadeScaleIn), + child: const SizedBox(width: 50, height: 50), + ))); + + expect(find.byType(FadeTransition), findsOneWidget); + expect(find.byType(ScaleTransition), findsOneWidget); + }); + + testWidgets('fadeSlideIn wraps in both FadeTransition and SlideTransition', (tester) async { + await tester.pumpWidget(wrap(AnimationModifier( + animation: makeAnim(AnimationType.fadeSlideIn), + child: const SizedBox(width: 50, height: 50), + ))); + + expect(find.byType(FadeTransition), findsOneWidget); + expect(find.byType(SlideTransition), findsOneWidget); + }); + + testWidgets('animation completes after pump', (tester) async { + await tester.pumpWidget(wrap(AnimationModifier( + animation: makeAnim(AnimationType.fadeIn, duration: 100), + child: const SizedBox(width: 50, height: 50), + ))); + + await tester.pumpAndSettle(); + // No exception = animation completed cleanly + expect(find.byType(FadeTransition), findsOneWidget); + }); + }); +} From 0a4cdc5df43e7b6a28819b219f785c36cb1a1b90 Mon Sep 17 00:00:00 2001 From: CTLalit <144685420+CTLalit@users.noreply.github.com> Date: Mon, 25 May 2026 16:42:37 +0530 Subject: [PATCH 02/12] =?UTF-8?q?SDK-5833:=20ActionHandler=20=E2=80=94=20a?= =?UTF-8?q?ction=20routing=20with=20url=5Flauncher=20(Flutter)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add url_launcher: ^6.3.0 dependency - ActionHandler: const class with optional NativeDisplayActionListener - OpenUrlAction: launches URL via launchUrl; openInBrowser=true uses externalApplication mode - CustomAction: forwards key, value, metadata to listener as flat params map - NavigateAction: forwards destination + params to listener - TrackEventAction: forwards eventName + properties to listener - CompositeAction: sequential iterates actions in order; parallel uses Future.wait - Export ActionHandler from clevertap_native_display.dart public API - Tests: 6 listener-dispatch tests (custom, navigate, event, composite, no-listener guards) Co-Authored-By: Claude Opus 4.7 (1M context) --- flutter/lib/clevertap_native_display.dart | 1 + flutter/lib/src/handler/action_handler.dart | 69 +++++++++++ flutter/pubspec.lock | 64 +++++++++++ flutter/pubspec.yaml | 1 + flutter/test/handler/action_handler_test.dart | 107 ++++++++++++++++++ 5 files changed, 242 insertions(+) create mode 100644 flutter/lib/src/handler/action_handler.dart create mode 100644 flutter/test/handler/action_handler_test.dart diff --git a/flutter/lib/clevertap_native_display.dart b/flutter/lib/clevertap_native_display.dart index 4d24cac..3158554 100644 --- a/flutter/lib/clevertap_native_display.dart +++ b/flutter/lib/clevertap_native_display.dart @@ -12,3 +12,4 @@ export 'src/models/native_display_node.dart'; export 'src/models/native_display_config.dart'; export 'src/renderer/native_display_view.dart' show NativeDisplayView, NativeDisplayActionListener, NativeDisplayComponentListener; +export 'src/handler/action_handler.dart' show ActionHandler; diff --git a/flutter/lib/src/handler/action_handler.dart b/flutter/lib/src/handler/action_handler.dart new file mode 100644 index 0000000..a49eb78 --- /dev/null +++ b/flutter/lib/src/handler/action_handler.dart @@ -0,0 +1,69 @@ +import 'package:flutter/widgets.dart'; +import 'package:url_launcher/url_launcher.dart'; + +import '../models/action.dart'; +import '../models/enums.dart'; +import '../renderer/native_display_view.dart'; + +class ActionHandler { + final NativeDisplayActionListener? listener; + + const ActionHandler({this.listener}); + + Future handle(NDAction action, String nodeId) async { + return switch (action) { + OpenUrlAction a => _handleOpenUrl(a, nodeId), + CustomAction a => _handleCustom(a, nodeId), + NavigateAction a => _handleNavigate(a, nodeId), + TrackEventAction a => _handleTrackEvent(a, nodeId), + CompositeAction a => _handleComposite(a, nodeId), + }; + } + + Future _handleOpenUrl(OpenUrlAction action, String nodeId) async { + final uri = Uri.tryParse(action.url); + if (uri == null) return; + try { + if (action.openInBrowser) { + await launchUrl(uri, mode: LaunchMode.externalApplication); + } else { + await launchUrl(uri, mode: LaunchMode.platformDefault); + } + } catch (e) { + debugPrint('[NativeDisplay] ActionHandler: failed to launch URL ${action.url}: $e'); + } + listener?.call('open_url', nodeId, {'url': action.url}); + } + + void _handleCustom(CustomAction action, String nodeId) { + listener?.call('custom', nodeId, { + 'key': action.key, + if (action.value != null) 'value': action.value, + if (action.metadata != null) ...?action.metadata, + }); + } + + void _handleNavigate(NavigateAction action, String nodeId) { + listener?.call('navigate', nodeId, { + 'destination': action.destination, + if (action.params != null) ...?action.params, + }); + } + + void _handleTrackEvent(TrackEventAction action, String nodeId) { + listener?.call('event', nodeId, { + 'eventName': action.eventName, + if (action.properties != null) ...?action.properties, + }); + } + + Future _handleComposite(CompositeAction action, String nodeId) async { + if (action.executionMode == ExecutionMode.parallel) { + await Future.wait(action.actions.map((a) => handle(a, nodeId))); + } else { + for (final a in action.actions) { + await handle(a, nodeId); + } + } + } +} diff --git a/flutter/pubspec.lock b/flutter/pubspec.lock index 241bafd..4883563 100644 --- a/flutter/pubspec.lock +++ b/flutter/pubspec.lock @@ -421,6 +421,70 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 + url: "https://pub.dev" + source: hosted + version: "6.3.2" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "81777b08c498a292d93ff2feead633174c386291e35612f8da438d6e92c4447e" + url: "https://pub.dev" + source: hosted + version: "6.3.20" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: d80b3f567a617cb923546034cc94bfe44eb15f989fe670b37f26abdb9d939cb7 + url: "https://pub.dev" + source: hosted + version: "6.3.4" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: c043a77d6600ac9c38300567f33ef12b0ef4f4783a2c1f00231d2b1941fea13f + url: "https://pub.dev" + source: hosted + version: "3.2.3" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" + url: "https://pub.dev" + source: hosted + version: "3.1.4" uuid: dependency: transitive description: diff --git a/flutter/pubspec.yaml b/flutter/pubspec.yaml index 3d99f5c..1146679 100644 --- a/flutter/pubspec.yaml +++ b/flutter/pubspec.yaml @@ -13,6 +13,7 @@ dependencies: cached_network_image: ^3.4.1 video_player: ^2.9.2 webview_flutter: ^4.9.0 + url_launcher: ^6.3.0 dev_dependencies: flutter_test: diff --git a/flutter/test/handler/action_handler_test.dart b/flutter/test/handler/action_handler_test.dart new file mode 100644 index 0000000..96b6d1a --- /dev/null +++ b/flutter/test/handler/action_handler_test.dart @@ -0,0 +1,107 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:clevertap_native_display/clevertap_native_display.dart'; + +void main() { + group('ActionHandler listener dispatch', () { + test('custom action calls listener with key', () async { + String? capturedAction; + String? capturedNodeId; + Map? capturedParams; + + final handler = ActionHandler( + listener: (action, nodeId, params) { + capturedAction = action; + capturedNodeId = nodeId; + capturedParams = params; + }, + ); + + final action = NDAction.fromJson({'type': 'custom', 'key': 'promo_click'}); + await handler.handle(action, 'btn1'); + + expect(capturedAction, 'custom'); + expect(capturedNodeId, 'btn1'); + expect(capturedParams?['key'], 'promo_click'); + }); + + test('navigate action calls listener with destination', () async { + String? capturedAction; + Map? capturedParams; + + final handler = ActionHandler( + listener: (action, nodeId, params) { + capturedAction = action; + capturedParams = params; + }, + ); + + final action = NDAction.fromJson({ + 'type': 'navigate', + 'destination': 'product_detail', + 'params': {'id': '42'}, + }); + await handler.handle(action, 'card1'); + + expect(capturedAction, 'navigate'); + expect(capturedParams?['destination'], 'product_detail'); + expect(capturedParams?['id'], '42'); + }); + + test('track event action calls listener with eventName', () async { + String? capturedAction; + Map? capturedParams; + + final handler = ActionHandler( + listener: (action, nodeId, params) { + capturedAction = action; + capturedParams = params; + }, + ); + + final action = NDAction.fromJson({ + 'type': 'event', + 'eventName': 'Banner Viewed', + 'properties': {'campaign': 'summer'}, + }); + await handler.handle(action, 'banner'); + + expect(capturedAction, 'event'); + expect(capturedParams?['eventName'], 'Banner Viewed'); + expect(capturedParams?['campaign'], 'summer'); + }); + + test('composite sequential action calls listener for each sub-action', () async { + final captured = []; + + final handler = ActionHandler( + listener: (action, nodeId, params) { + captured.add(action); + }, + ); + + final action = NDAction.fromJson({ + 'type': 'composite', + 'executionMode': 'sequential', + 'actions': [ + {'type': 'custom', 'key': 'first'}, + {'type': 'custom', 'key': 'second'}, + ], + }); + await handler.handle(action, 'node'); + + expect(captured, ['custom', 'custom']); + }); + + test('no listener — no crash on custom action', () async { + final handler = ActionHandler(); + final action = NDAction.fromJson({'type': 'custom', 'key': 'x'}); + await expectLater(handler.handle(action, 'n'), completes); + }); + + test('no listener — no crash on navigate action', () async { + final handler = ActionHandler(); + final action = NDAction.fromJson({'type': 'navigate', 'destination': 'home'}); + await expectLater(handler.handle(action, 'n'), completes); + }); + }); +} From 72e83a1b84ec60c79fea7feb0b2b5d90d8e6cfa0 Mon Sep 17 00:00:00 2001 From: CTLalit <144685420+CTLalit@users.noreply.github.com> Date: Mon, 25 May 2026 16:44:47 +0530 Subject: [PATCH 03/12] =?UTF-8?q?SDK-5834:=20Platform=20channel=20bridge?= =?UTF-8?q?=20=E2=80=94=20NativeDisplayBridge=20+=20Android/iOS=20plugin?= =?UTF-8?q?=20stubs=20(Flutter)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - NativeDisplayBridge.dart: static MethodChannel wrapper for com.clevertap.flutter.nativedisplay - fetchConfig(unitId): invokes fetchDisplayUnit, parses JSON into NativeDisplayConfig, returns null on PlatformException - pushViewedEvent(unitId): invokes pushViewedEvent - pushClickedEvent(unitId, elementId?): invokes pushClickedEvent; omits elementId key when null - Android: CleverTapNativeDisplayPlugin.kt — FlutterPlugin + MethodCallHandler; NativeDisplayPluginBridge interface for host-app delegation - iOS: CleverTapNativeDisplayPlugin.swift — FlutterPlugin; NativeDisplayPluginBridge protocol for host-app delegation - Export NativeDisplayBridge from clevertap_native_display.dart public API - Tests: 6 mock-MethodChannel tests covering fetchConfig (null, parse, PlatformException), pushViewedEvent, pushClickedEvent (with/without elementId) Co-Authored-By: Claude Opus 4.7 (1M context) --- .../CleverTapNativeDisplayPlugin.kt | 66 ++++++++++ .../CleverTapNativeDisplayPlugin.swift | 60 +++++++++ flutter/lib/clevertap_native_display.dart | 1 + .../lib/src/bridge/native_display_bridge.dart | 45 +++++++ .../bridge/native_display_bridge_test.dart | 115 ++++++++++++++++++ 5 files changed, 287 insertions(+) create mode 100644 flutter/android/src/main/kotlin/com/clevertap/flutter/nativedisplay/CleverTapNativeDisplayPlugin.kt create mode 100644 flutter/ios/Classes/CleverTapNativeDisplayPlugin.swift create mode 100644 flutter/lib/src/bridge/native_display_bridge.dart create mode 100644 flutter/test/bridge/native_display_bridge_test.dart diff --git a/flutter/android/src/main/kotlin/com/clevertap/flutter/nativedisplay/CleverTapNativeDisplayPlugin.kt b/flutter/android/src/main/kotlin/com/clevertap/flutter/nativedisplay/CleverTapNativeDisplayPlugin.kt new file mode 100644 index 0000000..22947c3 --- /dev/null +++ b/flutter/android/src/main/kotlin/com/clevertap/flutter/nativedisplay/CleverTapNativeDisplayPlugin.kt @@ -0,0 +1,66 @@ +package com.clevertap.flutter.nativedisplay + +import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import io.flutter.plugin.common.MethodChannel.MethodCallHandler +import io.flutter.plugin.common.MethodChannel.Result + +class CleverTapNativeDisplayPlugin : FlutterPlugin, MethodCallHandler { + + private lateinit var channel: MethodChannel + + // Set by the host app after initialising CleverTap Core SDK. + // The plugin delegates all display-unit calls to this bridge. + var bridge: NativeDisplayPluginBridge? = null + + override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) { + channel = MethodChannel(binding.binaryMessenger, CHANNEL) + channel.setMethodCallHandler(this) + } + + override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { + channel.setMethodCallHandler(null) + } + + override fun onMethodCall(call: MethodCall, result: Result) { + when (call.method) { + "fetchDisplayUnit" -> { + val unitId = call.argument("unitId") + if (unitId == null) { + result.error("INVALID_ARGUMENT", "unitId is required", null) + return + } + val json = bridge?.fetchDisplayUnit(unitId) + if (json != null) result.success(json) else result.success(null) + } + "pushViewedEvent" -> { + val unitId = call.argument("unitId") + if (unitId != null) bridge?.pushViewedEvent(unitId) + result.success(null) + } + "pushClickedEvent" -> { + val unitId = call.argument("unitId") + val elementId = call.argument("elementId") + if (unitId != null) bridge?.pushClickedEvent(unitId, elementId) + result.success(null) + } + else -> result.notImplemented() + } + } + + companion object { + const val CHANNEL = "com.clevertap.flutter.nativedisplay" + } +} + +interface NativeDisplayPluginBridge { + // Return display unit JSON string for the given unitId, or null if not found. + fun fetchDisplayUnit(unitId: String): String? + + // Report viewed event to CleverTap Core SDK. + fun pushViewedEvent(unitId: String) + + // Report clicked event to CleverTap Core SDK. + fun pushClickedEvent(unitId: String, elementId: String?) +} diff --git a/flutter/ios/Classes/CleverTapNativeDisplayPlugin.swift b/flutter/ios/Classes/CleverTapNativeDisplayPlugin.swift new file mode 100644 index 0000000..523e49e --- /dev/null +++ b/flutter/ios/Classes/CleverTapNativeDisplayPlugin.swift @@ -0,0 +1,60 @@ +import Flutter +import UIKit + +public class CleverTapNativeDisplayPlugin: NSObject, FlutterPlugin { + + // Set by the host app after initialising CleverTap Core SDK. + public var bridge: NativeDisplayPluginBridge? + + public static func register(with registrar: FlutterPluginRegistrar) { + let channel = FlutterMethodChannel( + name: "com.clevertap.flutter.nativedisplay", + binaryMessenger: registrar.messenger() + ) + let instance = CleverTapNativeDisplayPlugin() + registrar.addMethodCallDelegate(instance, channel: channel) + } + + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + guard let args = call.arguments as? [String: Any] else { + result(FlutterError(code: "INVALID_ARGUMENT", message: "Arguments missing", details: nil)) + return + } + + switch call.method { + case "fetchDisplayUnit": + guard let unitId = args["unitId"] as? String else { + result(FlutterError(code: "INVALID_ARGUMENT", message: "unitId is required", details: nil)) + return + } + result(bridge?.fetchDisplayUnit(unitId: unitId)) + + case "pushViewedEvent": + if let unitId = args["unitId"] as? String { + bridge?.pushViewedEvent(unitId: unitId) + } + result(nil) + + case "pushClickedEvent": + if let unitId = args["unitId"] as? String { + let elementId = args["elementId"] as? String + bridge?.pushClickedEvent(unitId: unitId, elementId: elementId) + } + result(nil) + + default: + result(FlutterMethodNotImplemented) + } + } +} + +public protocol NativeDisplayPluginBridge { + // Return display unit JSON string for the given unitId, or nil if not found. + func fetchDisplayUnit(unitId: String) -> String? + + // Report viewed event to CleverTap Core SDK. + func pushViewedEvent(unitId: String) + + // Report clicked event to CleverTap Core SDK. + func pushClickedEvent(unitId: String, elementId: String?) +} diff --git a/flutter/lib/clevertap_native_display.dart b/flutter/lib/clevertap_native_display.dart index 3158554..2083012 100644 --- a/flutter/lib/clevertap_native_display.dart +++ b/flutter/lib/clevertap_native_display.dart @@ -13,3 +13,4 @@ export 'src/models/native_display_config.dart'; export 'src/renderer/native_display_view.dart' show NativeDisplayView, NativeDisplayActionListener, NativeDisplayComponentListener; export 'src/handler/action_handler.dart' show ActionHandler; +export 'src/bridge/native_display_bridge.dart' show NativeDisplayBridge; diff --git a/flutter/lib/src/bridge/native_display_bridge.dart b/flutter/lib/src/bridge/native_display_bridge.dart new file mode 100644 index 0000000..2e43e91 --- /dev/null +++ b/flutter/lib/src/bridge/native_display_bridge.dart @@ -0,0 +1,45 @@ +import 'dart:convert'; + +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; + +import '../models/native_display_config.dart'; + +class NativeDisplayBridge { + static const _channel = MethodChannel('com.clevertap.flutter.nativedisplay'); + + // Fetch a display unit config JSON from the native CleverTap Core SDK by unitId. + // Returns null when the unit is not found or an error occurs. + static Future fetchConfig(String unitId) async { + try { + final result = await _channel.invokeMethod('fetchDisplayUnit', {'unitId': unitId}); + if (result == null) return null; + final json = jsonDecode(result) as Map; + return NativeDisplayConfig.fromJson(json); + } on PlatformException catch (e) { + debugPrint('[NativeDisplay] fetchConfig failed for $unitId: ${e.message}'); + return null; + } + } + + // Report a viewed event for the given display unit to the CleverTap Core SDK. + static Future pushViewedEvent(String unitId) async { + try { + await _channel.invokeMethod('pushViewedEvent', {'unitId': unitId}); + } on PlatformException catch (e) { + debugPrint('[NativeDisplay] pushViewedEvent failed for $unitId: ${e.message}'); + } + } + + // Report a clicked event for the given display unit and element to the CleverTap Core SDK. + static Future pushClickedEvent(String unitId, {String? elementId}) async { + try { + await _channel.invokeMethod('pushClickedEvent', { + 'unitId': unitId, + if (elementId != null) 'elementId': elementId, + }); + } on PlatformException catch (e) { + debugPrint('[NativeDisplay] pushClickedEvent failed for $unitId: ${e.message}'); + } + } +} diff --git a/flutter/test/bridge/native_display_bridge_test.dart b/flutter/test/bridge/native_display_bridge_test.dart new file mode 100644 index 0000000..85f11be --- /dev/null +++ b/flutter/test/bridge/native_display_bridge_test.dart @@ -0,0 +1,115 @@ +import 'dart:convert'; + +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:clevertap_native_display/clevertap_native_display.dart'; +import 'package:clevertap_native_display/src/bridge/native_display_bridge.dart'; + +const _channel = MethodChannel('com.clevertap.flutter.nativedisplay'); + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('NativeDisplayBridge', () { + setUp(() { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(_channel, null); + }); + + tearDown(() { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(_channel, null); + }); + + test('fetchConfig returns null when platform returns null', () async { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(_channel, (call) async { + if (call.method == 'fetchDisplayUnit') return null; + return null; + }); + + final result = await NativeDisplayBridge.fetchConfig('unit-1'); + expect(result, isNull); + }); + + test('fetchConfig parses returned JSON into NativeDisplayConfig', () async { + final configJson = jsonEncode({ + 'root': { + 'type': 'container', + 'id': 'root', + 'containerType': 'vertical', + 'children': [], + }, + }); + + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(_channel, (call) async { + if (call.method == 'fetchDisplayUnit') return configJson; + return null; + }); + + final result = await NativeDisplayBridge.fetchConfig('unit-1'); + expect(result, isNotNull); + expect(result!.root, isNotNull); + }); + + test('pushViewedEvent sends correct method and unitId', () async { + String? capturedMethod; + String? capturedUnitId; + + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(_channel, (call) async { + capturedMethod = call.method; + capturedUnitId = (call.arguments as Map)['unitId'] as String?; + return null; + }); + + await NativeDisplayBridge.pushViewedEvent('unit-42'); + + expect(capturedMethod, 'pushViewedEvent'); + expect(capturedUnitId, 'unit-42'); + }); + + test('pushClickedEvent sends correct method, unitId, and elementId', () async { + String? capturedMethod; + Map? capturedArgs; + + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(_channel, (call) async { + capturedMethod = call.method; + capturedArgs = call.arguments as Map; + return null; + }); + + await NativeDisplayBridge.pushClickedEvent('unit-7', elementId: 'btn-ok'); + + expect(capturedMethod, 'pushClickedEvent'); + expect(capturedArgs?['unitId'], 'unit-7'); + expect(capturedArgs?['elementId'], 'btn-ok'); + }); + + test('pushClickedEvent without elementId omits elementId from args', () async { + Map? capturedArgs; + + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(_channel, (call) async { + capturedArgs = call.arguments as Map; + return null; + }); + + await NativeDisplayBridge.pushClickedEvent('unit-8'); + + expect(capturedArgs?.containsKey('elementId'), false); + }); + + test('fetchConfig returns null and does not throw on PlatformException', () async { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(_channel, (call) async { + throw PlatformException(code: 'NOT_FOUND', message: 'Unit not found'); + }); + + final result = await NativeDisplayBridge.fetchConfig('missing'); + expect(result, isNull); + }); + }); +} From d9ab44095f5e5b1c263947d344df648d4d9ff2fe Mon Sep 17 00:00:00 2001 From: CTLalit <144685420+CTLalit@users.noreply.github.com> Date: Mon, 25 May 2026 16:46:05 +0530 Subject: [PATCH 04/12] fix(iOS): use conditional import for Flutter module in bridge plugin SourceKit cannot resolve 'import Flutter' outside a full Xcode/Flutter build context. The standard fix for Flutter plugin files is the canImport guard. UIKit was unused and removed. Co-Authored-By: Claude Opus 4.7 (1M context) --- flutter/ios/Classes/CleverTapNativeDisplayPlugin.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/flutter/ios/Classes/CleverTapNativeDisplayPlugin.swift b/flutter/ios/Classes/CleverTapNativeDisplayPlugin.swift index 523e49e..29da409 100644 --- a/flutter/ios/Classes/CleverTapNativeDisplayPlugin.swift +++ b/flutter/ios/Classes/CleverTapNativeDisplayPlugin.swift @@ -1,5 +1,8 @@ +#if canImport(Flutter) import Flutter -import UIKit +#elseif canImport(FlutterMacOS) +import FlutterMacOS +#endif public class CleverTapNativeDisplayPlugin: NSObject, FlutterPlugin { From 4de502b8d2b71091d22106ee53a53912f7839011 Mon Sep 17 00:00:00 2001 From: CTLalit <144685420+CTLalit@users.noreply.github.com> Date: Mon, 25 May 2026 16:58:12 +0530 Subject: [PATCH 05/12] =?UTF-8?q?SDK-5835:=20Flutter=20sample=20app=20?= =?UTF-8?q?=E2=80=94=20mirrors=20Android/iOS=20sample=20screens?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 4-tab bottom nav (Banners, Browser, Demos, More) mirroring Android/iOS apps - BannerShowcaseScreen: scrollable list of 10 banner cards with detail navigation - BannerDetailScreen: full-screen single banner display - ArrangementDemoScreen: interactive chip strip switching all 7 strategies - AnimationDemoScreen: 3 animation demos with info card, keyed for rebuild - HomeScreenDemo: loads home_screen.json with action/component listeners - OtherDemosScreen: tabbed view (Home, Gallery, E-commerce, Social, Dashboard) - BridgeIntegrationScreen: loads mock product/notification configs, shows API snippet - SlotDemoScreen: native display units interleaved with mock feed items - TestBrowserScreen: prev/next navigation across 181 test-config filenames - JsonViewerScreen: scrollable raw JSON with copy-to-clipboard - MoreMenuScreen: list navigation to all demo screens - Assets: 10 banner JSONs + 12 config JSONs copied from android-sample - flutter analyze: no issues Co-Authored-By: Claude Opus 4.7 (1M context) --- flutter-sample/.flutter-plugins | 24 + flutter-sample/.flutter-plugins-dependencies | 1 + .../banners/banner-01-hero-summer-sale.json | 162 ++ .../banners/banner-02-product-iphone.json | 186 ++ .../banner-03-announcement-update.json | 123 ++ .../banners/banner-04-travel-deals.json | 226 +++ .../banners/banner-05-fashion-collection.json | 127 ++ .../banners/banner-06-credit-card-offer.json | 211 ++ .../assets/banners/banner-07-app-rating.json | 143 ++ .../assets/banners/banner-08-flash-sale.json | 155 ++ .../banner-09-premium-subscription.json | 217 ++ .../banners/banner-10-welcome-onboarding.json | 185 ++ .../animation_container_and_children.json | 333 ++++ .../configs/animation_container_fade.json | 119 ++ .../configs/animation_staggered_children.json | 293 +++ .../assets/configs/arrangement_demo.json | 198 ++ .../configs/bridge_mock_notification.json | 104 + .../assets/configs/bridge_mock_product.json | 103 + .../assets/configs/gallery_three_modes.json | 362 ++++ .../assets/configs/home_screen.json | 1759 +++++++++++++++++ .../assets/configs/showcase_dashboard.json | 868 ++++++++ .../configs/showcase_ecommerce_product.json | 522 +++++ .../configs/showcase_profile_screen.json | 734 +++++++ .../configs/showcase_social_profile.json | 732 +++++++ flutter-sample/lib/app.dart | 60 + flutter-sample/lib/main.dart | 7 + .../lib/screens/animation_demo_screen.dart | 114 ++ .../lib/screens/arrangement_demo_screen.dart | 150 ++ .../lib/screens/banner_detail_screen.dart | 37 + .../lib/screens/banner_showcase_screen.dart | 161 ++ .../screens/bridge_integration_screen.dart | 156 ++ .../lib/screens/home_screen_demo.dart | 50 + .../lib/screens/json_viewer_screen.dart | 66 + .../lib/screens/more_menu_screen.dart | 104 + .../lib/screens/other_demos_screen.dart | 87 + .../lib/screens/slot_demo_screen.dart | 79 + .../lib/screens/test_browser_screen.dart | 347 ++++ flutter-sample/lib/widgets/error_widget.dart | 46 + flutter-sample/lib/widgets/json_loader.dart | 27 + flutter-sample/lib/widgets/nd_demo_card.dart | 61 + flutter-sample/pubspec.lock | 609 ++++++ flutter-sample/pubspec.yaml | 25 + 42 files changed, 10073 insertions(+) create mode 100644 flutter-sample/.flutter-plugins create mode 100644 flutter-sample/.flutter-plugins-dependencies create mode 100644 flutter-sample/assets/banners/banner-01-hero-summer-sale.json create mode 100644 flutter-sample/assets/banners/banner-02-product-iphone.json create mode 100644 flutter-sample/assets/banners/banner-03-announcement-update.json create mode 100644 flutter-sample/assets/banners/banner-04-travel-deals.json create mode 100644 flutter-sample/assets/banners/banner-05-fashion-collection.json create mode 100644 flutter-sample/assets/banners/banner-06-credit-card-offer.json create mode 100644 flutter-sample/assets/banners/banner-07-app-rating.json create mode 100644 flutter-sample/assets/banners/banner-08-flash-sale.json create mode 100644 flutter-sample/assets/banners/banner-09-premium-subscription.json create mode 100644 flutter-sample/assets/banners/banner-10-welcome-onboarding.json create mode 100644 flutter-sample/assets/configs/animation_container_and_children.json create mode 100644 flutter-sample/assets/configs/animation_container_fade.json create mode 100644 flutter-sample/assets/configs/animation_staggered_children.json create mode 100644 flutter-sample/assets/configs/arrangement_demo.json create mode 100644 flutter-sample/assets/configs/bridge_mock_notification.json create mode 100644 flutter-sample/assets/configs/bridge_mock_product.json create mode 100644 flutter-sample/assets/configs/gallery_three_modes.json create mode 100644 flutter-sample/assets/configs/home_screen.json create mode 100644 flutter-sample/assets/configs/showcase_dashboard.json create mode 100644 flutter-sample/assets/configs/showcase_ecommerce_product.json create mode 100644 flutter-sample/assets/configs/showcase_profile_screen.json create mode 100644 flutter-sample/assets/configs/showcase_social_profile.json create mode 100644 flutter-sample/lib/app.dart create mode 100644 flutter-sample/lib/main.dart create mode 100644 flutter-sample/lib/screens/animation_demo_screen.dart create mode 100644 flutter-sample/lib/screens/arrangement_demo_screen.dart create mode 100644 flutter-sample/lib/screens/banner_detail_screen.dart create mode 100644 flutter-sample/lib/screens/banner_showcase_screen.dart create mode 100644 flutter-sample/lib/screens/bridge_integration_screen.dart create mode 100644 flutter-sample/lib/screens/home_screen_demo.dart create mode 100644 flutter-sample/lib/screens/json_viewer_screen.dart create mode 100644 flutter-sample/lib/screens/more_menu_screen.dart create mode 100644 flutter-sample/lib/screens/other_demos_screen.dart create mode 100644 flutter-sample/lib/screens/slot_demo_screen.dart create mode 100644 flutter-sample/lib/screens/test_browser_screen.dart create mode 100644 flutter-sample/lib/widgets/error_widget.dart create mode 100644 flutter-sample/lib/widgets/json_loader.dart create mode 100644 flutter-sample/lib/widgets/nd_demo_card.dart create mode 100644 flutter-sample/pubspec.lock create mode 100644 flutter-sample/pubspec.yaml diff --git a/flutter-sample/.flutter-plugins b/flutter-sample/.flutter-plugins new file mode 100644 index 0000000..c4b8788 --- /dev/null +++ b/flutter-sample/.flutter-plugins @@ -0,0 +1,24 @@ +# This is a generated file; do not edit or check into version control. +clevertap_native_display=/Users/lalitkumar/StudioProjects/clevertap-native-ui-kit/flutter/ +path_provider=/Users/lalitkumar/.pub-cache/hosted/pub.dev/path_provider-2.1.5/ +path_provider_android=/Users/lalitkumar/.pub-cache/hosted/pub.dev/path_provider_android-2.2.19/ +path_provider_foundation=/Users/lalitkumar/.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.2/ +path_provider_linux=/Users/lalitkumar/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/ +path_provider_windows=/Users/lalitkumar/.pub-cache/hosted/pub.dev/path_provider_windows-2.3.0/ +sqflite=/Users/lalitkumar/.pub-cache/hosted/pub.dev/sqflite-2.4.2/ +sqflite_android=/Users/lalitkumar/.pub-cache/hosted/pub.dev/sqflite_android-2.4.1/ +sqflite_darwin=/Users/lalitkumar/.pub-cache/hosted/pub.dev/sqflite_darwin-2.4.2/ +url_launcher=/Users/lalitkumar/.pub-cache/hosted/pub.dev/url_launcher-6.3.2/ +url_launcher_android=/Users/lalitkumar/.pub-cache/hosted/pub.dev/url_launcher_android-6.3.20/ +url_launcher_ios=/Users/lalitkumar/.pub-cache/hosted/pub.dev/url_launcher_ios-6.3.4/ +url_launcher_linux=/Users/lalitkumar/.pub-cache/hosted/pub.dev/url_launcher_linux-3.2.1/ +url_launcher_macos=/Users/lalitkumar/.pub-cache/hosted/pub.dev/url_launcher_macos-3.2.3/ +url_launcher_web=/Users/lalitkumar/.pub-cache/hosted/pub.dev/url_launcher_web-2.4.1/ +url_launcher_windows=/Users/lalitkumar/.pub-cache/hosted/pub.dev/url_launcher_windows-3.1.4/ +video_player=/Users/lalitkumar/.pub-cache/hosted/pub.dev/video_player-2.10.1/ +video_player_android=/Users/lalitkumar/.pub-cache/hosted/pub.dev/video_player_android-2.8.15/ +video_player_avfoundation=/Users/lalitkumar/.pub-cache/hosted/pub.dev/video_player_avfoundation-2.8.4/ +video_player_web=/Users/lalitkumar/.pub-cache/hosted/pub.dev/video_player_web-2.4.0/ +webview_flutter=/Users/lalitkumar/.pub-cache/hosted/pub.dev/webview_flutter-4.13.0/ +webview_flutter_android=/Users/lalitkumar/.pub-cache/hosted/pub.dev/webview_flutter_android-4.10.1/ +webview_flutter_wkwebview=/Users/lalitkumar/.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.23.0/ diff --git a/flutter-sample/.flutter-plugins-dependencies b/flutter-sample/.flutter-plugins-dependencies new file mode 100644 index 0000000..700b01e --- /dev/null +++ b/flutter-sample/.flutter-plugins-dependencies @@ -0,0 +1 @@ +{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"clevertap_native_display","path":"/Users/lalitkumar/StudioProjects/clevertap-native-ui-kit/flutter/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_foundation","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.2/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"sqflite_darwin","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/sqflite_darwin-2.4.2/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_ios","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/url_launcher_ios-6.3.4/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"video_player_avfoundation","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/video_player_avfoundation-2.8.4/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"webview_flutter_wkwebview","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.23.0/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"clevertap_native_display","path":"/Users/lalitkumar/StudioProjects/clevertap-native-ui-kit/flutter/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_android","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/path_provider_android-2.2.19/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"sqflite_android","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/sqflite_android-2.4.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_android","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/url_launcher_android-6.3.20/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"video_player_android","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/video_player_android-2.8.15/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"webview_flutter_android","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/webview_flutter_android-4.10.1/","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[{"name":"path_provider_foundation","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.2/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"sqflite_darwin","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/sqflite_darwin-2.4.2/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_macos","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/url_launcher_macos-3.2.3/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"video_player_avfoundation","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/video_player_avfoundation-2.8.4/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"webview_flutter_wkwebview","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.23.0/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"linux":[{"name":"path_provider_linux","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_linux","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/url_launcher_linux-3.2.1/","native_build":true,"dependencies":[],"dev_dependency":false}],"windows":[{"name":"path_provider_windows","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/path_provider_windows-2.3.0/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_windows","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/url_launcher_windows-3.1.4/","native_build":true,"dependencies":[],"dev_dependency":false}],"web":[{"name":"url_launcher_web","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/url_launcher_web-2.4.1/","dependencies":[],"dev_dependency":false},{"name":"video_player_web","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/video_player_web-2.4.0/","dependencies":[],"dev_dependency":false}]},"dependencyGraph":[{"name":"clevertap_native_display","dependencies":["video_player","webview_flutter","url_launcher"]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"sqflite","dependencies":["sqflite_android","sqflite_darwin"]},{"name":"sqflite_android","dependencies":[]},{"name":"sqflite_darwin","dependencies":[]},{"name":"url_launcher","dependencies":["url_launcher_android","url_launcher_ios","url_launcher_linux","url_launcher_macos","url_launcher_web","url_launcher_windows"]},{"name":"url_launcher_android","dependencies":[]},{"name":"url_launcher_ios","dependencies":[]},{"name":"url_launcher_linux","dependencies":[]},{"name":"url_launcher_macos","dependencies":[]},{"name":"url_launcher_web","dependencies":[]},{"name":"url_launcher_windows","dependencies":[]},{"name":"video_player","dependencies":["video_player_android","video_player_avfoundation","video_player_web"]},{"name":"video_player_android","dependencies":[]},{"name":"video_player_avfoundation","dependencies":[]},{"name":"video_player_web","dependencies":[]},{"name":"webview_flutter","dependencies":["webview_flutter_android","webview_flutter_wkwebview"]},{"name":"webview_flutter_android","dependencies":[]},{"name":"webview_flutter_wkwebview","dependencies":[]}],"date_created":"2026-05-25 16:57:40.544270","version":"3.29.0","swift_package_manager_enabled":{"ios":false,"macos":false}} \ No newline at end of file diff --git a/flutter-sample/assets/banners/banner-01-hero-summer-sale.json b/flutter-sample/assets/banners/banner-01-hero-summer-sale.json new file mode 100644 index 0000000..b6ce722 --- /dev/null +++ b/flutter-sample/assets/banners/banner-01-hero-summer-sale.json @@ -0,0 +1,162 @@ +{ + "theme": { + "id": "banner-01", + "defaultStyle": { + "textColor": "#000000", + "fontSize": 16 + } + }, + "variables": { + "discount": "50%" + }, + "root": { + "type": "container", + "id": "banner-root", + "containerType": "box", + "layout": { + "width": { "value": 100, "unit": "percent" }, + "aspectRatio": 0.55 + }, + "style": { + "background": { + "type": "linear_gradient", + "angle": 135, + "colors": ["#FF6B6B", "#FFE66D"] + }, + "borderRadius": 16 + }, + "animation": { + "type": "fade_in", + "duration": 500, + "easing": "ease_out" + }, + "children": [ + { + "type": "element", + "id": "discount-badge", + "elementType": "text", + "bindings": { + "text": "{{discount}} OFF" + }, + "layout": { + "width": { "value": 28, "unit": "percent" }, + "height": { "value": 15, "unit": "percent" }, + "offset": { "x": 5, "y": 5, "unit": "percent" } + }, + "style": { + "fontSize": 22, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "textAlign": "center", + "backgroundColor": "#FF3B3BDD", + "borderRadius": 20 + } + }, + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "SUMMER SALE" + }, + "layout": { + "width": { "value": 100, "unit": "percent" }, + "offset": { "x": 0, "y": 28, "unit": "percent" } + }, + "style": { + "fontSize": 44, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "textAlign": "center", + "shadowColor": "#00000060", + "shadowRadius": 8, + "shadowOffsetX": 0, + "shadowOffsetY": 4 + } + }, + { + "type": "element", + "id": "subtitle", + "elementType": "text", + "bindings": { + "text": "Shop the hottest deals" + }, + "layout": { + "width": { "value": 100, "unit": "percent" }, + "offset": { "x": 0, "y": 52, "unit": "percent" } + }, + "style": { + "fontSize": 18, + "fontWeight": "medium", + "textColor": "#FFFFFF", + "textAlign": "center", + "opacity": 0.95 + } + }, + { + "type": "element", + "id": "shop-button", + "elementType": "button", + "bindings": { + "text": "Shop Now" + }, + "layout": { + "width": { "value": 42, "unit": "percent" }, + "height": { "value": 18, "unit": "percent" }, + "offset": { "x": 6, "y": 72, "unit": "percent" } + }, + "style": { + "backgroundColor": "#FFFFFF", + "textColor": "#FF6B6B", + "fontSize": 18, + "fontWeight": "bold", + "borderRadius": 28, + "shadowColor": "#00000050", + "shadowRadius": 12, + "shadowOffsetX": 0, + "shadowOffsetY": 6 + }, + "actions": { + "onClick": { + "type": "custom", + "key": "shop_now", + "value": { + "campaign": "summer_sale" + } + } + } + }, + { + "type": "element", + "id": "browse-button", + "elementType": "button", + "bindings": { + "text": "Browse All" + }, + "layout": { + "width": { "value": 42, "unit": "percent" }, + "height": { "value": 18, "unit": "percent" }, + "offset": { "x": 52, "y": 72, "unit": "percent" } + }, + "style": { + "backgroundColor": "#FFFFFF40", + "textColor": "#FFFFFF", + "fontSize": 18, + "fontWeight": "bold", + "borderRadius": 28, + "borderWidth": 2, + "borderColor": "#FFFFFF" + }, + "actions": { + "onClick": { + "type": "custom", + "key": "browse_all", + "value": { + "campaign": "summer_sale" + } + } + } + } + ] + } +} diff --git a/flutter-sample/assets/banners/banner-02-product-iphone.json b/flutter-sample/assets/banners/banner-02-product-iphone.json new file mode 100644 index 0000000..fd3e606 --- /dev/null +++ b/flutter-sample/assets/banners/banner-02-product-iphone.json @@ -0,0 +1,186 @@ +{ + "theme": { + "id": "banner-02", + "defaultStyle": { + "textColor": "#FFFFFF", + "fontSize": 16 + } + }, + "variables": { + "productName": "iPhone 15 Pro", + "tagline": "Titanium Design" + }, + "root": { + "type": "container", + "id": "banner-root", + "containerType": "box", + "layout": { + "width": { "value": 100, "unit": "percent" }, + "aspectRatio": 0.62 + }, + "style": { + "backgroundColor": "#000000", + "borderRadius": 16 + }, + "animation": { + "type": "slide_in_left", + "duration": 600, + "easing": "ease_in_out" + }, + "children": [ + { + "type": "element", + "id": "dark-overlay", + "elementType": "text", + "bindings": { + "text": "" + }, + "layout": { + "width": { "value": 100, "unit": "percent" }, + "height": { "value": 100, "unit": "percent" }, + "offset": { "x": 0, "y": 0, "unit": "percent" } + }, + "style": { + "backgroundColor": "#00000050" + } + }, + { + "type": "element", + "id": "product-image", + "elementType": "image", + "bindings": { + "url": "https://cdn.dummyjson.com/products/images/smartphones/iPhone%205s/1.png" + }, + "layout": { + "width": { "value": 48, "unit": "percent" }, + "height": { "value": 80, "unit": "percent" }, + "offset": { "x": 4, "y": 10, "unit": "percent" } + }, + "style": { + "borderRadius": 12 + } + }, + { + "type": "element", + "id": "new-badge", + "elementType": "text", + "bindings": { + "text": "NEW" + }, + "layout": { + "width": { "value": 18, "unit": "percent" }, + "height": { "value": 10, "unit": "percent" }, + "offset": { "x": 56, "y": 8, "unit": "percent" } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "textAlign": "center", + "backgroundColor": "#007AFFEE", + "borderRadius": 16 + } + }, + { + "type": "element", + "id": "product-name", + "elementType": "text", + "bindings": { + "text": "{{productName}}" + }, + "layout": { + "width": { "value": 42, "unit": "percent" }, + "offset": { "x": 54, "y": 25, "unit": "percent" } + }, + "style": { + "fontSize": 30, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "textAlign": "left" + } + }, + { + "type": "element", + "id": "product-tagline", + "elementType": "text", + "bindings": { + "text": "{{tagline}}" + }, + "layout": { + "width": { "value": 42, "unit": "percent" }, + "offset": { "x": 54, "y": 43, "unit": "percent" } + }, + "style": { + "fontSize": 16, + "fontWeight": "normal", + "textColor": "#AAAAAA", + "textAlign": "left" + } + }, + { + "type": "element", + "id": "learn-button", + "elementType": "button", + "bindings": { + "text": "Learn More" + }, + "layout": { + "width": { "value": 40, "unit": "percent" }, + "height": { "value": 16, "unit": "percent" }, + "offset": { "x": 54, "y": 60, "unit": "percent" } + }, + "style": { + "backgroundColor": "#007AFF", + "textColor": "#FFFFFF", + "fontSize": 16, + "fontWeight": "bold", + "borderRadius": 24, + "shadowColor": "#007AFF70", + "shadowRadius": 14, + "shadowOffsetX": 0, + "shadowOffsetY": 6 + }, + "actions": { + "onClick": { + "type": "custom", + "key": "view_product", + "value": { + "product_id": "iphone_15_pro" + } + } + } + }, + { + "type": "element", + "id": "buy-button", + "elementType": "button", + "bindings": { + "text": "Buy Now" + }, + "layout": { + "width": { "value": 40, "unit": "percent" }, + "height": { "value": 16, "unit": "percent" }, + "offset": { "x": 54, "y": 79, "unit": "percent" } + }, + "style": { + "backgroundColor": "#FFFFFF20", + "textColor": "#FFFFFF", + "fontSize": 16, + "fontWeight": "bold", + "borderRadius": 24, + "borderWidth": 2, + "borderColor": "#FFFFFF" + }, + "actions": { + "onClick": { + "type": "custom", + "key": "buy_product", + "value": { + "product_id": "iphone_15_pro" + } + } + } + } + ] + } +} diff --git a/flutter-sample/assets/banners/banner-03-announcement-update.json b/flutter-sample/assets/banners/banner-03-announcement-update.json new file mode 100644 index 0000000..1295bea --- /dev/null +++ b/flutter-sample/assets/banners/banner-03-announcement-update.json @@ -0,0 +1,123 @@ +{ + "theme": { + "id": "banner-03", + "defaultStyle": { + "textColor": "#1565C0", + "fontSize": 14 + } + }, + "variables": { + "message": "New Features Available!" + }, + "root": { + "type": "container", + "id": "banner-root", + "containerType": "box", + "layout": { + "width": { "value": 100, "unit": "percent" }, + "aspectRatio": 0.28 + }, + "style": { + "background": { + "type": "radial_gradient", + "center_x": 0.5, + "center_y": 0.5, + "radius": 1.0, + "colors": ["#E3F2FD", "#BBDEFB"] + }, + "borderRadius": 16 + }, + "animation": { + "type": "scale_in", + "duration": 400, + "easing": "ease_out" + }, + "children": [ + { + "type": "element", + "id": "update-badge", + "elementType": "text", + "bindings": { + "text": "UPDATE" + }, + "layout": { + "width": { "value": 22, "unit": "percent" }, + "height": { "value": 35, "unit": "percent" }, + "offset": { "x": 4, "y": 32, "unit": "percent" } + }, + "style": { + "fontSize": 13, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "textAlign": "center", + "backgroundColor": "#1976D2EE", + "borderRadius": 18 + } + }, + { + "type": "element", + "id": "announcement-text", + "elementType": "text", + "bindings": { + "text": "🎉 {{message}}" + }, + "layout": { + "width": { "value": 50, "unit": "percent" }, + "offset": { "x": 28, "y": 20, "unit": "percent" } + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "textColor": "#1565C0", + "textAlign": "left" + } + }, + { + "type": "element", + "id": "update-text", + "elementType": "text", + "bindings": { + "text": "Tap to learn more" + }, + "layout": { + "width": { "value": 50, "unit": "percent" }, + "offset": { "x": 28, "y": 58, "unit": "percent" } + }, + "style": { + "fontSize": 13, + "fontWeight": "normal", + "textColor": "#1976D2", + "textAlign": "left", + "opacity": 0.85 + } + }, + { + "type": "element", + "id": "close-button", + "elementType": "button", + "bindings": { + "text": "✕" + }, + "layout": { + "width": { "value": 12, "unit": "percent" }, + "height": { "value": 52, "unit": "percent" }, + "offset": { "x": 84, "y": 24, "unit": "percent" } + }, + "style": { + "backgroundColor": "#90CAF9BB", + "textColor": "#1565C0", + "fontSize": 20, + "fontWeight": "bold", + "borderRadius": 20 + }, + "actions": { + "onClick": { + "type": "custom", + "key": "dismiss", + "value": {} + } + } + } + ] + } +} diff --git a/flutter-sample/assets/banners/banner-04-travel-deals.json b/flutter-sample/assets/banners/banner-04-travel-deals.json new file mode 100644 index 0000000..c8e9965 --- /dev/null +++ b/flutter-sample/assets/banners/banner-04-travel-deals.json @@ -0,0 +1,226 @@ +{ + "theme": { + "id": "banner-04", + "defaultStyle": { + "textColor": "#FFFFFF", + "fontSize": 16 + } + }, + "variables": { + "title": "Travel the World", + "subtitle": "Exclusive Deals", + "badge": "HOT DEAL" + }, + "root": { + "type": "container", + "id": "banner-root", + "containerType": "box", + "layout": { + "width": { "value": 100, "unit": "percent" }, + "aspectRatio": 0.55 + }, + "style": { + "backgroundColor": "#000000", + "borderRadius": 16 + }, + "animation": { + "type": "fade_in", + "duration": 700, + "easing": "ease_in_out" + }, + "children": [ + { + "type": "element", + "id": "background-image", + "elementType": "image", + "bindings": { + "url": "https://dummyjson.com/image/600x400/1E88E5/FFFFFF?text=TRAVEL&fontSize=48&fontFamily=poppins" + }, + "layout": { + "width": { "value": 100, "unit": "percent" }, + "height": { "value": 100, "unit": "percent" }, + "offset": { "x": 0, "y": 0, "unit": "percent" } + }, + "style": { + "borderRadius": 16 + } + }, + { + "type": "element", + "id": "dark-overlay", + "elementType": "text", + "bindings": { + "text": "" + }, + "layout": { + "width": { "value": 100, "unit": "percent" }, + "height": { "value": 100, "unit": "percent" }, + "offset": { "x": 0, "y": 0, "unit": "percent" } + }, + "style": { + "backgroundColor": "#00000050", + "borderRadius": 16 + } + }, + { + "type": "element", + "id": "hot-deal-badge", + "elementType": "text", + "bindings": { + "text": "{{badge}}" + }, + "layout": { + "width": { "value": 32, "unit": "percent" }, + "height": { "value": 10, "unit": "percent" }, + "offset": { "x": 5, "y": 5, "unit": "percent" } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "textAlign": "center", + "backgroundColor": "#FF5722DD", + "borderRadius": 20, + "shadowColor": "#FF572260", + "shadowRadius": 8, + "shadowOffsetX": 0, + "shadowOffsetY": 4 + } + }, + { + "type": "element", + "id": "title-text", + "elementType": "text", + "bindings": { + "text": "{{title}}" + }, + "layout": { + "width": { "value": 100, "unit": "percent" }, + "offset": { "x": 0, "y": 20, "unit": "percent" } + }, + "style": { + "fontSize": 36, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "textAlign": "center", + "shadowColor": "#00000080", + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 3 + } + }, + { + "type": "element", + "id": "subtitle-text", + "elementType": "text", + "bindings": { + "text": "{{subtitle}}" + }, + "layout": { + "width": { "value": 100, "unit": "percent" }, + "offset": { "x": 0, "y": 38, "unit": "percent" } + }, + "style": { + "fontSize": 18, + "fontWeight": "medium", + "textColor": "#FFFFFF", + "textAlign": "center", + "opacity": 0.95 + } + }, + { + "type": "element", + "id": "flights-button", + "elementType": "button", + "bindings": { + "text": "✈️ Flights" + }, + "layout": { + "width": { "value": 28, "unit": "percent" }, + "height": { "value": 16, "unit": "percent" }, + "offset": { "x": 6, "y": 70, "unit": "percent" } + }, + "style": { + "backgroundColor": "#4CAF50", + "textColor": "#FFFFFF", + "fontSize": 16, + "fontWeight": "bold", + "borderRadius": 24, + "shadowColor": "#00000040", + "shadowRadius": 8, + "shadowOffsetX": 0, + "shadowOffsetY": 4 + }, + "actions": { + "onClick": { + "type": "custom", + "key": "view_flights", + "value": {} + } + } + }, + { + "type": "element", + "id": "hotels-button", + "elementType": "button", + "bindings": { + "text": "🏨 Hotels" + }, + "layout": { + "width": { "value": 28, "unit": "percent" }, + "height": { "value": 16, "unit": "percent" }, + "offset": { "x": 36, "y": 70, "unit": "percent" } + }, + "style": { + "backgroundColor": "#2196F3", + "textColor": "#FFFFFF", + "fontSize": 16, + "fontWeight": "bold", + "borderRadius": 24, + "shadowColor": "#00000040", + "shadowRadius": 8, + "shadowOffsetX": 0, + "shadowOffsetY": 4 + }, + "actions": { + "onClick": { + "type": "custom", + "key": "view_hotels", + "value": {} + } + } + }, + { + "type": "element", + "id": "packages-button", + "elementType": "button", + "bindings": { + "text": "📦 Packages" + }, + "layout": { + "width": { "value": 28, "unit": "percent" }, + "height": { "value": 16, "unit": "percent" }, + "offset": { "x": 66, "y": 70, "unit": "percent" } + }, + "style": { + "backgroundColor": "#FF9800", + "textColor": "#FFFFFF", + "fontSize": 16, + "fontWeight": "bold", + "borderRadius": 24, + "shadowColor": "#00000040", + "shadowRadius": 8, + "shadowOffsetX": 0, + "shadowOffsetY": 4 + }, + "actions": { + "onClick": { + "type": "custom", + "key": "view_packages", + "value": {} + } + } + } + ] + } +} diff --git a/flutter-sample/assets/banners/banner-05-fashion-collection.json b/flutter-sample/assets/banners/banner-05-fashion-collection.json new file mode 100644 index 0000000..70b041a --- /dev/null +++ b/flutter-sample/assets/banners/banner-05-fashion-collection.json @@ -0,0 +1,127 @@ +{ + "theme": { + "id": "banner-05", + "defaultStyle": { + "textColor": "#000000", + "fontSize": 16 + } + }, + "variables": { + "collectionName": "NEW COLLECTION", + "subtitle": "Winter 2026", + "discount": "UP TO 50% OFF" + }, + "root": { + "type": "container", + "id": "banner-root", + "containerType": "box", + "layout": { + "width": { "value": 100, "unit": "percent" }, + "aspectRatio": 0.6 + }, + "style": { + "backgroundColor": "#F5F5F5", + "borderRadius": 16 + }, + "children": [ + { + "type": "element", + "id": "fashion-image", + "elementType": "image", + "bindings": { + "url": "https://cdn.dummyjson.com/products/images/womens-dresses/Black%20Elegant%20Cocktail%20Dress/1.png" + }, + "layout": { + "width": { "value": 100, "unit": "percent" }, + "height": { "value": 100, "unit": "percent" }, + "offset": { "x": 0, "y": 0, "unit": "percent" } + }, + "style": { + "borderRadius": 16 + } + }, + { + "type": "element", + "id": "gradient-overlay", + "elementType": "text", + "bindings": { + "text": "" + }, + "layout": { + "width": { "value": 100, "unit": "percent" }, + "height": { "value": 40, "unit": "percent" }, + "offset": { "x": 0, "y": 60, "unit": "percent" } + }, + "style": { + "backgroundColor": "#00000070", + "borderRadius": 0 + } + }, + { + "type": "element", + "id": "discount-badge", + "elementType": "text", + "bindings": { + "text": "{{discount}}" + }, + "layout": { + "width": { "value": 48, "unit": "percent" }, + "height": { "value": 9, "unit": "percent" }, + "offset": { "x": 4, "y": 4, "unit": "percent" } + }, + "style": { + "fontSize": 13, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "textAlign": "center", + "backgroundColor": "#E91E63DD", + "borderRadius": 18, + "shadowColor": "#E91E6360", + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 3 + } + }, + { + "type": "element", + "id": "collection-label", + "elementType": "text", + "bindings": { + "text": "{{collectionName}}" + }, + "layout": { + "width": { "value": 90, "unit": "percent" }, + "offset": { "x": 5, "y": 68, "unit": "percent" } + }, + "style": { + "fontSize": 26, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "textAlign": "center", + "shadowColor": "#00000060", + "shadowRadius": 4, + "shadowOffsetX": 0, + "shadowOffsetY": 2 + } + }, + { + "type": "element", + "id": "subtitle-text", + "elementType": "text", + "bindings": { + "text": "{{subtitle}}" + }, + "layout": { + "width": { "value": 90, "unit": "percent" }, + "offset": { "x": 5, "y": 82, "unit": "percent" } + }, + "style": { + "fontSize": 16, + "fontWeight": "medium", + "textColor": "#FFFFFFDD", + "textAlign": "center" + } + } + ] + } +} diff --git a/flutter-sample/assets/banners/banner-06-credit-card-offer.json b/flutter-sample/assets/banners/banner-06-credit-card-offer.json new file mode 100644 index 0000000..ebedaf6 --- /dev/null +++ b/flutter-sample/assets/banners/banner-06-credit-card-offer.json @@ -0,0 +1,211 @@ +{ + "theme": { + "id": "banner-06", + "defaultStyle": { + "textColor": "#FFFFFF", + "fontSize": 16 + } + }, + "variables": { + "cashback": "5%", + "terms": "*T&C Apply", + "badge": "5% OFF" + }, + "root": { + "type": "container", + "id": "banner-root", + "containerType": "box", + "layout": { + "width": { "value": 100, "unit": "percent" }, + "aspectRatio": 0.7 + }, + "style": { + "background": { + "type": "linear_gradient", + "angle": 135, + "colors": ["#667EEA", "#764BA2"] + }, + "borderRadius": 16 + }, + "children": [ + { + "type": "element", + "id": "discount-badge", + "elementType": "text", + "bindings": { + "text": "{{badge}}" + }, + "layout": { + "width": { "value": 22, "unit": "percent" }, + "height": { "value": 8, "unit": "percent" }, + "offset": { "x": 74, "y": 4, "unit": "percent" } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "textAlign": "center", + "backgroundColor": "#00C853DD", + "borderRadius": 20, + "shadowColor": "#00C85360", + "shadowRadius": 8, + "shadowOffsetX": 0, + "shadowOffsetY": 4 + } + }, + { + "type": "element", + "id": "cashback-title", + "elementType": "text", + "bindings": { + "text": "GET {{cashback}} CASHBACK" + }, + "layout": { + "width": { "value": 100, "unit": "percent" }, + "offset": { "x": 0, "y": 15, "unit": "percent" } + }, + "style": { + "fontSize": 32, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "textAlign": "center" + } + }, + { + "type": "element", + "id": "subtitle", + "elementType": "text", + "bindings": { + "text": "On All Purchases" + }, + "layout": { + "width": { "value": 100, "unit": "percent" }, + "offset": { "x": 0, "y": 30, "unit": "percent" } + }, + "style": { + "fontSize": 18, + "fontWeight": "medium", + "textColor": "#E0E0FF", + "textAlign": "center" + } + }, + { + "type": "element", + "id": "benefit-1", + "elementType": "text", + "bindings": { + "text": "✓ No Annual Fee" + }, + "layout": { + "width": { "value": 90, "unit": "percent" }, + "offset": { "x": 5, "y": 48, "unit": "percent" } + }, + "style": { + "fontSize": 16, + "fontWeight": "normal", + "textColor": "#FFFFFF", + "textAlign": "center" + } + }, + { + "type": "element", + "id": "benefit-2", + "elementType": "text", + "bindings": { + "text": "✓ Lifetime Free" + }, + "layout": { + "width": { "value": 90, "unit": "percent" }, + "offset": { "x": 5, "y": 58, "unit": "percent" } + }, + "style": { + "fontSize": 16, + "fontWeight": "normal", + "textColor": "#FFFFFF", + "textAlign": "center" + } + }, + { + "type": "element", + "id": "apply-button", + "elementType": "button", + "bindings": { + "text": "Apply Now" + }, + "layout": { + "width": { "value": 46, "unit": "percent" }, + "height": { "value": 14, "unit": "percent" }, + "offset": { "x": 6, "y": 75, "unit": "percent" } + }, + "style": { + "backgroundColor": "#FFFFFF", + "textColor": "#667EEA", + "fontSize": 18, + "fontWeight": "bold", + "borderRadius": 28, + "shadowColor": "#00000040", + "shadowRadius": 12, + "shadowOffsetX": 0, + "shadowOffsetY": 6 + }, + "actions": { + "onClick": { + "type": "custom", + "key": "apply_card", + "value": { + "product": "cashback_card" + } + } + } + }, + { + "type": "element", + "id": "learn-button", + "elementType": "button", + "bindings": { + "text": "Learn More" + }, + "layout": { + "width": { "value": 42, "unit": "percent" }, + "height": { "value": 14, "unit": "percent" }, + "offset": { "x": 52, "y": 75, "unit": "percent" } + }, + "style": { + "backgroundColor": "#FFFFFF30", + "textColor": "#FFFFFF", + "fontSize": 18, + "fontWeight": "bold", + "borderRadius": 28, + "borderWidth": 2, + "borderColor": "#FFFFFF" + }, + "actions": { + "onClick": { + "type": "custom", + "key": "learn_more", + "value": {} + } + } + }, + { + "type": "element", + "id": "terms", + "elementType": "text", + "bindings": { + "text": "{{terms}}" + }, + "layout": { + "width": { "value": 100, "unit": "percent" }, + "offset": { "x": 0, "y": 92, "unit": "percent" } + }, + "style": { + "fontSize": 11, + "fontWeight": "normal", + "textColor": "#FFFFFF", + "textAlign": "center", + "opacity": 0.7 + } + } + ] + } +} diff --git a/flutter-sample/assets/banners/banner-07-app-rating.json b/flutter-sample/assets/banners/banner-07-app-rating.json new file mode 100644 index 0000000..6d28428 --- /dev/null +++ b/flutter-sample/assets/banners/banner-07-app-rating.json @@ -0,0 +1,143 @@ +{ + "theme": { + "id": "banner-07", + "defaultStyle": { + "textColor": "#424242", + "fontSize": 14 + } + }, + "variables": { + "rating": "4.8", + "users": "50K+", + "badge": "VERIFIED" + }, + "root": { + "type": "container", + "id": "banner-root", + "containerType": "box", + "layout": { + "width": { "value": 100, "unit": "percent" }, + "aspectRatio": 0.38 + }, + "style": { + "background": { + "type": "linear_gradient", + "angle": 90, + "colors": ["#FFFFFF", "#FFF3E0"] + }, + "borderRadius": 16 + }, + "children": [ + { + "type": "element", + "id": "verified-badge", + "elementType": "text", + "bindings": { + "text": "✓ {{badge}}" + }, + "layout": { + "width": { "value": 28, "unit": "percent" }, + "height": { "value": 22, "unit": "percent" }, + "offset": { "x": 4, "y": 6, "unit": "percent" } + }, + "style": { + "fontSize": 12, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "textAlign": "center", + "backgroundColor": "#00C853DD", + "borderRadius": 18, + "shadowColor": "#00C85360", + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 3 + } + }, + { + "type": "element", + "id": "rating-number", + "elementType": "text", + "bindings": { + "text": "{{rating}}" + }, + "layout": { + "width": { "value": 45, "unit": "percent" }, + "offset": { "x": 5, "y": 20, "unit": "percent" } + }, + "style": { + "fontSize": 64, + "fontWeight": "bold", + "textColor": "#FF9800", + "textAlign": "center" + } + }, + { + "type": "element", + "id": "stars", + "elementType": "text", + "bindings": { + "text": "★★★★★" + }, + "layout": { + "width": { "value": 45, "unit": "percent" }, + "offset": { "x": 5, "y": 62, "unit": "percent" } + }, + "style": { + "fontSize": 24, + "fontWeight": "normal", + "textColor": "#FF9800", + "textAlign": "center" + } + }, + { + "type": "element", + "id": "join-text", + "elementType": "text", + "bindings": { + "text": "Join {{users}} Users" + }, + "layout": { + "width": { "value": 48, "unit": "percent" }, + "offset": { "x": 50, "y": 22, "unit": "percent" } + }, + "style": { + "fontSize": 20, + "fontWeight": "bold", + "textColor": "#424242", + "textAlign": "center" + } + }, + { + "type": "element", + "id": "download-button", + "elementType": "button", + "bindings": { + "text": "Download" + }, + "layout": { + "width": { "value": 42, "unit": "percent" }, + "height": { "value": 32, "unit": "percent" }, + "offset": { "x": 52, "y": 50, "unit": "percent" } + }, + "style": { + "backgroundColor": "#FF9800", + "textColor": "#FFFFFF", + "fontSize": 18, + "fontWeight": "bold", + "borderRadius": 24, + "shadowColor": "#FF980060", + "shadowRadius": 12, + "shadowOffsetX": 0, + "shadowOffsetY": 6 + }, + "actions": { + "onClick": { + "type": "custom", + "key": "download_app", + "value": {} + } + } + } + ] + } +} diff --git a/flutter-sample/assets/banners/banner-08-flash-sale.json b/flutter-sample/assets/banners/banner-08-flash-sale.json new file mode 100644 index 0000000..a818b6f --- /dev/null +++ b/flutter-sample/assets/banners/banner-08-flash-sale.json @@ -0,0 +1,155 @@ +{ + "theme": { + "id": "banner-08", + "defaultStyle": { + "textColor": "#FFFFFF", + "fontSize": 16 + } + }, + "variables": { + "timeLeft": "2 Hours", + "badge": "⏰ ENDS SOON", + "discount": "70% OFF" + }, + "root": { + "type": "container", + "id": "banner-root", + "containerType": "box", + "layout": { + "width": { "value": 100, "unit": "percent" }, + "aspectRatio": 0.32 + }, + "style": { + "background": { + "type": "linear_gradient", + "angle": 135, + "colors": ["#D32F2F", "#F44336"] + }, + "borderRadius": 16 + }, + "animation": { + "type": "fade_in", + "duration": 300, + "easing": "ease_out" + }, + "children": [ + { + "type": "element", + "id": "timer-badge", + "elementType": "text", + "bindings": { + "text": "{{badge}}" + }, + "layout": { + "width": { "value": 36, "unit": "percent" }, + "height": { "value": 28, "unit": "percent" }, + "offset": { "x": 4, "y": 8, "unit": "percent" } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "textAlign": "center", + "backgroundColor": "#00000050", + "borderRadius": 20, + "shadowColor": "#00000060", + "shadowRadius": 8, + "shadowOffsetX": 0, + "shadowOffsetY": 4 + } + }, + { + "type": "element", + "id": "discount-badge", + "elementType": "text", + "bindings": { + "text": "{{discount}}" + }, + "layout": { + "width": { "value": 32, "unit": "percent" }, + "height": { "value": 28, "unit": "percent" }, + "offset": { "x": 64, "y": 8, "unit": "percent" } + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "textAlign": "center", + "backgroundColor": "#FFD600DD", + "borderRadius": 20, + "shadowColor": "#FFD60060", + "shadowRadius": 8, + "shadowOffsetX": 0, + "shadowOffsetY": 4 + } + }, + { + "type": "element", + "id": "flash-title", + "elementType": "text", + "bindings": { + "text": "⚡ FLASH SALE ⚡" + }, + "layout": { + "width": { "value": 100, "unit": "percent" }, + "offset": { "x": 0, "y": 18, "unit": "percent" } + }, + "style": { + "fontSize": 32, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "textAlign": "center" + } + }, + { + "type": "element", + "id": "countdown", + "elementType": "text", + "bindings": { + "text": "Ends in {{timeLeft}}!" + }, + "layout": { + "width": { "value": 100, "unit": "percent" }, + "offset": { "x": 0, "y": 48, "unit": "percent" } + }, + "style": { + "fontSize": 20, + "fontWeight": "medium", + "textColor": "#FFEBEE", + "textAlign": "center" + } + }, + { + "type": "element", + "id": "shop-button", + "elementType": "button", + "bindings": { + "text": "Shop Now →" + }, + "layout": { + "width": { "value": 45, "unit": "percent" }, + "height": { "value": 35, "unit": "percent" }, + "offset": { "x": 27, "y": 68, "unit": "percent" } + }, + "style": { + "backgroundColor": "#FFFFFF", + "textColor": "#D32F2F", + "fontSize": 18, + "fontWeight": "bold", + "borderRadius": 26, + "shadowColor": "#00000050", + "shadowRadius": 12, + "shadowOffsetX": 0, + "shadowOffsetY": 6 + }, + "actions": { + "onClick": { + "type": "custom", + "key": "shop_flash_sale", + "value": {} + } + } + } + ] + } +} diff --git a/flutter-sample/assets/banners/banner-09-premium-subscription.json b/flutter-sample/assets/banners/banner-09-premium-subscription.json new file mode 100644 index 0000000..90ff965 --- /dev/null +++ b/flutter-sample/assets/banners/banner-09-premium-subscription.json @@ -0,0 +1,217 @@ +{ + "theme": { + "id": "banner-09", + "defaultStyle": { + "textColor": "#424242", + "fontSize": 14 + } + }, + "variables": { + "price": "$9.99", + "period": "month", + "badge": "7 DAYS FREE" + }, + "root": { + "type": "container", + "id": "banner-root", + "containerType": "box", + "layout": { + "width": { "value": 100, "unit": "percent" }, + "aspectRatio": 0.65 + }, + "style": { + "background": { + "type": "radial_gradient", + "center_x": 0.5, + "center_y": 0.5, + "radius": 1.0, + "colors": ["#E8EAF6", "#C5CAE9"] + }, + "borderRadius": 16 + }, + "animation": { + "type": "fade_in", + "duration": 600, + "easing": "ease_out" + }, + "children": [ + { + "type": "element", + "id": "trial-badge", + "elementType": "text", + "bindings": { + "text": "{{badge}}" + }, + "layout": { + "width": { "value": 38, "unit": "percent" }, + "height": { "value": 7, "unit": "percent" }, + "offset": { "x": 31, "y": 3, "unit": "percent" } + }, + "style": { + "fontSize": 13, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "textAlign": "center", + "backgroundColor": "#00C853DD", + "borderRadius": 18, + "shadowColor": "#00C85360", + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 3 + } + }, + { + "type": "element", + "id": "premium-title", + "elementType": "text", + "bindings": { + "text": "Go Premium" + }, + "layout": { + "width": { "value": 100, "unit": "percent" }, + "offset": { "x": 0, "y": 12, "unit": "percent" } + }, + "style": { + "fontSize": 36, + "fontWeight": "bold", + "textColor": "#424242", + "textAlign": "center" + } + }, + { + "type": "element", + "id": "feature-1", + "elementType": "text", + "bindings": { + "text": "✓ Ad-Free Experience" + }, + "layout": { + "width": { "value": 90, "unit": "percent" }, + "offset": { "x": 5, "y": 28, "unit": "percent" } + }, + "style": { + "fontSize": 17, + "fontWeight": "normal", + "textColor": "#424242", + "textAlign": "center" + } + }, + { + "type": "element", + "id": "feature-2", + "elementType": "text", + "bindings": { + "text": "✓ Unlimited Downloads" + }, + "layout": { + "width": { "value": 90, "unit": "percent" }, + "offset": { "x": 5, "y": 39, "unit": "percent" } + }, + "style": { + "fontSize": 17, + "fontWeight": "normal", + "textColor": "#424242", + "textAlign": "center" + } + }, + { + "type": "element", + "id": "feature-3", + "elementType": "text", + "bindings": { + "text": "✓ HD Quality" + }, + "layout": { + "width": { "value": 90, "unit": "percent" }, + "offset": { "x": 5, "y": 50, "unit": "percent" } + }, + "style": { + "fontSize": 17, + "fontWeight": "normal", + "textColor": "#424242", + "textAlign": "center" + } + }, + { + "type": "element", + "id": "feature-4", + "elementType": "text", + "bindings": { + "text": "✓ Offline Mode" + }, + "layout": { + "width": { "value": 90, "unit": "percent" }, + "offset": { "x": 5, "y": 61, "unit": "percent" } + }, + "style": { + "fontSize": 17, + "fontWeight": "normal", + "textColor": "#424242", + "textAlign": "center" + } + }, + { + "type": "element", + "id": "trial-button", + "elementType": "button", + "bindings": { + "text": "Start Free Trial" + }, + "layout": { + "width": { "value": 52, "unit": "percent" }, + "height": { "value": 15, "unit": "percent" }, + "offset": { "x": 6, "y": 78, "unit": "percent" } + }, + "style": { + "backgroundColor": "#5C6BC0", + "textColor": "#FFFFFF", + "fontSize": 18, + "fontWeight": "bold", + "borderRadius": 28, + "shadowColor": "#5C6BC060", + "shadowRadius": 12, + "shadowOffsetX": 0, + "shadowOffsetY": 6 + }, + "actions": { + "onClick": { + "type": "custom", + "key": "start_trial", + "value": { + "plan": "premium" + } + } + } + }, + { + "type": "element", + "id": "upgrade-button", + "elementType": "button", + "bindings": { + "text": "Buy Now" + }, + "layout": { + "width": { "value": 32, "unit": "percent" }, + "height": { "value": 15, "unit": "percent" }, + "offset": { "x": 62, "y": 78, "unit": "percent" } + }, + "style": { + "backgroundColor": "#FFFFFF30", + "textColor": "#424242", + "fontSize": 18, + "fontWeight": "bold", + "borderRadius": 28, + "borderWidth": 2, + "borderColor": "#5C6BC0" + }, + "actions": { + "onClick": { + "type": "custom", + "key": "buy_premium", + "value": {} + } + } + } + ] + } +} diff --git a/flutter-sample/assets/banners/banner-10-welcome-onboarding.json b/flutter-sample/assets/banners/banner-10-welcome-onboarding.json new file mode 100644 index 0000000..0e0a653 --- /dev/null +++ b/flutter-sample/assets/banners/banner-10-welcome-onboarding.json @@ -0,0 +1,185 @@ +{ + "theme": { + "id": "banner-10", + "defaultStyle": { + "textColor": "#FFFFFF", + "fontSize": 16 + } + }, + "variables": { + "appName": "CleverTap" + }, + "root": { + "type": "container", + "id": "banner-root", + "containerType": "box", + "layout": { + "width": { "value": 100, "unit": "percent" }, + "aspectRatio": 0.6 + }, + "style": { + "backgroundColor": "#000000", + "borderRadius": 20 + }, + "animation": { + "type": "slide_in_bottom", + "duration": 800, + "easing": "spring" + }, + "children": [ + { + "type": "element", + "id": "background-image", + "elementType": "image", + "bindings": { + "url": "https://cdn.dummyjson.com/products/images/laptops/Apple%20MacBook%20Pro%2014%20Inch%20Space%20Grey/1.png" + }, + "layout": { + "width": { "value": 100, "unit": "percent" }, + "height": { "value": 100, "unit": "percent" }, + "offset": { "x": 0, "y": 0, "unit": "percent" } + }, + "style": { + "borderRadius": 20 + } + }, + { + "type": "element", + "id": "gradient-overlay", + "elementType": "text", + "bindings": { + "text": "" + }, + "layout": { + "width": { "value": 100, "unit": "percent" }, + "height": { "value": 100, "unit": "percent" }, + "offset": { "x": 0, "y": 0, "unit": "percent" } + }, + "style": { + "backgroundColor": "#00000060", + "borderRadius": 20 + } + }, + { + "type": "element", + "id": "app-icon", + "elementType": "image", + "bindings": { + "url": "https://cdn.dummyjson.com/products/images/laptops/Apple%20MacBook%20Pro%2014%20Inch%20Space%20Grey/1.png" + }, + "layout": { + "width": { "value": 28, "unit": "percent" }, + "height": { "value": 28, "unit": "percent" }, + "offset": { "x": 36, "y": 8, "unit": "percent" } + }, + "style": { + "borderRadius": 24, + "shadowColor": "#00000050", + "shadowRadius": 16, + "shadowOffsetX": 0, + "shadowOffsetY": 8 + } + }, + { + "type": "element", + "id": "welcome-title", + "elementType": "text", + "bindings": { + "text": "Welcome to {{appName}}!" + }, + "layout": { + "width": { "value": 100, "unit": "percent" }, + "offset": { "x": 0, "y": 42, "unit": "percent" } + }, + "style": { + "fontSize": 32, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "textAlign": "center", + "shadowColor": "#00000080", + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 3 + } + }, + { + "type": "element", + "id": "subtitle", + "elementType": "text", + "bindings": { + "text": "Let's get you started with your journey" + }, + "layout": { + "width": { "value": 90, "unit": "percent" }, + "offset": { "x": 5, "y": 57, "unit": "percent" } + }, + "style": { + "fontSize": 17, + "fontWeight": "normal", + "textColor": "#FFFFFFDD", + "textAlign": "center" + } + }, + { + "type": "element", + "id": "tour-button", + "elementType": "button", + "bindings": { + "text": "Take Tour" + }, + "layout": { + "width": { "value": 42, "unit": "percent" }, + "height": { "value": 16, "unit": "percent" }, + "offset": { "x": 7, "y": 76, "unit": "percent" } + }, + "style": { + "backgroundColor": "#FFFFFF", + "textColor": "#667EEA", + "fontSize": 18, + "fontWeight": "bold", + "borderRadius": 28, + "shadowColor": "#00000040", + "shadowRadius": 12, + "shadowOffsetX": 0, + "shadowOffsetY": 6 + }, + "actions": { + "onClick": { + "type": "custom", + "key": "start_tour", + "value": {} + } + } + }, + { + "type": "element", + "id": "skip-button", + "elementType": "button", + "bindings": { + "text": "Skip" + }, + "layout": { + "width": { "value": 38, "unit": "percent" }, + "height": { "value": 16, "unit": "percent" }, + "offset": { "x": 53, "y": 76, "unit": "percent" } + }, + "style": { + "backgroundColor": "#FFFFFF20", + "textColor": "#FFFFFF", + "fontSize": 18, + "fontWeight": "bold", + "borderRadius": 28, + "borderWidth": 2, + "borderColor": "#FFFFFF" + }, + "actions": { + "onClick": { + "type": "custom", + "key": "dismiss", + "value": {} + } + } + } + ] + } +} diff --git a/flutter-sample/assets/configs/animation_container_and_children.json b/flutter-sample/assets/configs/animation_container_and_children.json new file mode 100644 index 0000000..0b9b8bd --- /dev/null +++ b/flutter-sample/assets/configs/animation_container_and_children.json @@ -0,0 +1,333 @@ +{ + "theme": { + "id": "default", + "defaultStyle": { + "textColor": "#000000", + "fontSize": 14 + }, + "colors": {} + }, + "styleClasses": [], + "variables": {}, + "root": { + "type": "container", + "id": "combined_container", + "containerType": "vertical", + "layout": { + "padding": { + "all": 24 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 24 + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 16, + "shadowRadius": 8, + "shadowColor": "#00000040" + }, + "animation": { + "type": "fade_in", + "duration": 400, + "delay": 0, + "easing": "ease_out" + }, + "children": [ + { + "type": "element", + "id": "combined_title", + "elementType": "text", + "bindings": { + "text": "Container + Children Animations" + }, + "style": { + "textColor": "#1A237E", + "fontSize": 24, + "fontWeight": "bold", + "textAlign": "center" + } + }, + { + "type": "element", + "id": "combined_subtitle", + "elementType": "text", + "bindings": { + "text": "Container fades in first, then children appear sequentially" + }, + "style": { + "textColor": "#757575", + "fontSize": 14, + "textAlign": "center" + } + }, + { + "type": "element", + "id": "hero_image", + "elementType": "image", + "bindings": { + "url": "https://dummyjson.com/image/400x200/008080/000000?text=Hello+LP" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "dp" + } + }, + "style": { + "borderRadius": 12, + "backgroundColor" : "#77007700" + }, + "animation": { + "type": "scale_in", + "duration": 500, + "delay": 400, + "easing": "ease_out_back" + } + }, + { + "type": "element", + "id": "hero_title", + "elementType": "text", + "bindings": { + "text": "Premium Experience" + }, + "style": { + "textColor": "#1976D2", + "fontSize": 28, + "fontWeight": "bold", + "textAlign": "center" + }, + "animation": { + "type": "fade_slide_in", + "duration": 400, + "delay": 600, + "easing": "ease_out" + } + }, + { + "type": "element", + "id": "hero_description", + "elementType": "text", + "bindings": { + "text": "Discover amazing features with smooth animations that bring your content to life." + }, + "style": { + "textColor": "#424242", + "fontSize": 16, + "lineHeight": 24, + "textAlign": "center" + }, + "animation": { + "type": "fade_slide_in", + "duration": 400, + "delay": 800, + "easing": "ease_out" + } + }, + { + "type": "container", + "id": "features_container", + "containerType": "horizontal", + "layout": { + "arrangement": { + "strategy": "space_evenly" + } + }, + "children": [ + { + "type": "container", + "id": "feature_1", + "containerType": "vertical", + "layout": { + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#E3F2FD", + "borderRadius": 12 + }, + "animation": { + "type": "fade_scale_in", + "duration": 400, + "delay": 1000, + "easing": "ease_out" + }, + "children": [ + { + "type": "element", + "id": "feature_1_icon", + "elementType": "text", + "bindings": { + "text": "✨" + }, + "style": { + "fontSize": 40, + "textAlign": "center" + }, + "layout": { + "padding": { + "bottom": 8 + } + } + }, + { + "type": "element", + "id": "feature_1_text", + "elementType": "text", + "bindings": { + "text": "Beautiful" + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#1565C0", + "textAlign": "center" + } + } + ] + }, + { + "type": "container", + "id": "feature_2", + "containerType": "vertical", + "layout": { + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#F3E5F5", + "borderRadius": 12 + }, + "animation": { + "type": "fade_scale_in", + "duration": 400, + "delay": 1100, + "easing": "ease_out" + }, + "children": [ + { + "type": "element", + "id": "feature_2_icon", + "elementType": "text", + "bindings": { + "text": "⚡" + }, + "style": { + "fontSize": 40, + "textAlign": "center" + }, + "layout": { + "padding": { + "bottom": 8 + } + } + }, + { + "type": "element", + "id": "feature_2_text", + "elementType": "text", + "bindings": { + "text": "Fast" + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#7B1FA2", + "textAlign": "center" + } + } + ] + }, + { + "type": "container", + "id": "feature_3", + "containerType": "vertical", + "layout": { + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#E8F5E9", + "borderRadius": 12 + }, + "animation": { + "type": "fade_scale_in", + "duration": 400, + "delay": 1200, + "easing": "ease_out" + }, + "children": [ + { + "type": "element", + "id": "feature_3_icon", + "elementType": "text", + "bindings": { + "text": "🎯" + }, + "style": { + "fontSize": 40, + "textAlign": "center" + }, + "layout": { + "padding": { + "bottom": 8 + } + } + }, + { + "type": "element", + "id": "feature_3_text", + "elementType": "text", + "bindings": { + "text": "Precise" + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#2E7D32", + "textAlign": "center" + } + } + ] + } + ] + }, + { + "type": "element", + "id": "cta_button", + "elementType": "button", + "bindings": { + "text": "Get Started Now" + }, + "style": { + "backgroundColor": "#1976D2", + "textColor": "#FFFFFF", + "fontSize": 16, + "fontWeight": "bold", + "borderRadius": 8 + }, + "layout": { + "padding": { + "vertical": 14, + "horizontal": 48 + } + }, + "animation": { + "type": "fade_scale_in", + "duration": 500, + "delay": 1400, + "easing": "spring" + } + } + ] + } +} diff --git a/flutter-sample/assets/configs/animation_container_fade.json b/flutter-sample/assets/configs/animation_container_fade.json new file mode 100644 index 0000000..114c9a8 --- /dev/null +++ b/flutter-sample/assets/configs/animation_container_fade.json @@ -0,0 +1,119 @@ +{ + "theme": { + "id": "default", + "defaultStyle": { + "textColor": "#000000", + "fontSize": 14 + }, + "colors": {} + }, + "styleClasses": [], + "variables": {}, + "root": { + "type": "container", + "id": "fade_container", + "containerType": "vertical", + "layout": { + "padding": { + "all": 24 + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 16, + "shadowRadius": 8, + "shadowColor": "#00000040" + }, + "animation": { + "type": "fade_in", + "duration": 500, + "delay": 0, + "easing": "ease_out" + }, + "children": [ + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "Container with Fade Animation" + }, + "style": { + "textColor": "#1A237E", + "fontSize": 24, + "fontWeight": "bold", + "textAlign": "center" + }, + "layout": { + "padding": { + "bottom": 16 + } + } + }, + { + "type": "element", + "id": "description", + "elementType": "text", + "bindings": { + "text": "This entire container fades in smoothly over 500ms. All children appear together with the container." + }, + "style": { + "textColor": "#424242", + "fontSize": 16, + "lineHeight": 24, + "textAlign": "center" + }, + "layout": { + "padding": { + "bottom": 24 + } + } + }, + { + "type": "element", + "id": "demo_image", + "elementType": "image", + "bindings": { + "url": "https://dummyjson.com/image/400x200/008080/0000ff?text=Hello+Lalit" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 50, + "unit": "dp" + }, + "padding": { + "bottom": 16 + } + }, + "style": { + "borderRadius": 12 + } + }, + { + "type": "element", + "id": "cta_button", + "elementType": "button", + "bindings": { + "text": "Get Started" + }, + "style": { + "backgroundColor": "#1976D2", + "textColor": "#FFFFFF", + "fontSize": 16, + "fontWeight": "bold", + "borderRadius": 8 + }, + "layout": { + "padding": { + "vertical": 12, + "horizontal": 32 + } + } + } + ] + } +} diff --git a/flutter-sample/assets/configs/animation_staggered_children.json b/flutter-sample/assets/configs/animation_staggered_children.json new file mode 100644 index 0000000..c11b1b9 --- /dev/null +++ b/flutter-sample/assets/configs/animation_staggered_children.json @@ -0,0 +1,293 @@ +{ + "theme": { + "id": "default", + "defaultStyle": { + "textColor": "#000000", + "fontSize": 14 + }, + "colors": {} + }, + "styleClasses": [], + "variables": {}, + "root": { + "type": "container", + "id": "stagger_container", + "containerType": "vertical", + "layout": { + "padding": { + "all": 24 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 16 + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 16, + "shadowRadius": 8, + "shadowColor": "#00000040" + }, + "children": [ + { + "type": "element", + "id": "stagger_title", + "elementType": "text", + "bindings": { + "text": "Staggered Children Animation" + }, + "style": { + "textColor": "#1A237E", + "fontSize": 24, + "fontWeight": "bold", + "textAlign": "center" + }, + "layout": { + "padding": { + "bottom": 8 + } + }, + "animation": { + "type": "slide_in_left", + "duration": 300, + "delay": 0, + "easing": "ease_out" + } + }, + { + "type": "element", + "id": "stagger_subtitle", + "elementType": "text", + "bindings": { + "text": "Each child slides in with 100ms delay" + }, + "style": { + "textColor": "#757575", + "fontSize": 14, + "textAlign": "center" + }, + "layout": { + "padding": { + "bottom": 16 + } + }, + "animation": { + "type": "slide_in_left", + "duration": 300, + "delay": 100, + "easing": "ease_out" + } + }, + { + "type": "container", + "id": "card_1", + "containerType": "horizontal", + "layout": { + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#E3F2FD", + "borderRadius": 12 + }, + "animation": { + "type": "slide_in_left", + "duration": 300, + "delay": 200, + "easing": "ease_out" + }, + "children": [ + { + "type": "element", + "id": "icon_1", + "elementType": "text", + "bindings": { + "text": "🎨" + }, + "style": { + "fontSize": 32 + }, + "layout": { + "padding": { + "right": 16 + } + } + }, + { + "type": "container", + "id": "card_1_content", + "containerType": "vertical", + "children": [ + { + "type": "element", + "id": "card_1_title", + "elementType": "text", + "bindings": { + "text": "Design First" + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "textColor": "#1565C0" + } + }, + { + "type": "element", + "id": "card_1_desc", + "elementType": "text", + "bindings": { + "text": "Beautiful UI components" + }, + "style": { + "fontSize": 14, + "textColor": "#424242" + } + } + ] + } + ] + }, + { + "type": "container", + "id": "card_2", + "containerType": "horizontal", + "layout": { + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#F3E5F5", + "borderRadius": 12 + }, + "animation": { + "type": "slide_in_left", + "duration": 300, + "delay": 300, + "easing": "ease_out" + }, + "children": [ + { + "type": "element", + "id": "icon_2", + "elementType": "text", + "bindings": { + "text": "⚡" + }, + "style": { + "fontSize": 32 + }, + "layout": { + "padding": { + "right": 16 + } + } + }, + { + "type": "container", + "id": "card_2_content", + "containerType": "vertical", + "children": [ + { + "type": "element", + "id": "card_2_title", + "elementType": "text", + "bindings": { + "text": "Fast Performance" + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "textColor": "#7B1FA2" + } + }, + { + "type": "element", + "id": "card_2_desc", + "elementType": "text", + "bindings": { + "text": "Optimized rendering" + }, + "style": { + "fontSize": 14, + "textColor": "#424242" + } + } + ] + } + ] + }, + { + "type": "container", + "id": "card_3", + "containerType": "horizontal", + "layout": { + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#E8F5E9", + "borderRadius": 12 + }, + "animation": { + "type": "slide_in_left", + "duration": 300, + "delay": 400, + "easing": "ease_out" + }, + "children": [ + { + "type": "element", + "id": "icon_3", + "elementType": "text", + "bindings": { + "text": "🚀" + }, + "style": { + "fontSize": 32 + }, + "layout": { + "padding": { + "right": 16 + } + } + }, + { + "type": "container", + "id": "card_3_content", + "containerType": "vertical", + "children": [ + { + "type": "element", + "id": "card_3_title", + "elementType": "text", + "bindings": { + "text": "Easy to Use" + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "textColor": "#2E7D32" + } + }, + { + "type": "element", + "id": "card_3_desc", + "elementType": "text", + "bindings": { + "text": "Simple JSON configuration" + }, + "style": { + "fontSize": 14, + "textColor": "#424242" + } + } + ] + } + ] + } + ] + } +} diff --git a/flutter-sample/assets/configs/arrangement_demo.json b/flutter-sample/assets/configs/arrangement_demo.json new file mode 100644 index 0000000..8220e21 --- /dev/null +++ b/flutter-sample/assets/configs/arrangement_demo.json @@ -0,0 +1,198 @@ +{ + "theme": { + "id": "arrangement_demo", + "defaultStyle": { + "textColor": "#000000", + "fontSize": 16 + } + }, + "styleClasses": [], + "variables": {}, + "root": { + "type": "container", + "id": "demo_container", + "containerType": "vertical", + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "match_parent" + }, + "height": { + "value": 400, + "unit": "dp" + }, + "arrangement": { + "spacing": 16, + "strategy": "spaced" + } + }, + "style": { + "backgroundColor": "#F5F5F5", + "borderWidth": 1, + "borderColor": "#000000", + "borderRadius": 4 + }, + "children": [ + { + "type": "container", + "id": "box_1", + "containerType": "box", + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "match_parent" + }, + "height": { + "value": 48, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#E3F2FD", + "borderRadius": 8, + "borderWidth": 2, + "borderColor": "#1976D2" + }, + "children": [ + { + "type": "element", + "id": "text_1", + "elementType": "text", + "bindings": { + "text": "Item 1" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 8 + } + }, + "style": { + "fontSize": 16, + "fontWeight": "bold", + "textColor": "#1976D2", + "backgroundColor": "#BBDEFB" + } + } + ] + }, + { + "type": "container", + "id": "box_2", + "containerType": "box", + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "match_parent" + }, + "height": { + "value": 48, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#E8F5E9", + "borderRadius": 8, + "borderWidth": 2, + "borderColor": "#388E3C" + }, + "children": [ + { + "type": "element", + "id": "text_2", + "elementType": "text", + "bindings": { + "text": "Item 2" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 8 + } + }, + "style": { + "fontSize": 16, + "fontWeight": "bold", + "textColor": "#388E3C", + "backgroundColor": "#C8E6C9" + } + } + ] + }, + { + "type": "container", + "id": "box_3", + "containerType": "box", + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "match_parent" + }, + "height": { + "value": 48, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FFF3E0", + "borderRadius": 8, + "borderWidth": 2, + "borderColor": "#F57C00" + }, + "children": [ + { + "type": "element", + "id": "text_3", + "elementType": "text", + "bindings": { + "text": "Item 3" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 8 + } + }, + "style": { + "fontSize": 16, + "fontWeight": "bold", + "textColor": "#F57C00", + "backgroundColor": "#FFE0B2" + } + } + ] + } + ] + } +} diff --git a/flutter-sample/assets/configs/bridge_mock_notification.json b/flutter-sample/assets/configs/bridge_mock_notification.json new file mode 100644 index 0000000..a500160 --- /dev/null +++ b/flutter-sample/assets/configs/bridge_mock_notification.json @@ -0,0 +1,104 @@ +{ + "wzrk_id": "demo_unit_notification", + "type": "native_display", + "native_display_config": { + "theme": { + "id": "notification", + "defaultStyle": { + "textColor": "#1F2937", + "fontSize": 14, + "lineHeight": 20 + } + }, + "root": { + "type": "container", + "id": "notification-card", + "containerType": "horizontal", + "layout": { + "width": { "value": 100, "unit": "percent" }, + "height": { "value": -2, "unit": "dp" }, + "padding": { "all": 16 }, + "arrangement": { "spacing": 12, "strategy": "spaced" } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 12, + "shadowRadius": 8, + "shadowColor": "#000000", + "shadowOpacity": 0.08, + "shadowOffsetY": 2, + "borderWidth": 1, + "borderColor": "#E5E7EB" + }, + "children": [ + { + "type": "element", + "id": "avatar", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-750.jpg" + }, + "layout": { + "width": { "value": 48, "unit": "dp" }, + "height": { "value": 48, "unit": "dp" } + }, + "style": { "borderRadius": 24 } + }, + { + "type": "container", + "id": "content", + "containerType": "vertical", + "layout": { + "width": { "value": -1, "unit": "dp" }, + "arrangement": { "spacing": 4, "strategy": "spaced" } + }, + "children": [ + { + "type": "element", + "id": "sender", + "elementType": "text", + "bindings": { "text": "Sarah Johnson" }, + "layout": { "width": { "value": 100, "unit": "percent" } }, + "style": { + "fontSize": 16, + "fontWeight": "bold", + "textColor": "#111827", + "lineHeight": 22 + } + }, + { + "type": "element", + "id": "message", + "elementType": "text", + "bindings": { "text": "Hey! Just wanted to check in about our meeting tomorrow." }, + "layout": { "width": { "value": 100, "unit": "percent" } }, + "style": { + "fontSize": 14, + "textColor": "#6B7280", + "lineHeight": 20 + } + }, + { + "type": "element", + "id": "time", + "elementType": "text", + "bindings": { "text": "2 minutes ago" }, + "layout": { "width": { "value": 100, "unit": "percent" } }, + "style": { + "fontSize": 12, + "textColor": "#9CA3AF", + "lineHeight": 17 + } + } + ] + } + ] + }, + "styleClasses": [], + "variables": {} + }, + "custom_kv": { + "campaign": "re_engagement", + "sender_id": "user_42" + } +} diff --git a/flutter-sample/assets/configs/bridge_mock_product.json b/flutter-sample/assets/configs/bridge_mock_product.json new file mode 100644 index 0000000..7f7bce9 --- /dev/null +++ b/flutter-sample/assets/configs/bridge_mock_product.json @@ -0,0 +1,103 @@ +{ + "wzrk_id": "demo_unit_product", + "type": "native_display", + "native_display_config": { + "theme": { + "id": "product-card", + "defaultStyle": { + "textColor": "#1F2937", + "fontSize": 14, + "lineHeight": 20 + } + }, + "root": { + "type": "container", + "id": "product-card-container", + "containerType": "vertical", + "layout": { + "width": { "value": 100, "unit": "percent" }, + "height": { "value": -2, "unit": "dp" }, + "padding": { "all": 16 } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 16, + "shadowRadius": 12, + "shadowColor": "#000000", + "shadowOpacity": 0.1, + "shadowOffsetY": 4 + }, + "children": [ + { + "type": "element", + "id": "product-image", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-83.jpg" + }, + "layout": { + "width": { "value": 100, "unit": "percent" }, + "height": { "value": 200, "unit": "dp" } + }, + "style": { "borderRadius": 12 } + }, + { + "type": "element", + "id": "product-name", + "elementType": "text", + "bindings": { "text": "Premium Wireless Headphones" }, + "layout": { + "width": { "value": 100, "unit": "percent" }, + "height": { "value": -2, "unit": "dp" } + }, + "style": { + "fontSize": 20, + "fontWeight": "bold", + "textColor": "#111827", + "lineHeight": 28 + } + }, + { + "type": "element", + "id": "product-price", + "elementType": "text", + "bindings": { "text": "$299.99" }, + "layout": { + "width": { "value": 100, "unit": "percent" }, + "height": { "value": -2, "unit": "dp" } + }, + "style": { + "fontSize": 24, + "fontWeight": "bold", + "textColor": "#10B981", + "lineHeight": 34 + } + }, + { + "type": "element", + "id": "buy-button", + "elementType": "button", + "bindings": { "text": "Add to Cart" }, + "layout": { + "width": { "value": 100, "unit": "percent" }, + "height": { "value": 48, "unit": "dp" } + }, + "style": { + "backgroundColor": "#3B82F6", + "borderRadius": 12, + "textColor": "#FFFFFF", + "fontSize": 16, + "fontWeight": "bold", + "lineHeight": 22 + } + } + ] + }, + "styleClasses": [], + "variables": {} + }, + "custom_kv": { + "campaign": "summer_sale", + "category": "electronics" + } +} diff --git a/flutter-sample/assets/configs/gallery_three_modes.json b/flutter-sample/assets/configs/gallery_three_modes.json new file mode 100644 index 0000000..759e3dc --- /dev/null +++ b/flutter-sample/assets/configs/gallery_three_modes.json @@ -0,0 +1,362 @@ +{ + "theme": { + "id": "default", + "defaultStyle": {"textColor": "#000000", "fontSize": 14}, + "colors": {} + }, + "styleClasses": [], + "variables": {}, + "root": { + "type": "container", + "id": "gallery_examples_root", + "containerType": "vertical", + "layout": {"spacing": 24, "padding": {"all": 16}, "height": {"value": 0, "unit": "dp", "special": "match_parent"}}, + "children": [ + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": {"text": "Gallery Examples"}, + "style": {"textColor": "#000000", "fontSize": 24, "fontWeight": "bold"} + }, + + { + "type": "element", + "id": "mode1_label", + "elementType": "text", + "bindings": {"text": "Mode 1: Snapping with Peek (20%)"}, + "style": {"textColor": "#666666", "fontSize": 16, "fontWeight": "bold"} + }, + { + "type": "container", + "id": "snapping_gallery", + "containerType": "gallery", + "layout": {"height": {"value": 200, "unit": "dp"}}, + "galleryConfig": { + "mode": "snapping", + "orientation": "horizontal", + "peekPercentage": 20, + "snapBehavior": "center", + "spacing": 12, + "showIndicators": true, + "indicatorStyle": { + "activeColor": "#2196F3", + "inactiveColor": "#BDBDBD" + }, + "autoScrollInterval": 3000 + }, + "children": [ + { + "type": "container", + "id": "snap_item_1", + "containerType": "box", + "layout": {"height": {"value": 200, "unit": "dp"}}, + "style": { + "background": { + "type": "linear_gradient", + "angle": 135, + "colors": ["#667eea", "#764ba2"] + }, + "borderRadius": 16 + }, + "children": [ + { + "type": "element", + "id": "snap_text_1", + "elementType": "text", + "bindings": {"text": "Image 1"}, + "style": {"textColor": "#FFFFFF", "fontSize": 32, "fontWeight": "bold"} + } + ] + }, + { + "type": "container", + "id": "snap_item_2", + "containerType": "box", + "layout": {"height": {"value": 200, "unit": "dp"}}, + "style": { + "background": { + "type": "linear_gradient", + "angle": 135, + "colors": ["#f093fb", "#f5576c"] + }, + "borderRadius": 16 + }, + "children": [ + { + "type": "element", + "id": "snap_text_2", + "elementType": "text", + "bindings": {"text": "Image 2"}, + "style": {"textColor": "#FFFFFF", "fontSize": 32, "fontWeight": "bold"} + } + ] + }, + { + "type": "container", + "id": "snap_item_3", + "containerType": "box", + "layout": {"height": {"value": 200, "unit": "dp"}}, + "style": { + "background": { + "type": "linear_gradient", + "angle": 135, + "colors": ["#4facfe", "#00f2fe"] + }, + "borderRadius": 16 + }, + "children": [ + { + "type": "element", + "id": "snap_text_3", + "elementType": "text", + "bindings": {"text": "Image 3"}, + "style": {"textColor": "#FFFFFF", "fontSize": 32, "fontWeight": "bold"} + } + ] + } + ] + }, + + { + "type": "element", + "id": "mode2_label", + "elementType": "text", + "bindings": {"text": "Mode 2: Free Flow - Independent Sizing"}, + "style": {"textColor": "#666666", "fontSize": 16, "fontWeight": "bold"}, + "layout": {"padding": {"vertical": 16}} + }, + { + "type": "container", + "id": "freeflow_gallery", + "containerType": "gallery", + "galleryConfig": { + "mode": "free_flow", + "orientation": "horizontal", + "spacing": 8 + }, + "children": [ + { + "type": "container", + "id": "tag_1", + "containerType": "box", + "layout": {"padding": {"vertical": 8, "horizontal": 16}}, + "style": {"backgroundColor": "#E3F2FD", "borderRadius": 16}, + "children": [ + { + "type": "element", + "id": "tag_text_1", + "elementType": "text", + "bindings": {"text": "Design"}, + "style": {"textColor": "#1976D2", "fontSize": 14, "fontWeight": "bold"} + } + ] + }, + { + "type": "container", + "id": "tag_2", + "containerType": "box", + "layout": {"padding": {"vertical": 8, "horizontal": 16}}, + "style": {"backgroundColor": "#F3E5F5", "borderRadius": 16}, + "children": [ + { + "type": "element", + "id": "tag_text_2", + "elementType": "text", + "bindings": {"text": "Development"}, + "style": {"textColor": "#7B1FA2", "fontSize": 14, "fontWeight": "bold"} + } + ] + }, + { + "type": "container", + "id": "tag_3", + "containerType": "box", + "layout": {"padding": {"vertical": 8, "horizontal": 16}}, + "style": {"backgroundColor": "#FFF3E0", "borderRadius": 16}, + "children": [ + { + "type": "element", + "id": "tag_text_3", + "elementType": "text", + "bindings": {"text": "UI"}, + "style": {"textColor": "#F57C00", "fontSize": 14, "fontWeight": "bold"} + } + ] + }, + { + "type": "container", + "id": "tag_4", + "containerType": "box", + "layout": {"padding": {"vertical": 8, "horizontal": 16}}, + "style": {"backgroundColor": "#E8F5E9", "borderRadius": 16}, + "children": [ + { + "type": "element", + "id": "tag_text_4", + "elementType": "text", + "bindings": {"text": "Mobile"}, + "style": {"textColor": "#388E3C", "fontSize": 14, "fontWeight": "bold"} + } + ] + } + ] + }, + + { + "type": "element", + "id": "mode3_label", + "elementType": "text", + "bindings": {"text": "Mode 3: Free Flow Grid (2.5 items per view)"}, + "style": {"textColor": "#666666", "fontSize": 16, "fontWeight": "bold"}, + "layout": {"margin": {"top": 24}} + }, + { + "type": "container", + "id": "grid_gallery", + "containerType": "gallery", + "layout": {"height": {"value": 150, "unit": "dp"}}, + "galleryConfig": { + "mode": "free_flow_grid", + "orientation": "horizontal", + "itemsPerView": 2.5, + "spacing": 12 + }, + "children": [ + { + "type": "container", + "id": "product_1", + "containerType": "vertical", + "layout": {"padding": {"all": 12}, "spacing": 8}, + "style": {"backgroundColor": "#FFFFFF", "borderRadius": 12, "shadowRadius": 4, "shadowColor": "#00000015"}, + "children": [ + { + "type": "container", + "id": "product_image_1", + "containerType": "box", + "layout": {"height": {"value": 80, "unit": "dp"}}, + "style": {"backgroundColor": "#E0E0E0", "borderRadius": 8}, + "children": [ + { + "type": "element", + "id": "product_emoji_1", + "elementType": "text", + "bindings": {"text": "📱"}, + "style": {"textColor": "#666666", "fontSize": 32, "textAlign": "center"}, + "layout": {"padding": {"top": 24}} + } + ] + }, + { + "type": "element", + "id": "product_name_1", + "elementType": "text", + "bindings": {"text": "Product 1"}, + "style": {"textColor": "#000000", "fontSize": 12, "fontWeight": "bold"} + } + ] + }, + { + "type": "container", + "id": "product_2", + "containerType": "vertical", + "layout": {"padding": {"all": 12}, "spacing": 8}, + "style": {"backgroundColor": "#FFFFFF", "borderRadius": 12, "shadowRadius": 4, "shadowColor": "#00000015"}, + "children": [ + { + "type": "container", + "id": "product_image_2", + "containerType": "box", + "layout": {"height": {"value": 80, "unit": "dp"}}, + "style": {"backgroundColor": "#E0E0E0", "borderRadius": 8}, + "children": [ + { + "type": "element", + "id": "product_emoji_2", + "elementType": "text", + "bindings": {"text": "💻"}, + "style": {"textColor": "#666666", "fontSize": 32, "textAlign": "center"}, + "layout": {"padding": {"top": 24}} + } + ] + }, + { + "type": "element", + "id": "product_name_2", + "elementType": "text", + "bindings": {"text": "Product 2"}, + "style": {"textColor": "#000000", "fontSize": 12, "fontWeight": "bold"} + } + ] + }, + { + "type": "container", + "id": "product_3", + "containerType": "vertical", + "layout": {"padding": {"all": 12}, "spacing": 8}, + "style": {"backgroundColor": "#FFFFFF", "borderRadius": 12, "shadowRadius": 4, "shadowColor": "#00000015"}, + "children": [ + { + "type": "container", + "id": "product_image_3", + "containerType": "box", + "layout": {"height": {"value": 80, "unit": "dp"}}, + "style": {"backgroundColor": "#E0E0E0", "borderRadius": 8}, + "children": [ + { + "type": "element", + "id": "product_emoji_3", + "elementType": "text", + "bindings": {"text": "⌚"}, + "style": {"textColor": "#666666", "fontSize": 32, "textAlign": "center"}, + "layout": {"padding": {"top": 24}} + } + ] + }, + { + "type": "element", + "id": "product_name_3", + "elementType": "text", + "bindings": {"text": "Product 3"}, + "style": {"textColor": "#000000", "fontSize": 12, "fontWeight": "bold"} + } + ] + }, + { + "type": "container", + "id": "product_4", + "containerType": "vertical", + "layout": {"padding": {"all": 12}, "spacing": 8}, + "style": {"backgroundColor": "#FFFFFF", "borderRadius": 12, "shadowRadius": 4, "shadowColor": "#00000015"}, + "children": [ + { + "type": "container", + "id": "product_image_4", + "containerType": "box", + "layout": {"height": {"value": 80, "unit": "dp"}}, + "style": {"backgroundColor": "#E0E0E0", "borderRadius": 8}, + "children": [ + { + "type": "element", + "id": "product_emoji_4", + "elementType": "text", + "bindings": {"text": "🎧"}, + "style": {"textColor": "#666666", "fontSize": 32, "textAlign": "center"}, + "layout": {"padding": {"top": 24}} + } + ] + }, + { + "type": "element", + "id": "product_name_4", + "elementType": "text", + "bindings": {"text": "Product 4"}, + "style": {"textColor": "#000000", "fontSize": 12, "fontWeight": "bold"} + } + ] + } + ] + } + ] + } +} diff --git a/flutter-sample/assets/configs/home_screen.json b/flutter-sample/assets/configs/home_screen.json new file mode 100644 index 0000000..3c9838a --- /dev/null +++ b/flutter-sample/assets/configs/home_screen.json @@ -0,0 +1,1759 @@ +{ + "theme": { + "id": "modern_home", + "defaultStyle": { + "textColor": "#1A1A2E", + "fontSize": 14 + }, + "colors": { + "primary": "#6C63FF", + "primaryLight": "#8B7CF7", + "secondary": "#FF6584", + "background": "#F8F9FE", + "surface": "#FFFFFF", + "textPrimary": "#1A1A2E", + "textSecondary": "#6B7280", + "textMuted": "#9CA3AF", + "accent": "#FFD700", + "success": "#10B981", + "error": "#EF4444", + "warning": "#F97316", + "rating": "#F59E0B", + "divider": "#E5E7EB" + } + }, + "styleClasses": [ + { + "name": "sectionTitle", + "style": { + "textColor": "#1A1A2E", + "fontSize": 20, + "fontWeight": "bold" + } + }, + { + "name": "cardContainer", + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 16, + "shadowRadius": 6, + "shadowColor": "#00000015" + } + }, + { + "name": "primaryButton", + "style": { + "background": { + "type": "linear_gradient", + "angle": 90, + "colors": ["#6C63FF", "#8B7CF7"] + }, + "borderRadius": 25 + } + }, + { + "name": "primaryButtonText", + "style": { + "textColor": "#FFFFFF", + "fontSize": 14, + "fontWeight": "bold", + "textAlign": "center" + } + }, + { + "name": "outlineButton", + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 20 + } + }, + { + "name": "outlineButtonText", + "style": { + "fontSize": 13, + "fontWeight": "bold", + "textAlign": "center" + } + }, + { + "name": "badgeText", + "style": { + "textColor": "#FFFFFF", + "fontSize": 11, + "fontWeight": "bold" + } + }, + { + "name": "discountBadge", + "style": { + "backgroundColor": "#EF4444", + "borderRadius": 8 + } + }, + { + "name": "newBadge", + "style": { + "backgroundColor": "#10B981", + "borderRadius": 8 + } + }, + { + "name": "hotBadge", + "style": { + "backgroundColor": "#F97316", + "borderRadius": 8 + } + }, + { + "name": "exclusiveBadge", + "style": { + "backgroundColor": "#FFD700", + "borderRadius": 12 + } + }, + { + "name": "categoryChip", + "style": { + "borderRadius": 25 + } + }, + { + "name": "categoryChipText", + "style": { + "fontSize": 14, + "fontWeight": "bold" + } + }, + { + "name": "productName", + "style": { + "textColor": "#1A1A2E", + "fontSize": 14, + "fontWeight": "bold" + } + }, + { + "name": "productDesc", + "style": { + "textColor": "#6B7280", + "fontSize": 12 + } + }, + { + "name": "productRating", + "style": { + "textColor": "#F59E0B", + "fontSize": 12, + "fontWeight": "bold" + } + }, + { + "name": "productPrice", + "style": { + "textColor": "#1A1A2E", + "fontSize": 18, + "fontWeight": "bold" + } + }, + { + "name": "productImage", + "style": { + "borderRadius": 16 + } + }, + { + "name": "bannerTitle", + "style": { + "textColor": "#FFFFFF", + "fontSize": 26, + "fontWeight": "bold" + } + }, + { + "name": "bannerSubtitle", + "style": { + "fontSize": 14 + } + }, + { + "name": "bannerLabel", + "style": { + "fontSize": 12, + "fontWeight": "bold" + } + }, + { + "name": "bannerCard", + "style": { + "borderRadius": 16 + } + }, + { + "name": "quickActionCard", + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 16, + "shadowRadius": 4, + "shadowColor": "#00000010" + } + }, + { + "name": "quickActionIcon", + "style": { + "fontSize": 28, + "textAlign": "center" + } + }, + { + "name": "quickActionLabel", + "style": { + "textColor": "#1A1A2E", + "fontSize": 11, + "fontWeight": "bold", + "textAlign": "center" + } + }, + { + "name": "headerGradient", + "style": { + "background": { + "type": "linear_gradient", + "angle": 135, + "colors": ["#6C63FF", "#8B7CF7"] + } + } + }, + { + "name": "darkGradient", + "style": { + "background": { + "type": "linear_gradient", + "angle": 135, + "colors": ["#1a1a2e", "#16213e", "#0f3460"] + }, + "borderRadius": 20 + } + } + ], + "variables": { + "userName": "John", + "cartCount": 3, + "notificationCount": 5 + }, + "root": { + "type": "container", + "id": "home_root", + "containerType": "vertical", + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "spacing": 0 + }, + "style": { + "backgroundColor": "#F8F9FE" + }, + "children": [ + { + "type": "container", + "id": "header_section", + "containerType": "vertical", + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "padding": {"horizontal": 20, "vertical": 20}, + "spacing": 4 + }, + "styleClass": "headerGradient", + "children": [ + { + "type": "element", + "id": "greeting", + "elementType": "text", + "bindings": {"text": "Hello, {{userName}} 👋"}, + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "style": { + "textColor": "#FFFFFF", + "fontSize": 24, + "fontWeight": "bold" + } + }, + { + "type": "element", + "id": "subtitle", + "elementType": "text", + "bindings": {"text": "Find your favorite products"}, + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "style": { + "textColor": "#E8E8FF", + "fontSize": 15 + } + } + ] + }, + + { + "type": "element", + "id": "spacer_1", + "elementType": "spacer", + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 20, "unit": "dp"} + } + }, + + { + "type": "container", + "id": "banner_section", + "containerType": "vertical", + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "padding": {"horizontal": 16}, + "spacing": 12 + }, + "children": [ + { + "type": "element", + "id": "banners_title", + "elementType": "text", + "bindings": {"text": "🔥 Hot Deals"}, + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "styleClass": "sectionTitle" + }, + { + "type": "container", + "id": "auto_scroll_banner", + "containerType": "gallery", + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 180, "unit": "dp"} + }, + "galleryConfig": { + "mode": "snapping", + "orientation": "horizontal", + "peekPercentage": 5, + "snapBehavior": "center", + "spacing": 12, + "showIndicators": true, + "indicatorStyle": { + "activeColor": "#6C63FF", + "inactiveColor": "#D1D5DB", + "size": 8, + "spacing": 6, + "shape": "circle", + "position": "bottom" + }, + "autoScrollInterval": 4000, + "infiniteScroll": true + }, + "children": [ + { + "type": "container", + "id": "banner_1", + "containerType": "vertical", + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 160, "unit": "dp"}, + "padding": {"all": 20}, + "spacing": 6 + }, + "styleClass": "bannerCard", + "style": { + "background": { + "type": "linear_gradient", + "angle": 135, + "colors": ["#667eea", "#764ba2"] + } + }, + "children": [ + { + "type": "element", + "id": "banner_1_label", + "elementType": "text", + "bindings": {"text": "SUMMER SALE"}, + "layout": { + "width": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "styleClass": "bannerLabel", + "style": {"textColor": "#FFD700"} + }, + { + "type": "element", + "id": "banner_1_title", + "elementType": "text", + "bindings": {"text": "Up to 50% Off"}, + "layout": { + "width": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "styleClass": "bannerTitle" + }, + { + "type": "element", + "id": "banner_1_subtitle", + "elementType": "text", + "bindings": {"text": "On all fragrances & beauty"}, + "layout": { + "width": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "styleClass": "bannerSubtitle", + "style": {"textColor": "#E8E8FF"} + }, + { + "type": "container", + "id": "banner_1_btn", + "containerType": "box", + "layout": { + "width": {"value": 100, "unit": "dp"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "padding": {"vertical": 8, "horizontal": 16} + }, + "styleClass": "outlineButton", + "children": [ + { + "type": "element", + "id": "banner_1_btn_text", + "elementType": "text", + "bindings": {"text": "Shop Now"}, + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "styleClass": "outlineButtonText", + "style": {"textColor": "#667eea"} + } + ] + } + ] + }, + { + "type": "container", + "id": "banner_2", + "containerType": "vertical", + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 160, "unit": "dp"}, + "padding": {"all": 20}, + "spacing": 6 + }, + "styleClass": "bannerCard", + "style": { + "background": { + "type": "linear_gradient", + "angle": 135, + "colors": ["#f093fb", "#f5576c"] + } + }, + "children": [ + { + "type": "element", + "id": "banner_2_label", + "elementType": "text", + "bindings": {"text": "NEW ARRIVALS"}, + "layout": { + "width": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "styleClass": "bannerLabel", + "style": {"textColor": "#FFFFFF"} + }, + { + "type": "element", + "id": "banner_2_title", + "elementType": "text", + "bindings": {"text": "Fashion Week"}, + "layout": { + "width": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "styleClass": "bannerTitle" + }, + { + "type": "element", + "id": "banner_2_subtitle", + "elementType": "text", + "bindings": {"text": "Trending styles 2024"}, + "layout": { + "width": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "styleClass": "bannerSubtitle", + "style": {"textColor": "#FFE4E1"} + }, + { + "type": "container", + "id": "banner_2_btn", + "containerType": "box", + "layout": { + "width": {"value": 100, "unit": "dp"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "padding": {"vertical": 8, "horizontal": 16} + }, + "styleClass": "outlineButton", + "children": [ + { + "type": "element", + "id": "banner_2_btn_text", + "elementType": "text", + "bindings": {"text": "Explore"}, + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "styleClass": "outlineButtonText", + "style": {"textColor": "#f5576c"} + } + ] + } + ] + }, + { + "type": "container", + "id": "banner_3", + "containerType": "vertical", + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 160, "unit": "dp"}, + "padding": {"all": 20}, + "spacing": 6 + }, + "styleClass": "bannerCard", + "style": { + "background": { + "type": "linear_gradient", + "angle": 135, + "colors": ["#11998e", "#38ef7d"] + } + }, + "children": [ + { + "type": "element", + "id": "banner_3_label", + "elementType": "text", + "bindings": {"text": "ORGANIC"}, + "layout": { + "width": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "styleClass": "bannerLabel", + "style": {"textColor": "#FFFFFF"} + }, + { + "type": "element", + "id": "banner_3_title", + "elementType": "text", + "bindings": {"text": "Fresh Groceries"}, + "layout": { + "width": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "styleClass": "bannerTitle" + }, + { + "type": "element", + "id": "banner_3_subtitle", + "elementType": "text", + "bindings": {"text": "Farm to table products"}, + "layout": { + "width": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "styleClass": "bannerSubtitle", + "style": {"textColor": "#E8FFE8"} + }, + { + "type": "container", + "id": "banner_3_btn", + "containerType": "box", + "layout": { + "width": {"value": 100, "unit": "dp"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "padding": {"vertical": 8, "horizontal": 16} + }, + "styleClass": "outlineButton", + "children": [ + { + "type": "element", + "id": "banner_3_btn_text", + "elementType": "text", + "bindings": {"text": "Order Now"}, + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "styleClass": "outlineButtonText", + "style": {"textColor": "#11998e"} + } + ] + } + ] + } + ] + } + ] + }, + + { + "type": "element", + "id": "spacer_2", + "elementType": "spacer", + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 24, "unit": "dp"} + } + }, + + { + "type": "container", + "id": "categories_section", + "containerType": "vertical", + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "padding": {"horizontal": 16}, + "spacing": 12 + }, + "children": [ + { + "type": "element", + "id": "categories_title", + "elementType": "text", + "bindings": {"text": "🏷️ Categories"}, + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "styleClass": "sectionTitle" + }, + { + "type": "container", + "id": "categories_gallery", + "containerType": "gallery", + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 48, "unit": "dp"} + }, + "galleryConfig": { + "mode": "free_flow", + "orientation": "horizontal", + "spacing": 10 + }, + "children": [ + { + "type": "container", + "id": "cat_beauty", + "containerType": "horizontal", + "layout": { + "width": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "height": {"value": 44, "unit": "dp"}, + "padding": {"vertical": 12, "horizontal": 18}, + "spacing": 8 + }, + "styleClass": "categoryChip", + "style": {"backgroundColor": "#FCE7F3"}, + "children": [ + { + "type": "element", + "id": "cat_beauty_icon", + "elementType": "text", + "bindings": {"text": "💄"}, + "layout": { + "width": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "style": {"fontSize": 16} + }, + { + "type": "element", + "id": "cat_beauty_text", + "elementType": "text", + "bindings": {"text": "Beauty"}, + "layout": { + "width": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "styleClass": "categoryChipText", + "style": {"textColor": "#BE185D"} + } + ] + }, + { + "type": "container", + "id": "cat_fragrances", + "containerType": "horizontal", + "layout": { + "width": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "height": {"value": 44, "unit": "dp"}, + "padding": {"vertical": 12, "horizontal": 18}, + "spacing": 8 + }, + "styleClass": "categoryChip", + "style": {"backgroundColor": "#EDE9FE"}, + "children": [ + { + "type": "element", + "id": "cat_fragrances_icon", + "elementType": "text", + "bindings": {"text": "🌸"}, + "layout": { + "width": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "style": {"fontSize": 16} + }, + { + "type": "element", + "id": "cat_fragrances_text", + "elementType": "text", + "bindings": {"text": "Fragrances"}, + "layout": { + "width": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "styleClass": "categoryChipText", + "style": {"textColor": "#7C3AED"} + } + ] + }, + { + "type": "container", + "id": "cat_furniture", + "containerType": "horizontal", + "layout": { + "width": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "height": {"value": 44, "unit": "dp"}, + "padding": {"vertical": 12, "horizontal": 18}, + "spacing": 8 + }, + "styleClass": "categoryChip", + "style": {"backgroundColor": "#FEF3C7"}, + "children": [ + { + "type": "element", + "id": "cat_furniture_icon", + "elementType": "text", + "bindings": {"text": "🛋️"}, + "layout": { + "width": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "style": {"fontSize": 16} + }, + { + "type": "element", + "id": "cat_furniture_text", + "elementType": "text", + "bindings": {"text": "Furniture"}, + "layout": { + "width": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "styleClass": "categoryChipText", + "style": {"textColor": "#B45309"} + } + ] + }, + { + "type": "container", + "id": "cat_groceries", + "containerType": "horizontal", + "layout": { + "width": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "height": {"value": 44, "unit": "dp"}, + "padding": {"vertical": 12, "horizontal": 18}, + "spacing": 8 + }, + "styleClass": "categoryChip", + "style": {"backgroundColor": "#D1FAE5"}, + "children": [ + { + "type": "element", + "id": "cat_groceries_icon", + "elementType": "text", + "bindings": {"text": "🥗"}, + "layout": { + "width": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "style": {"fontSize": 16} + }, + { + "type": "element", + "id": "cat_groceries_text", + "elementType": "text", + "bindings": {"text": "Groceries"}, + "layout": { + "width": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "styleClass": "categoryChipText", + "style": {"textColor": "#047857"} + } + ] + } + ] + } + ] + }, + + { + "type": "element", + "id": "spacer_3", + "elementType": "spacer", + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 24, "unit": "dp"} + } + }, + + { + "type": "container", + "id": "full_banner_section", + "containerType": "vertical", + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "padding": {"horizontal": 16} + }, + "children": [ + { + "type": "container", + "id": "full_banner", + "containerType": "vertical", + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "padding": {"all": 24}, + "spacing": 10 + }, + "styleClass": "darkGradient", + "children": [ + { + "type": "container", + "id": "full_banner_badge", + "containerType": "box", + "layout": { + "width": {"value": 110, "unit": "dp"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "padding": {"vertical": 6, "horizontal": 12} + }, + "styleClass": "exclusiveBadge", + "children": [ + { + "type": "element", + "id": "full_banner_badge_text", + "elementType": "text", + "bindings": {"text": "⭐ EXCLUSIVE"}, + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "style": { + "textColor": "#1A1A2E", + "fontSize": 11, + "fontWeight": "bold", + "textAlign": "center" + } + } + ] + }, + { + "type": "element", + "id": "full_banner_title", + "elementType": "text", + "bindings": {"text": "Premium Membership 👑"}, + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "style": { + "textColor": "#FFFFFF", + "fontSize": 24, + "fontWeight": "bold" + } + }, + { + "type": "element", + "id": "full_banner_desc", + "elementType": "text", + "bindings": {"text": "Get unlimited free delivery, exclusive deals, and early access to sales!"}, + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "style": { + "textColor": "#B8B8D1", + "fontSize": 14, + "lineHeight": 20 + } + }, + { + "type": "container", + "id": "full_banner_cta", + "containerType": "box", + "layout": { + "width": {"value": 130, "unit": "dp"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "padding": {"vertical": 12, "horizontal": 20} + }, + "styleClass": "primaryButton", + "children": [ + { + "type": "element", + "id": "full_banner_cta_text", + "elementType": "text", + "bindings": {"text": "Join Now →"}, + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "styleClass": "primaryButtonText" + } + ] + } + ] + } + ] + }, + + { + "type": "element", + "id": "spacer_4", + "elementType": "spacer", + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 24, "unit": "dp"} + } + }, + + { + "type": "element", + "id": "divider_1", + "elementType": "divider", + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 1, "unit": "dp"}, + "padding": {"horizontal": 16} + }, + "dividerConfig": { + "orientation": "horizontal", + "thickness": 1, + "color": "#E5E7EB" + } + }, + + { + "type": "element", + "id": "spacer_5", + "elementType": "spacer", + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 24, "unit": "dp"} + } + }, + + { + "type": "container", + "id": "products_section", + "containerType": "vertical", + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "padding": {"horizontal": 16}, + "spacing": 16 + }, + "children": [ + { + "type": "element", + "id": "products_title", + "elementType": "text", + "bindings": {"text": "🛍️ Featured Products"}, + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "styleClass": "sectionTitle" + }, + { + "type": "container", + "id": "products_grid", + "containerType": "gallery", + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 320, "unit": "dp"} + }, + "galleryConfig": { + "mode": "free_flow_grid", + "orientation": "horizontal", + "itemsPerView": 2.15, + "spacing": 12 + }, + "children": [ + { + "type": "container", + "id": "product_card_1", + "containerType": "vertical", + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 100, "unit": "percent"}, + "spacing": 0 + }, + "styleClass": "cardContainer", + "children": [ + { + "type": "container", + "id": "product_1_image_container", + "containerType": "box", + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 140, "unit": "dp"} + }, + "style": { + "backgroundColor": "#FDF2F8", + "borderRadius": 16 + }, + "children": [ + { + "type": "element", + "id": "product_1_image", + "elementType": "image", + "bindings": { + "url": "https://cdn.dummyjson.com/product-images/beauty/essence-mascara-lash-princess/1.webp", + "contentDescription": "Essence Mascara" + }, + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 140, "unit": "dp"} + }, + "styleClass": "productImage" + } + ] + }, + { + "type": "container", + "id": "product_1_badge_row", + "containerType": "horizontal", + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "padding": {"horizontal": 12, "top": 10}, + "spacing": 6 + }, + "children": [ + { + "type": "container", + "id": "product_1_discount_badge", + "containerType": "box", + "layout": { + "width": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "padding": {"vertical": 4, "horizontal": 8} + }, + "styleClass": "discountBadge", + "children": [ + { + "type": "element", + "id": "product_1_discount_text", + "elementType": "text", + "bindings": {"text": "-10%"}, + "layout": { + "width": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "styleClass": "badgeText" + } + ] + } + ] + }, + { + "type": "container", + "id": "product_1_info", + "containerType": "vertical", + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "padding": {"horizontal": 12, "bottom": 12, "top": 6}, + "spacing": 4 + }, + "children": [ + { + "type": "element", + "id": "product_1_name", + "elementType": "text", + "bindings": {"text": "Essence Mascara"}, + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "styleClass": "productName" + }, + { + "type": "element", + "id": "product_1_desc", + "elementType": "text", + "bindings": {"text": "Lash Princess"}, + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "styleClass": "productDesc" + }, + { + "type": "element", + "id": "product_1_rating", + "elementType": "text", + "bindings": {"text": "⭐ 2.6 (99 in stock)"}, + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "styleClass": "productRating" + }, + { + "type": "element", + "id": "product_1_price", + "elementType": "text", + "bindings": {"text": "$9.99"}, + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "styleClass": "productPrice" + } + ] + } + ] + }, + { + "type": "container", + "id": "product_card_2", + "containerType": "vertical", + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 100, "unit": "percent"}, + "spacing": 0 + }, + "styleClass": "cardContainer", + "children": [ + { + "type": "container", + "id": "product_2_image_container", + "containerType": "box", + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 140, "unit": "dp"} + }, + "style": { + "backgroundColor": "#F5F3FF", + "borderRadius": 16 + }, + "children": [ + { + "type": "element", + "id": "product_2_image", + "elementType": "image", + "bindings": { + "url": "https://cdn.dummyjson.com/product-images/fragrances/chanel-coco-noir-eau-de/1.webp", + "contentDescription": "Chanel Coco Noir" + }, + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 140, "unit": "dp"} + }, + "styleClass": "productImage" + } + ] + }, + { + "type": "container", + "id": "product_2_badge_row", + "containerType": "horizontal", + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "padding": {"horizontal": 12, "top": 10}, + "spacing": 6 + }, + "children": [ + { + "type": "container", + "id": "product_2_new_badge", + "containerType": "box", + "layout": { + "width": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "padding": {"vertical": 4, "horizontal": 8} + }, + "styleClass": "newBadge", + "children": [ + { + "type": "element", + "id": "product_2_new_text", + "elementType": "text", + "bindings": {"text": "NEW"}, + "layout": { + "width": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "styleClass": "badgeText" + } + ] + } + ] + }, + { + "type": "container", + "id": "product_2_info", + "containerType": "vertical", + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "padding": {"horizontal": 12, "bottom": 12, "top": 6}, + "spacing": 4 + }, + "children": [ + { + "type": "element", + "id": "product_2_name", + "elementType": "text", + "bindings": {"text": "Chanel Coco Noir"}, + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "styleClass": "productName" + }, + { + "type": "element", + "id": "product_2_desc", + "elementType": "text", + "bindings": {"text": "Eau De Parfum"}, + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "styleClass": "productDesc" + }, + { + "type": "element", + "id": "product_2_rating", + "elementType": "text", + "bindings": {"text": "⭐ 4.3 (58 in stock)"}, + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "styleClass": "productRating" + }, + { + "type": "element", + "id": "product_2_price", + "elementType": "text", + "bindings": {"text": "$129.99"}, + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "styleClass": "productPrice" + } + ] + } + ] + }, + { + "type": "container", + "id": "product_card_3", + "containerType": "vertical", + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 100, "unit": "percent"}, + "spacing": 0 + }, + "styleClass": "cardContainer", + "children": [ + { + "type": "container", + "id": "product_3_image_container", + "containerType": "box", + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 140, "unit": "dp"} + }, + "style": { + "backgroundColor": "#FEF3C7", + "borderRadius": 16 + }, + "children": [ + { + "type": "element", + "id": "product_3_image", + "elementType": "image", + "bindings": { + "url": "https://cdn.dummyjson.com/product-images/furniture/annibale-colombo-sofa/1.webp", + "contentDescription": "Annibale Colombo Sofa" + }, + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 140, "unit": "dp"} + }, + "styleClass": "productImage" + } + ] + }, + { + "type": "container", + "id": "product_3_badge_row", + "containerType": "horizontal", + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "padding": {"horizontal": 12, "top": 10}, + "spacing": 6 + }, + "children": [ + { + "type": "container", + "id": "product_3_discount_badge", + "containerType": "box", + "layout": { + "width": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "padding": {"vertical": 4, "horizontal": 8} + }, + "styleClass": "discountBadge", + "children": [ + { + "type": "element", + "id": "product_3_discount_text", + "elementType": "text", + "bindings": {"text": "-14%"}, + "layout": { + "width": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "styleClass": "badgeText" + } + ] + } + ] + }, + { + "type": "container", + "id": "product_3_info", + "containerType": "vertical", + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "padding": {"horizontal": 12, "bottom": 12, "top": 6}, + "spacing": 4 + }, + "children": [ + { + "type": "element", + "id": "product_3_name", + "elementType": "text", + "bindings": {"text": "Colombo Sofa"}, + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "styleClass": "productName" + }, + { + "type": "element", + "id": "product_3_desc", + "elementType": "text", + "bindings": {"text": "Premium furniture"}, + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "styleClass": "productDesc" + }, + { + "type": "element", + "id": "product_3_rating", + "elementType": "text", + "bindings": {"text": "⭐ 3.9 (60 in stock)"}, + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "styleClass": "productRating" + }, + { + "type": "element", + "id": "product_3_price", + "elementType": "text", + "bindings": {"text": "$2,499"}, + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "styleClass": "productPrice" + } + ] + } + ] + }, + { + "type": "container", + "id": "product_card_4", + "containerType": "vertical", + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 100, "unit": "percent"}, + "spacing": 0 + }, + "styleClass": "cardContainer", + "children": [ + { + "type": "container", + "id": "product_4_image_container", + "containerType": "box", + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 140, "unit": "dp"} + }, + "style": { + "backgroundColor": "#DCFCE7", + "borderRadius": 16 + }, + "children": [ + { + "type": "element", + "id": "product_4_image", + "elementType": "image", + "bindings": { + "url": "https://cdn.dummyjson.com/product-images/groceries/apple/1.webp", + "contentDescription": "Fresh Apple" + }, + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 140, "unit": "dp"} + }, + "styleClass": "productImage" + } + ] + }, + { + "type": "container", + "id": "product_4_badge_row", + "containerType": "horizontal", + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "padding": {"horizontal": 12, "top": 10}, + "spacing": 6 + }, + "children": [ + { + "type": "container", + "id": "product_4_hot_badge", + "containerType": "box", + "layout": { + "width": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "padding": {"vertical": 4, "horizontal": 8} + }, + "styleClass": "hotBadge", + "children": [ + { + "type": "element", + "id": "product_4_hot_text", + "elementType": "text", + "bindings": {"text": "FRESH"}, + "layout": { + "width": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "styleClass": "badgeText" + } + ] + } + ] + }, + { + "type": "container", + "id": "product_4_info", + "containerType": "vertical", + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "padding": {"horizontal": 12, "bottom": 12, "top": 6}, + "spacing": 4 + }, + "children": [ + { + "type": "element", + "id": "product_4_name", + "elementType": "text", + "bindings": {"text": "Fresh Apples"}, + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "styleClass": "productName" + }, + { + "type": "element", + "id": "product_4_desc", + "elementType": "text", + "bindings": {"text": "Organic fruits"}, + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "styleClass": "productDesc" + }, + { + "type": "element", + "id": "product_4_rating", + "elementType": "text", + "bindings": {"text": "⭐ 4.2 (8 in stock)"}, + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "styleClass": "productRating" + }, + { + "type": "element", + "id": "product_4_price", + "elementType": "text", + "bindings": {"text": "$1.99"}, + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "styleClass": "productPrice" + } + ] + } + ] + } + ] + } + ] + }, + + { + "type": "element", + "id": "spacer_6", + "elementType": "spacer", + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 24, "unit": "dp"} + } + }, + + { + "type": "container", + "id": "quick_actions_section", + "containerType": "vertical", + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"}, + "padding": {"horizontal": 16}, + "spacing": 16 + }, + "children": [ + { + "type": "element", + "id": "quick_actions_title", + "elementType": "text", + "bindings": {"text": "⚡ Quick Actions"}, + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "styleClass": "sectionTitle" + }, + { + "type": "container", + "id": "quick_actions_grid", + "containerType": "gallery", + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 90, "unit": "dp"} + }, + "galleryConfig": { + "mode": "free_flow_grid", + "orientation": "horizontal", + "itemsPerView": 4, + "spacing": 12 + }, + "children": [ + { + "type": "container", + "id": "action_orders", + "containerType": "vertical", + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 100, "unit": "percent"}, + "padding": {"vertical": 16, "horizontal": 8}, + "spacing": 8 + }, + "styleClass": "quickActionCard", + "children": [ + { + "type": "element", + "id": "action_orders_icon", + "elementType": "text", + "bindings": {"text": "📦"}, + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "styleClass": "quickActionIcon" + }, + { + "type": "element", + "id": "action_orders_label", + "elementType": "text", + "bindings": {"text": "Orders"}, + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "styleClass": "quickActionLabel" + } + ] + }, + { + "type": "container", + "id": "action_wishlist", + "containerType": "vertical", + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 100, "unit": "percent"}, + "padding": {"vertical": 16, "horizontal": 8}, + "spacing": 8 + }, + "styleClass": "quickActionCard", + "children": [ + { + "type": "element", + "id": "action_wishlist_icon", + "elementType": "text", + "bindings": {"text": "❤️"}, + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "styleClass": "quickActionIcon" + }, + { + "type": "element", + "id": "action_wishlist_label", + "elementType": "text", + "bindings": {"text": "Saved"}, + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "styleClass": "quickActionLabel" + } + ] + }, + { + "type": "container", + "id": "action_wallet", + "containerType": "vertical", + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 100, "unit": "percent"}, + "padding": {"vertical": 16, "horizontal": 8}, + "spacing": 8 + }, + "styleClass": "quickActionCard", + "children": [ + { + "type": "element", + "id": "action_wallet_icon", + "elementType": "text", + "bindings": {"text": "💳"}, + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "styleClass": "quickActionIcon" + }, + { + "type": "element", + "id": "action_wallet_label", + "elementType": "text", + "bindings": {"text": "Wallet"}, + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "styleClass": "quickActionLabel" + } + ] + }, + { + "type": "container", + "id": "action_support", + "containerType": "vertical", + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 100, "unit": "percent"}, + "padding": {"vertical": 16, "horizontal": 8}, + "spacing": 8 + }, + "styleClass": "quickActionCard", + "children": [ + { + "type": "element", + "id": "action_support_icon", + "elementType": "text", + "bindings": {"text": "💬"}, + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "styleClass": "quickActionIcon" + }, + { + "type": "element", + "id": "action_support_label", + "elementType": "text", + "bindings": {"text": "Help"}, + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 0, "unit": "dp", "special": "wrap_content"} + }, + "styleClass": "quickActionLabel" + } + ] + } + ] + } + ] + }, + + { + "type": "element", + "id": "spacer_7", + "elementType": "spacer", + "layout": { + "width": {"value": 100, "unit": "percent"}, + "height": {"value": 32, "unit": "dp"} + } + } + ] + } +} \ No newline at end of file diff --git a/flutter-sample/assets/configs/showcase_dashboard.json b/flutter-sample/assets/configs/showcase_dashboard.json new file mode 100644 index 0000000..c062a45 --- /dev/null +++ b/flutter-sample/assets/configs/showcase_dashboard.json @@ -0,0 +1,868 @@ +{ + "theme": { + "id": "default", + "defaultStyle": { + "textColor": "#000000", + "fontSize": 14 + }, + "colors": {} + }, + "styleClasses": [], + "variables": {}, + "root": { + "type": "container", + "id": "dashboard_root", + "containerType": "vertical", + "layout": { + "spacing": 16, + "padding": { + "all": 16 + } + }, + "children": [ + { + "type": "container", + "id": "header_card", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 20 + }, + "spacing": 12 + }, + "style": { + "background": { + "type": "layered", + "layers": [ + { + "type": "linear_gradient", + "angle": 135, + "colors": ["#1A237E", "#283593", "#3949AB"] + }, + { + "type": "pattern", + "pattern_type": "dots", + "primary_color": "#00000000", + "secondary_color": "#FFFFFF05", + "size": 3, + "spacing": 15 + } + ] + }, + "borderRadius": 20, + "shadowRadius": 10, + "shadowColor": "#00000030" + }, + "children": [ + { + "type": "container", + "id": "header_top", + "containerType": "horizontal", + "layout": { + "spacing": 12 + }, + "children": [ + { + "type": "container", + "id": "header_text_section", + "containerType": "vertical", + "layout": { + "width": { + "value": 0, + "unit": "dp" + }, + "spacing": 6 + }, + "children": [ + { + "type": "element", + "id": "greeting", + "elementType": "text", + "bindings": { + "text": "Welcome back," + }, + "style": { + "textColor": "#FFFFFF", + "fontSize": 14, + "opacity": 0.8 + } + }, + { + "type": "element", + "id": "username_header", + "elementType": "text", + "bindings": { + "text": "John Doe" + }, + "style": { + "textColor": "#FFFFFF", + "fontSize": 22, + "fontWeight": "bold" + } + } + ] + }, + { + "type": "container", + "id": "notification_button", + "containerType": "box", + "layout": { + "width": { + "value": 48, + "unit": "dp" + }, + "height": { + "value": 48, + "unit": "dp" + }, + "padding": { + "all": 12 + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 12, + "opacity": 0.15 + }, + "children": [ + { + "type": "element", + "id": "bell_icon", + "elementType": "text", + "bindings": { + "text": "🔔" + }, + "style": { + "textColor": "#FFFFFF", + "fontSize": 20, + "textAlign": "center" + } + } + ] + } + ] + }, + { + "type": "container", + "id": "total_revenue_card", + "containerType": "vertical", + "layout": { + "spacing": 8, + "padding": { + "all": 16 + }, + "margin": { + "top": 12 + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 16, + "opacity": 0.12 + }, + "children": [ + { + "type": "element", + "id": "revenue_label", + "elementType": "text", + "bindings": { + "text": "Total Revenue" + }, + "style": { + "textColor": "#FFFFFF", + "fontSize": 13, + "opacity": 0.9 + } + }, + { + "type": "container", + "id": "revenue_amount_section", + "containerType": "horizontal", + "layout": { + "spacing": 12 + }, + "children": [ + { + "type": "element", + "id": "revenue_amount", + "elementType": "text", + "bindings": { + "text": "$45,231" + }, + "style": { + "textColor": "#FFFFFF", + "fontSize": 32, + "fontWeight": "bold" + } + }, + { + "type": "container", + "id": "growth_badge", + "containerType": "horizontal", + "layout": { + "spacing": 4, + "padding": { + "vertical": 6, + "horizontal": 10 + }, + "margin": { + "top": 8 + } + }, + "style": { + "backgroundColor": "#4CAF50", + "borderRadius": 12, + "opacity": 0.9 + }, + "children": [ + { + "type": "element", + "id": "growth_arrow", + "elementType": "text", + "bindings": { + "text": "↑" + }, + "style": { + "textColor": "#FFFFFF", + "fontSize": 12, + "fontWeight": "bold" + } + }, + { + "type": "element", + "id": "growth_percent", + "elementType": "text", + "bindings": { + "text": "12.5%" + }, + "style": { + "textColor": "#FFFFFF", + "fontSize": 12, + "fontWeight": "bold" + } + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "container", + "id": "metrics_grid", + "containerType": "horizontal", + "layout": { + "spacing": 12 + }, + "children": [ + { + "type": "container", + "id": "metric_card_1", + "containerType": "vertical", + "layout": { + "width": { + "value": 0, + "unit": "dp" + }, + "padding": { + "all": 16 + }, + "spacing": 12 + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 16, + "shadowRadius": 6, + "shadowColor": "#00000010" + }, + "children": [ + { + "type": "container", + "id": "metric_1_icon_container", + "containerType": "box", + "layout": { + "width": { + "value": 40, + "unit": "dp" + }, + "height": { + "value": 40, + "unit": "dp" + }, + "padding": { + "all": 8 + } + }, + "style": { + "backgroundColor": "#E3F2FD", + "borderRadius": 10, + "opacity": 0.9 + }, + "children": [ + { + "type": "element", + "id": "metric_1_icon", + "elementType": "text", + "bindings": { + "text": "👥" + }, + "style": { + "textColor": "#1976D2", + "fontSize": 20, + "textAlign": "center" + } + } + ] + }, + { + "type": "element", + "id": "metric_1_value", + "elementType": "text", + "bindings": { + "text": "1,234" + }, + "style": { + "textColor": "#1A1A1A", + "fontSize": 24, + "fontWeight": "bold" + } + }, + { + "type": "element", + "id": "metric_1_label", + "elementType": "text", + "bindings": { + "text": "Active Users" + }, + "style": { + "textColor": "#666666", + "fontSize": 12, + "opacity": 0.8 + } + } + ] + }, + { + "type": "container", + "id": "metric_card_2", + "containerType": "vertical", + "layout": { + "width": { + "value": 0, + "unit": "dp" + }, + "padding": { + "all": 16 + }, + "spacing": 12 + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 16, + "shadowRadius": 6, + "shadowColor": "#00000010" + }, + "children": [ + { + "type": "container", + "id": "metric_2_icon_container", + "containerType": "box", + "layout": { + "width": { + "value": 40, + "unit": "dp" + }, + "height": { + "value": 40, + "unit": "dp" + }, + "padding": { + "all": 8 + } + }, + "style": { + "backgroundColor": "#F3E5F5", + "borderRadius": 10, + "opacity": 0.9 + }, + "children": [ + { + "type": "element", + "id": "metric_2_icon", + "elementType": "text", + "bindings": { + "text": "📦" + }, + "style": { + "textColor": "#7B1FA2", + "fontSize": 20, + "textAlign": "center" + } + } + ] + }, + { + "type": "element", + "id": "metric_2_value", + "elementType": "text", + "bindings": { + "text": "89" + }, + "style": { + "textColor": "#1A1A1A", + "fontSize": 24, + "fontWeight": "bold" + } + }, + { + "type": "element", + "id": "metric_2_label", + "elementType": "text", + "bindings": { + "text": "Orders" + }, + "style": { + "textColor": "#666666", + "fontSize": 12, + "opacity": 0.8 + } + } + ] + } + ] + }, + { + "type": "container", + "id": "activity_card", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 20 + }, + "spacing": 16 + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 20, + "shadowRadius": 6, + "shadowColor": "#00000010" + }, + "children": [ + { + "type": "container", + "id": "activity_header", + "containerType": "horizontal", + "layout": { + "spacing": 12 + }, + "children": [ + { + "type": "element", + "id": "activity_title", + "elementType": "text", + "bindings": { + "text": "Recent Activity" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp" + } + }, + "style": { + "textColor": "#1A1A1A", + "fontSize": 18, + "fontWeight": "bold" + } + }, + { + "type": "container", + "id": "view_all_button", + "containerType": "box", + "layout": { + "padding": { + "vertical": 6, + "horizontal": 12 + } + }, + "style": { + "backgroundColor": "#F5F5F5", + "borderRadius": 8, + "opacity": 0.9 + }, + "children": [ + { + "type": "element", + "id": "view_all_text", + "elementType": "text", + "bindings": { + "text": "View All" + }, + "style": { + "textColor": "#667eea", + "fontSize": 12, + "fontWeight": "bold" + } + } + ] + } + ] + }, + { + "type": "container", + "id": "activity_item_1", + "containerType": "horizontal", + "layout": { + "spacing": 12, + "padding": { + "all": 12 + } + }, + "style": { + "backgroundColor": "#F8F9FA", + "borderRadius": 12, + "opacity": 0.7 + }, + "children": [ + { + "type": "container", + "id": "activity_1_icon", + "containerType": "box", + "layout": { + "width": { + "value": 40, + "unit": "dp" + }, + "height": { + "value": 40, + "unit": "dp" + }, + "padding": { + "all": 8 + } + }, + "style": { + "background": { + "type": "pulse", + "color": "#4CAF50", + "min_opacity": 0.3, + "max_opacity": 0.8, + "duration": 1500, + "loop": true + }, + "borderRadius": 10 + }, + "children": [ + { + "type": "element", + "id": "activity_1_emoji", + "elementType": "text", + "bindings": { + "text": "✓" + }, + "style": { + "textColor": "#FFFFFF", + "fontSize": 18, + "fontWeight": "bold", + "textAlign": "center" + } + } + ] + }, + { + "type": "container", + "id": "activity_1_content", + "containerType": "vertical", + "layout": { + "width": { + "value": 0, + "unit": "dp" + }, + "spacing": 4 + }, + "children": [ + { + "type": "element", + "id": "activity_1_title", + "elementType": "text", + "bindings": { + "text": "Payment Received" + }, + "style": { + "textColor": "#1A1A1A", + "fontSize": 14, + "fontWeight": "bold" + } + }, + { + "type": "element", + "id": "activity_1_desc", + "elementType": "text", + "bindings": { + "text": "Order #12345 completed" + }, + "style": { + "textColor": "#666666", + "fontSize": 12, + "opacity": 0.8 + } + } + ] + }, + { + "type": "element", + "id": "activity_1_time", + "elementType": "text", + "bindings": { + "text": "2m ago" + }, + "style": { + "textColor": "#999999", + "fontSize": 11, + "opacity": 0.7 + } + } + ] + }, + { + "type": "container", + "id": "activity_item_2", + "containerType": "horizontal", + "layout": { + "spacing": 12, + "padding": { + "all": 12 + } + }, + "style": { + "backgroundColor": "#F8F9FA", + "borderRadius": 12, + "opacity": 0.7 + }, + "children": [ + { + "type": "container", + "id": "activity_2_icon", + "containerType": "box", + "layout": { + "width": { + "value": 40, + "unit": "dp" + }, + "height": { + "value": 40, + "unit": "dp" + }, + "padding": { + "all": 8 + } + }, + "style": { + "backgroundColor": "#FFF3E0", + "borderRadius": 10, + "opacity": 0.9 + }, + "children": [ + { + "type": "element", + "id": "activity_2_emoji", + "elementType": "text", + "bindings": { + "text": "📦" + }, + "style": { + "textColor": "#F57C00", + "fontSize": 18, + "textAlign": "center" + } + } + ] + }, + { + "type": "container", + "id": "activity_2_content", + "containerType": "vertical", + "layout": { + "width": { + "value": 0, + "unit": "dp" + }, + "spacing": 4 + }, + "children": [ + { + "type": "element", + "id": "activity_2_title", + "elementType": "text", + "bindings": { + "text": "New Order Placed" + }, + "style": { + "textColor": "#1A1A1A", + "fontSize": 14, + "fontWeight": "bold" + } + }, + { + "type": "element", + "id": "activity_2_desc", + "elementType": "text", + "bindings": { + "text": "Order #12346 pending" + }, + "style": { + "textColor": "#666666", + "fontSize": 12, + "opacity": 0.8 + } + } + ] + }, + { + "type": "element", + "id": "activity_2_time", + "elementType": "text", + "bindings": { + "text": "15m ago" + }, + "style": { + "textColor": "#999999", + "fontSize": 11, + "opacity": 0.7 + } + } + ] + } + ] + }, + { + "type": "container", + "id": "quick_actions", + "containerType": "horizontal", + "layout": { + "spacing": 12 + }, + "children": [ + { + "type": "container", + "id": "action_1", + "containerType": "vertical", + "layout": { + "width": { + "value": 0, + "unit": "dp" + }, + "padding": { + "all": 16 + }, + "spacing": 8 + }, + "style": { + "background": { + "type": "linear_gradient", + "angle": 135, + "colors": ["#667eea", "#764ba2"] + }, + "borderRadius": 16, + "shadowRadius": 8, + "shadowColor": "#667eea40" + }, + "children": [ + { + "type": "element", + "id": "action_1_icon", + "elementType": "text", + "bindings": { + "text": "+" + }, + "style": { + "textColor": "#FFFFFF", + "fontSize": 28, + "fontWeight": "bold", + "textAlign": "center" + } + }, + { + "type": "element", + "id": "action_1_label", + "elementType": "text", + "bindings": { + "text": "New Order" + }, + "style": { + "textColor": "#FFFFFF", + "fontSize": 12, + "fontWeight": "bold", + "textAlign": "center", + "opacity": 0.95 + } + } + ] + }, + { + "type": "container", + "id": "action_2", + "containerType": "vertical", + "layout": { + "width": { + "value": 0, + "unit": "dp" + }, + "padding": { + "all": 16 + }, + "spacing": 8 + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 16, + "borderWidth": 2, + "borderColor": "#E0E0E0", + "opacity": 0.9 + }, + "children": [ + { + "type": "element", + "id": "action_2_icon", + "elementType": "text", + "bindings": { + "text": "📊" + }, + "style": { + "textColor": "#667eea", + "fontSize": 28, + "textAlign": "center" + } + }, + { + "type": "element", + "id": "action_2_label", + "elementType": "text", + "bindings": { + "text": "Reports" + }, + "style": { + "textColor": "#333333", + "fontSize": 12, + "fontWeight": "bold", + "textAlign": "center", + "opacity": 0.9 + } + } + ] + } + ] + } + ] + } +} diff --git a/flutter-sample/assets/configs/showcase_ecommerce_product.json b/flutter-sample/assets/configs/showcase_ecommerce_product.json new file mode 100644 index 0000000..0f764ce --- /dev/null +++ b/flutter-sample/assets/configs/showcase_ecommerce_product.json @@ -0,0 +1,522 @@ +{ + "theme": { + "id": "default", + "defaultStyle": { + "textColor": "#000000", + "fontSize": 14 + }, + "colors": {} + }, + "styleClasses": [], + "variables": {}, + "root": { + "type": "container", + "id": "ecommerce_root", + "containerType": "vertical", + "layout": { + "spacing": 16, + "padding": { + "all": 16 + } + }, + "children": [ + { + "type": "container", + "id": "product_card", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "background": { + "type": "layered", + "layers": [ + { + "type": "linear_gradient", + "angle": 135, + "colors": ["#FFFFFF", "#F8F9FA"] + }, + { + "type": "pattern", + "pattern_type": "dots", + "primary_color": "#00000000", + "secondary_color": "#E0E0E010", + "size": 4, + "spacing": 20 + } + ] + }, + "borderRadius": 20, + "shadowRadius": 12, + "shadowColor": "#00000020" + }, + "children": [ + { + "type": "container", + "id": "product_image_section", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 200, + "unit": "dp" + } + }, + "style": { + "background": { + "type": "radial_gradient", + "center_x": 0.5, + "center_y": 0.5, + "radius": 1.0, + "colors": ["#F0F0F0", "#D0D0D0"] + }, + "borderRadius": 20 + }, + "children": [ + { + "type": "container", + "id": "new_badge", + "containerType": "box", + "layout": { + "width": { + "value": 70, + "unit": "dp" + }, + "height": { + "value": 32, + "unit": "dp" + }, + "margin": { + "left": 12, + "top": 12 + }, + "padding": { + "vertical": 8, + "horizontal": 12 + } + }, + "style": { + "background": { + "type": "animated_gradient", + "gradient_type": "linear", + "angle": 45, + "colors": ["#FFD700", "#FFA500", "#FFD700"], + "duration": 2000, + "loop": true, + "animation_style": "smooth" + }, + "borderRadius": 16, + "opacity": 0.95 + }, + "children": [ + { + "type": "element", + "id": "new_badge_text", + "elementType": "text", + "bindings": { + "text": "NEW" + }, + "style": { + "textColor": "#FFFFFF", + "fontSize": 12, + "fontWeight": "bold", + "textAlign": "center" + } + } + ] + }, + { + "type": "element", + "id": "product_image_placeholder", + "elementType": "text", + "bindings": { + "text": "🎁" + }, + "style": { + "textColor": "#666666", + "fontSize": 80, + "textAlign": "center", + "opacity": 0.5 + }, + "layout": { + "padding": { + "top": 85 + } + } + } + ] + }, + { + "type": "container", + "id": "product_info", + "containerType": "vertical", + "layout": { + "padding": { + "all": 20 + }, + "spacing": 12 + }, + "children": [ + { + "type": "element", + "id": "product_category", + "elementType": "text", + "bindings": { + "text": "PREMIUM WATCHES" + }, + "style": { + "textColor": "#FF6B6B", + "fontSize": 11, + "fontWeight": "bold", + "opacity": 0.9 + } + }, + { + "type": "element", + "id": "product_name", + "elementType": "text", + "bindings": { + "text": "Luxury Chronograph Watch" + }, + "style": { + "textColor": "#1A1A1A", + "fontSize": 22, + "fontWeight": "bold", + "lineHeight": 28 + } + }, + { + "type": "element", + "id": "product_description", + "elementType": "text", + "bindings": { + "text": "Elegant timepiece with Swiss movement, sapphire crystal, and water resistance up to 100m. Perfect blend of style and functionality." + }, + "style": { + "textColor": "#666666", + "fontSize": 14, + "lineHeight": 20, + "opacity": 0.85 + } + }, + { + "type": "container", + "id": "price_section", + "containerType": "horizontal", + "layout": { + "spacing": 12, + "margin": { + "top": 8 + } + }, + "children": [ + { + "type": "element", + "id": "current_price", + "elementType": "text", + "bindings": { + "text": "$599" + }, + "style": { + "textColor": "#1A1A1A", + "fontSize": 28, + "fontWeight": "bold" + } + }, + { + "type": "element", + "id": "original_price", + "elementType": "text", + "bindings": { + "text": "$899" + }, + "style": { + "textColor": "#999999", + "fontSize": 18, + "textDecoration": "strikethrough", + "opacity": 0.7 + } + }, + { + "type": "container", + "id": "discount_badge", + "containerType": "box", + "layout": { + "padding": { + "horizontal": 12, + "vertical": 6 + } + }, + "style": { + "backgroundColor": "#FF6B6B", + "borderRadius": 12, + "opacity": 0.9 + }, + "children": [ + { + "type": "element", + "id": "discount_text", + "elementType": "text", + "bindings": { + "text": "33% OFF" + }, + "style": { + "textColor": "#FFFFFF", + "fontSize": 12, + "fontWeight": "bold" + } + } + ] + } + ] + }, + { + "type": "element", + "id": "divider", + "elementType": "divider", + "layout": { + "margin": { + "vertical": 16 + } + }, + "dividerConfig": { + "orientation": "horizontal", + "thickness": 1, + "color": "#E0E0E0" + } + }, + { + "type": "container", + "id": "features_section", + "containerType": "vertical", + "layout": { + "spacing": 10 + }, + "children": [ + { + "type": "element", + "id": "features_title", + "elementType": "text", + "bindings": { + "text": "Key Features:" + }, + "style": { + "textColor": "#1A1A1A", + "fontSize": 14, + "fontWeight": "bold" + } + }, + { + "type": "container", + "id": "feature_1", + "containerType": "horizontal", + "layout": { + "spacing": 8 + }, + "children": [ + { + "type": "element", + "id": "feature_1_bullet", + "elementType": "text", + "bindings": { + "text": "✓" + }, + "style": { + "textColor": "#4ECDC4", + "fontSize": 14, + "fontWeight": "bold" + } + }, + { + "type": "element", + "id": "feature_1_text", + "elementType": "text", + "bindings": { + "text": "Swiss Automatic Movement" + }, + "style": { + "textColor": "#666666", + "fontSize": 14, + "opacity": 0.9 + } + } + ] + }, + { + "type": "container", + "id": "feature_2", + "containerType": "horizontal", + "layout": { + "spacing": 8 + }, + "children": [ + { + "type": "element", + "id": "feature_2_bullet", + "elementType": "text", + "bindings": { + "text": "✓" + }, + "style": { + "textColor": "#4ECDC4", + "fontSize": 14, + "fontWeight": "bold" + } + }, + { + "type": "element", + "id": "feature_2_text", + "elementType": "text", + "bindings": { + "text": "Sapphire Crystal Glass" + }, + "style": { + "textColor": "#666666", + "fontSize": 14, + "opacity": 0.9 + } + } + ] + }, + { + "type": "container", + "id": "feature_3", + "containerType": "horizontal", + "layout": { + "spacing": 8 + }, + "children": [ + { + "type": "element", + "id": "feature_3_bullet", + "elementType": "text", + "bindings": { + "text": "✓" + }, + "style": { + "textColor": "#4ECDC4", + "fontSize": 14, + "fontWeight": "bold" + } + }, + { + "type": "element", + "id": "feature_3_text", + "elementType": "text", + "bindings": { + "text": "100m Water Resistant" + }, + "style": { + "textColor": "#666666", + "fontSize": 14, + "opacity": 0.9 + } + } + ] + } + ] + }, + { + "type": "container", + "id": "action_buttons", + "containerType": "horizontal", + "layout": { + "spacing": 12, + "margin": { + "top": 20 + } + }, + "children": [ + { + "type": "container", + "id": "add_to_cart_container", + "containerType": "box", + "layout": { + "width": { + "value": 0, + "unit": "dp" + }, + "padding": { + "vertical": 16, + "horizontal": 32 + } + }, + "style": { + "background": { + "type": "linear_gradient", + "angle": 135, + "colors": ["#667eea", "#764ba2"] + }, + "borderRadius": 16 + }, + "children": [ + { + "type": "element", + "id": "add_to_cart_text", + "elementType": "text", + "bindings": { + "text": "Add to Cart" + }, + "style": { + "textColor": "#FFFFFF", + "fontSize": 16, + "fontWeight": "bold" + } + } + ] + }, + { + "type": "container", + "id": "wishlist_button", + "containerType": "box", + "layout": { + "width": { + "value": 56, + "unit": "dp" + }, + "height": { + "value": 56, + "unit": "dp" + }, + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 16, + "borderWidth": 2, + "borderColor": "#FF6B6B", + "opacity": 0.9 + }, + "children": [ + { + "type": "element", + "id": "wishlist_icon", + "elementType": "text", + "bindings": { + "text": "♥" + }, + "style": { + "textColor": "#FF6B6B", + "fontSize": 20, + "textAlign": "center" + } + } + ] + } + ] + } + ] + } + ] + } + ] + } +} diff --git a/flutter-sample/assets/configs/showcase_profile_screen.json b/flutter-sample/assets/configs/showcase_profile_screen.json new file mode 100644 index 0000000..f8d81de --- /dev/null +++ b/flutter-sample/assets/configs/showcase_profile_screen.json @@ -0,0 +1,734 @@ +{ + "root": { + "id": "profile_root", + "type": "container", + "container_type": "vertical", + "layout": { + "spacing": 0, + "padding": { "all": 0 } + }, + "children": [ + { + "id": "header_section", + "type": "container", + "container_type": "box", + "layout": { + "width": { "value": 100, "unit": "percent" }, + "height": { "value": 300, "unit": "dp" } + }, + "style": { + "background": { + "type": "layered", + "layers": [ + { + "type": "linear_gradient", + "angle": 180, + "colors": ["#667eea", "#764ba2"] + }, + { + "type": "pattern", + "pattern_type": "dots", + "primary_color": "#00000000", + "secondary_color": "#FFFFFF15", + "size": 6, + "spacing": 20 + } + ] + } + }, + "children": [ + { + "id": "header_content", + "type": "container", + "container_type": "vertical", + "layout": { "padding": { "all": 24 } }, + "children": [ + { + "id": "profile_image", + "type": "element", + "element_type": "image", + "bindings": { "url": "https://i.pravatar.cc/200?img=68" }, + "layout": { + "width": { "value": 120, "unit": "dp" }, + "height": { "value": 120, "unit": "dp" }, + "margin": { "bottom": 16 } + }, + "style": { + "border_radius": 60, + "border_width": 4, + "border_color": "#FFFFFF", + "shadow_radius": 8, + "shadow_color": "#00000040" + } + }, + { + "id": "user_name", + "type": "element", + "element_type": "text", + "bindings": { "text": "Sarah Anderson" }, + "style": { + "text_color": "#FFFFFF", + "font_size": 28, + "font_weight": "bold" + } + }, + { + "id": "user_role", + "type": "element", + "element_type": "text", + "bindings": { "text": "Premium Member • Since 2023" }, + "layout": { "margin": { "top": 4 } }, + "style": { + "text_color": "#FFFFFFCC", + "font_size": 14, + "opacity": 0.9 + } + }, + { + "id": "premium_badge", + "type": "container", + "container_type": "box", + "layout": { + "width": { "value": 140, "unit": "dp" }, + "height": { "value": 36, "unit": "dp" }, + "margin": { "top": 16 } + }, + "style": { + "background": { + "type": "animated_gradient", + "gradient_type": "linear", + "angle": 45, + "colors": ["#FFD700", "#FFA500", "#FFD700"], + "duration": 3000, + "loop": true, + "animation_style": "smooth" + }, + "border_radius": 20, + "opacity": 0.95 + }, + "children": [ + { + "id": "premium_text", + "type": "element", + "element_type": "text", + "bindings": { "text": "⭐ PREMIUM" }, + "layout": { + "padding": { "horizontal": 16, "vertical": 8 } + }, + "style": { + "text_color": "#1A1A1A", + "font_size": 14, + "font_weight": "bold", + "text_align": "center" + } + } + ] + } + ] + } + ] + }, + { + "id": "stats_section", + "type": "container", + "container_type": "horizontal", + "layout": { + "spacing": 12, + "padding": { "all": 16 } + }, + "children": [ + { + "id": "stat_card_1", + "type": "container", + "container_type": "vertical", + "layout": { + "width": { "value": 0, "unit": "dp", "weight": 1 }, + "padding": { "all": 16 } + }, + "style": { + "background": { + "type": "linear_gradient", + "angle": 135, + "colors": ["#FF6B6B", "#FF8E53"] + }, + "border_radius": 16, + "shadow_radius": 4, + "shadow_color": "#FF6B6B40" + }, + "children": [ + { + "id": "stat_1_value", + "type": "element", + "element_type": "text", + "bindings": { "text": "248" }, + "style": { + "text_color": "#FFFFFF", + "font_size": 32, + "font_weight": "bold", + "text_align": "center" + } + }, + { + "id": "stat_1_label", + "type": "element", + "element_type": "text", + "bindings": { "text": "Posts" }, + "layout": { "margin": { "top": 4 } }, + "style": { + "text_color": "#FFFFFFCC", + "font_size": 12, + "text_align": "center", + "opacity": 0.9 + } + } + ] + }, + { + "id": "stat_card_2", + "type": "container", + "container_type": "vertical", + "layout": { + "width": { "value": 0, "unit": "dp", "weight": 1 }, + "padding": { "all": 16 } + }, + "style": { + "background": { + "type": "linear_gradient", + "angle": 135, + "colors": ["#4ECDC4", "#44A08D"] + }, + "border_radius": 16, + "shadow_radius": 4, + "shadow_color": "#4ECDC440" + }, + "children": [ + { + "id": "stat_2_value", + "type": "element", + "element_type": "text", + "bindings": { "text": "12.5K" }, + "style": { + "text_color": "#FFFFFF", + "font_size": 32, + "font_weight": "bold", + "text_align": "center" + } + }, + { + "id": "stat_2_label", + "type": "element", + "element_type": "text", + "bindings": { "text": "Followers" }, + "layout": { "margin": { "top": 4 } }, + "style": { + "text_color": "#FFFFFFCC", + "font_size": 12, + "text_align": "center", + "opacity": 0.9 + } + } + ] + }, + { + "id": "stat_card_3", + "type": "container", + "container_type": "vertical", + "layout": { + "width": { "value": 0, "unit": "dp", "weight": 1 }, + "padding": { "all": 16 } + }, + "style": { + "background": { + "type": "linear_gradient", + "angle": 135, + "colors": ["#A770EF", "#CF8BF3"] + }, + "border_radius": 16, + "shadow_radius": 4, + "shadow_color": "#A770EF40" + }, + "children": [ + { + "id": "stat_3_value", + "type": "element", + "element_type": "text", + "bindings": { "text": "3.2K" }, + "style": { + "text_color": "#FFFFFF", + "font_size": 32, + "font_weight": "bold", + "text_align": "center" + } + }, + { + "id": "stat_3_label", + "type": "element", + "element_type": "text", + "bindings": { "text": "Following" }, + "layout": { "margin": { "top": 4 } }, + "style": { + "text_color": "#FFFFFFCC", + "font_size": 12, + "text_align": "center", + "opacity": 0.9 + } + } + ] + } + ] + }, + { + "id": "actions_section", + "type": "container", + "container_type": "vertical", + "layout": { + "spacing": 12, + "padding": { "horizontal": 16, "vertical": 8 } + }, + "children": [ + { + "id": "primary_button", + "type": "element", + "element_type": "button", + "bindings": { "text": "Edit Profile" }, + "layout": { + "width": { "value": 100, "unit": "percent" }, + "height": { "value": 56, "unit": "dp" } + }, + "style": { + "background": { + "type": "linear_gradient", + "angle": 90, + "colors": ["#667eea", "#764ba2"] + }, + "text_color": "#FFFFFF", + "font_size": 16, + "font_weight": "bold", + "border_radius": 28, + "shadow_radius": 8, + "shadow_color": "#667eea60" + } + }, + { + "id": "button_row", + "type": "container", + "container_type": "horizontal", + "layout": { "spacing": 12 }, + "children": [ + { + "id": "secondary_button_1", + "type": "element", + "element_type": "button", + "bindings": { "text": "Share Profile" }, + "layout": { + "width": { "value": 0, "unit": "dp", "weight": 1 }, + "height": { "value": 48, "unit": "dp" } + }, + "style": { + "background": { "type": "solid", "color": "#F5F5F5" }, + "text_color": "#333333", + "font_size": 14, + "font_weight": "medium", + "border_radius": 24, + "opacity": 0.95 + } + }, + { + "id": "secondary_button_2", + "type": "element", + "element_type": "button", + "bindings": { "text": "Settings" }, + "layout": { + "width": { "value": 0, "unit": "dp", "weight": 1 }, + "height": { "value": 48, "unit": "dp" } + }, + "style": { + "background": { "type": "solid", "color": "#F5F5F5" }, + "text_color": "#333333", + "font_size": 14, + "font_weight": "medium", + "border_radius": 24, + "opacity": 0.95 + } + } + ] + } + ] + }, + { + "id": "content_section", + "type": "container", + "container_type": "vertical", + "layout": { + "spacing": 16, + "padding": { "all": 16 } + }, + "children": [ + { + "id": "section_header", + "type": "element", + "element_type": "text", + "bindings": { "text": "Recent Activity" }, + "style": { + "text_color": "#1A1A1A", + "font_size": 20, + "font_weight": "bold" + } + }, + { + "id": "activity_card_1", + "type": "container", + "container_type": "horizontal", + "layout": { + "spacing": 16, + "padding": { "all": 16 } + }, + "style": { + "background": { + "type": "layered", + "layers": [ + { "type": "solid", "color": "#FFFFFF" }, + { + "type": "pattern", + "pattern_type": "grid", + "primary_color": "#00000000", + "secondary_color": "#E0E0E015", + "size": 1, + "spacing": 30 + } + ] + }, + "border_radius": 16, + "border_width": 1, + "border_color": "#E0E0E0", + "shadow_radius": 2, + "shadow_color": "#00000010" + }, + "children": [ + { + "id": "activity_icon", + "type": "container", + "container_type": "box", + "layout": { + "width": { "value": 60, "unit": "dp" }, + "height": { "value": 60, "unit": "dp" } + }, + "style": { + "background": { + "type": "radial_gradient", + "center_x": 0.5, + "center_y": 0.5, + "radius": 1.0, + "colors": ["#FF6B6B", "#FF8E53"] + }, + "border_radius": 30, + "opacity": 0.9 + }, + "children": [ + { + "id": "activity_icon_text", + "type": "element", + "element_type": "text", + "bindings": { "text": "📸" }, + "layout": { "padding": { "all": 15 } }, + "style": { + "font_size": 28, + "text_align": "center" + } + } + ] + }, + { + "id": "activity_content", + "type": "container", + "container_type": "vertical", + "layout": { + "width": { "value": 0, "unit": "dp", "weight": 1 }, + "spacing": 4 + }, + "children": [ + { + "id": "activity_title", + "type": "element", + "element_type": "text", + "bindings": { "text": "Posted a new photo" }, + "style": { + "text_color": "#1A1A1A", + "font_size": 16, + "font_weight": "bold" + } + }, + { + "id": "activity_description", + "type": "element", + "element_type": "text", + "bindings": { "text": "Beautiful sunset at the beach 🌅" }, + "style": { + "text_color": "#666666", + "font_size": 14, + "opacity": 0.8 + } + }, + { + "id": "activity_time", + "type": "element", + "element_type": "text", + "bindings": { "text": "2 hours ago" }, + "layout": { "margin": { "top": 4 } }, + "style": { + "text_color": "#999999", + "font_size": 12, + "opacity": 0.7 + } + } + ] + } + ] + }, + { + "id": "activity_card_2", + "type": "container", + "container_type": "horizontal", + "layout": { + "spacing": 16, + "padding": { "all": 16 } + }, + "style": { + "background": { + "type": "shimmer", + "base_color": "#F5F5F5", + "highlight_color": "#FFFFFF", + "angle": 45, + "duration": 1500, + "loop": true + }, + "border_radius": 16, + "opacity": 0.6 + }, + "children": [ + { + "id": "loading_placeholder_icon", + "type": "container", + "container_type": "box", + "layout": { + "width": { "value": 60, "unit": "dp" }, + "height": { "value": 60, "unit": "dp" } + }, + "style": { + "background": { "type": "solid", "color": "#E0E0E0" }, + "border_radius": 30 + } + }, + { + "id": "loading_placeholder_content", + "type": "container", + "container_type": "vertical", + "layout": { + "width": { "value": 0, "unit": "dp", "weight": 1 }, + "spacing": 8 + }, + "children": [ + { + "id": "loading_line_1", + "type": "element", + "element_type": "spacer", + "layout": { + "width": { "value": 80, "unit": "percent" }, + "height": { "value": 16, "unit": "dp" } + }, + "style": { + "background": { "type": "solid", "color": "#E0E0E0" }, + "border_radius": 8 + } + }, + { + "id": "loading_line_2", + "type": "element", + "element_type": "spacer", + "layout": { + "width": { "value": 60, "unit": "percent" }, + "height": { "value": 14, "unit": "dp" } + }, + "style": { + "background": { "type": "solid", "color": "#E0E0E0" }, + "border_radius": 8 + } + }, + { + "id": "loading_line_3", + "type": "element", + "element_type": "spacer", + "layout": { + "width": { "value": 40, "unit": "percent" }, + "height": { "value": 12, "unit": "dp" } + }, + "style": { + "background": { "type": "solid", "color": "#E0E0E0" }, + "border_radius": 8 + } + } + ] + } + ] + }, + { + "id": "activity_card_3", + "type": "container", + "container_type": "horizontal", + "layout": { + "spacing": 16, + "padding": { "all": 16 } + }, + "style": { + "background": { + "type": "layered", + "layers": [ + { "type": "solid", "color": "#FFFFFF" }, + { + "type": "pattern", + "pattern_type": "stripes_diagonal", + "primary_color": "#00000000", + "secondary_color": "#4ECDC410", + "size": 2, + "spacing": 20 + } + ] + }, + "border_radius": 16, + "border_width": 1, + "border_color": "#E0E0E0", + "shadow_radius": 2, + "shadow_color": "#00000010" + }, + "children": [ + { + "id": "achievement_icon", + "type": "container", + "container_type": "box", + "layout": { + "width": { "value": 60, "unit": "dp" }, + "height": { "value": 60, "unit": "dp" } + }, + "style": { + "background": { + "type": "sweep_gradient", + "center_x": 0.5, + "center_y": 0.5, + "colors": ["#4ECDC4", "#44A08D", "#4ECDC4"] + }, + "border_radius": 30, + "opacity": 0.9 + }, + "children": [ + { + "id": "achievement_icon_text", + "type": "element", + "element_type": "text", + "bindings": { "text": "🏆" }, + "layout": { "padding": { "all": 15 } }, + "style": { + "font_size": 28, + "text_align": "center" + } + } + ] + }, + { + "id": "achievement_content", + "type": "container", + "container_type": "vertical", + "layout": { + "width": { "value": 0, "unit": "dp", "weight": 1 }, + "spacing": 4 + }, + "children": [ + { + "id": "achievement_title", + "type": "element", + "element_type": "text", + "bindings": { "text": "Achievement Unlocked!" }, + "style": { + "text_color": "#1A1A1A", + "font_size": 16, + "font_weight": "bold" + } + }, + { + "id": "achievement_description", + "type": "element", + "element_type": "text", + "bindings": { "text": "You've reached 10K followers milestone" }, + "style": { + "text_color": "#666666", + "font_size": 14, + "opacity": 0.8 + } + }, + { + "id": "achievement_time", + "type": "element", + "element_type": "text", + "bindings": { "text": "1 day ago" }, + "layout": { "margin": { "top": 4 } }, + "style": { + "text_color": "#999999", + "font_size": 12, + "opacity": 0.7 + } + } + ] + } + ] + } + ] + }, + { + "id": "footer_section", + "type": "container", + "container_type": "box", + "layout": { + "width": { "value": 100, "unit": "percent" }, + "padding": { "all": 24 } + }, + "style": { + "background": { + "type": "pulse", + "color": "#667eea", + "min_opacity": 0.05, + "max_opacity": 0.15, + "duration": 2000, + "loop": true + } + }, + "children": [ + { + "id": "footer_text", + "type": "element", + "element_type": "text", + "bindings": { + "text": "✨ Powered by Native Display Kit\nServer-Driven UI with 11 Background Types" + }, + "style": { + "text_color": "#667eea", + "font_size": 12, + "text_align": "center", + "opacity": 0.8, + "line_height": 18 + } + } + ] + } + ] + }, + "theme": { + "name": "default", + "colors": { + "primary": "#667eea", + "secondary": "#764ba2", + "background": "#FFFFFF", + "text": "#1A1A1A" + } + }, + "style_classes": [], + "variables": {} +} diff --git a/flutter-sample/assets/configs/showcase_social_profile.json b/flutter-sample/assets/configs/showcase_social_profile.json new file mode 100644 index 0000000..89cc3ab --- /dev/null +++ b/flutter-sample/assets/configs/showcase_social_profile.json @@ -0,0 +1,732 @@ +{ + "theme": { + "id": "default", + "defaultStyle": { + "textColor": "#000000", + "fontSize": 14 + }, + "colors": {} + }, + "styleClasses": [], + "variables": {}, + "root": { + "type": "container", + "id": "social_profile_root", + "containerType": "vertical", + "layout": { + "spacing": 0, + "padding": { + "all": 0 + } + }, + "children": [ + { + "type": "container", + "id": "profile_card", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "shadowRadius": 8, + "shadowColor": "#00000015" + }, + "children": [ + { + "type": "container", + "id": "hero_section", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 200, + "unit": "dp" + } + }, + "style": { + "background": { + "type": "layered", + "layers": [ + { + "type": "linear_gradient", + "angle": 135, + "colors": ["#667eea", "#764ba2", "#f093fb"] + }, + { + "type": "pattern", + "pattern_type": "grid", + "primary_color": "#00000000", + "secondary_color": "#FFFFFF08", + "size": 1, + "spacing": 20 + } + ] + } + }, + "children": [ + { + "type": "container", + "id": "status_badges", + "containerType": "horizontal", + "layout": { + "spacing": 8, + "margin": { + "top": 16, + "right": 16 + } + }, + "children": [ + { + "type": "container", + "id": "verified_badge", + "containerType": "horizontal", + "layout": { + "spacing": 6, + "padding": { + "vertical": 6, + "horizontal": 12 + } + }, + "style": { + "backgroundColor": "#4CAF50", + "borderRadius": 12, + "opacity": 0.95 + }, + "children": [ + { + "type": "element", + "id": "verified_icon", + "elementType": "text", + "bindings": { + "text": "✓" + }, + "style": { + "textColor": "#FFFFFF", + "fontSize": 12, + "fontWeight": "bold" + } + }, + { + "type": "element", + "id": "verified_text", + "elementType": "text", + "bindings": { + "text": "Verified" + }, + "style": { + "textColor": "#FFFFFF", + "fontSize": 11, + "fontWeight": "bold" + } + } + ] + }, + { + "type": "container", + "id": "pro_badge", + "containerType": "box", + "layout": { + "padding": { + "vertical": 6, + "horizontal": 12 + } + }, + "style": { + "background": { + "type": "animated_gradient", + "gradient_type": "linear", + "angle": 90, + "colors": ["#FFD700", "#FFA500"], + "duration": 3000, + "loop": true, + "animation_style": "smooth" + }, + "borderRadius": 12, + "opacity": 0.95 + }, + "children": [ + { + "type": "element", + "id": "pro_text", + "elementType": "text", + "bindings": { + "text": "PRO" + }, + "style": { + "textColor": "#FFFFFF", + "fontSize": 11, + "fontWeight": "bold" + } + } + ] + } + ] + } + ] + }, + { + "type": "container", + "id": "profile_info_section", + "containerType": "vertical", + "layout": { + "padding": { + "horizontal": 20, + "top": 0, + "bottom": 20 + }, + "spacing": 16, + "margin": { + "top": -60 + } + }, + "children": [ + { + "type": "container", + "id": "avatar_section", + "containerType": "vertical", + "layout": { + "spacing": 12 + }, + "children": [ + { + "type": "container", + "id": "avatar_container", + "containerType": "box", + "layout": { + "width": { + "value": 120, + "unit": "dp" + }, + "height": { + "value": 120, + "unit": "dp" + } + }, + "style": { + "background": { + "type": "radial_gradient", + "center_x": 0.5, + "center_y": 0.5, + "radius": 1.0, + "colors": ["#E8E8E8", "#D0D0D0"] + }, + "borderRadius": 60, + "borderWidth": 4, + "borderColor": "#FFFFFF", + "shadowRadius": 8, + "shadowColor": "#00000020" + }, + "children": [ + { + "type": "element", + "id": "avatar_emoji", + "elementType": "text", + "bindings": { + "text": "👤" + }, + "style": { + "textColor": "#999999", + "fontSize": 60, + "textAlign": "center", + "opacity": 0.6 + }, + "layout": { + "padding": { + "top": 30 + } + } + } + ] + }, + { + "type": "container", + "id": "name_section", + "containerType": "vertical", + "layout": { + "spacing": 4 + }, + "children": [ + { + "type": "element", + "id": "display_name", + "elementType": "text", + "bindings": { + "text": "Sarah Anderson" + }, + "style": { + "textColor": "#1A1A1A", + "fontSize": 24, + "fontWeight": "bold" + } + }, + { + "type": "element", + "id": "username", + "elementType": "text", + "bindings": { + "text": "@sarah_designs" + }, + "style": { + "textColor": "#666666", + "fontSize": 16, + "opacity": 0.8 + } + } + ] + }, + { + "type": "element", + "id": "bio", + "elementType": "text", + "bindings": { + "text": "UI/UX Designer | Creating beautiful digital experiences | Love minimalism and good coffee ☕" + }, + "style": { + "textColor": "#333333", + "fontSize": 14, + "lineHeight": 20, + "opacity": 0.9 + } + } + ] + }, + { + "type": "container", + "id": "stats_section", + "containerType": "horizontal", + "layout": { + "spacing": 24, + "padding": { + "vertical": 20 + } + }, + "style": { + "backgroundColor": "#F8F9FA", + "borderRadius": 16, + "opacity": 0.6 + }, + "children": [ + { + "type": "container", + "id": "followers_stat", + "containerType": "vertical", + "layout": { + "spacing": 4, + "padding": { + "horizontal": 16 + } + }, + "children": [ + { + "type": "element", + "id": "followers_count", + "elementType": "text", + "bindings": { + "text": "12.5K" + }, + "style": { + "textColor": "#1A1A1A", + "fontSize": 20, + "fontWeight": "bold", + "textAlign": "center" + } + }, + { + "type": "element", + "id": "followers_label", + "elementType": "text", + "bindings": { + "text": "Followers" + }, + "style": { + "textColor": "#666666", + "fontSize": 12, + "textAlign": "center", + "opacity": 0.8 + } + } + ] + }, + { + "type": "element", + "id": "stat_divider_1", + "elementType": "divider", + "dividerConfig": { + "orientation": "vertical", + "thickness": 1, + "color": "#E0E0E0" + } + }, + { + "type": "container", + "id": "following_stat", + "containerType": "vertical", + "layout": { + "spacing": 4, + "padding": { + "horizontal": 16 + } + }, + "children": [ + { + "type": "element", + "id": "following_count", + "elementType": "text", + "bindings": { + "text": "842" + }, + "style": { + "textColor": "#1A1A1A", + "fontSize": 20, + "fontWeight": "bold", + "textAlign": "center" + } + }, + { + "type": "element", + "id": "following_label", + "elementType": "text", + "bindings": { + "text": "Following" + }, + "style": { + "textColor": "#666666", + "fontSize": 12, + "textAlign": "center", + "opacity": 0.8 + } + } + ] + }, + { + "type": "element", + "id": "stat_divider_2", + "elementType": "divider", + "dividerConfig": { + "orientation": "vertical", + "thickness": 1, + "color": "#E0E0E0" + } + }, + { + "type": "container", + "id": "posts_stat", + "containerType": "vertical", + "layout": { + "spacing": 4, + "padding": { + "horizontal": 16 + } + }, + "children": [ + { + "type": "element", + "id": "posts_count", + "elementType": "text", + "bindings": { + "text": "234" + }, + "style": { + "textColor": "#1A1A1A", + "fontSize": 20, + "fontWeight": "bold", + "textAlign": "center" + } + }, + { + "type": "element", + "id": "posts_label", + "elementType": "text", + "bindings": { + "text": "Posts" + }, + "style": { + "textColor": "#666666", + "fontSize": 12, + "textAlign": "center", + "opacity": 0.8 + } + } + ] + } + ] + }, + { + "type": "container", + "id": "action_buttons_section", + "containerType": "horizontal", + "layout": { + "spacing": 12 + }, + "children": [ + { + "type": "container", + "id": "follow_button", + "containerType": "box", + "layout": { + "width": { + "value": 0, + "unit": "dp" + }, + "padding": { + "vertical": 14, + "horizontal": 24 + } + }, + "style": { + "background": { + "type": "linear_gradient", + "angle": 135, + "colors": ["#667eea", "#764ba2"] + }, + "borderRadius": 12 + }, + "children": [ + { + "type": "element", + "id": "follow_text", + "elementType": "text", + "bindings": { + "text": "Follow" + }, + "style": { + "textColor": "#FFFFFF", + "fontSize": 15, + "fontWeight": "bold", + "textAlign": "center" + } + } + ] + }, + { + "type": "container", + "id": "message_button", + "containerType": "box", + "layout": { + "width": { + "value": 0, + "unit": "dp" + }, + "padding": { + "vertical": 14, + "horizontal": 24 + } + }, + "style": { + "backgroundColor": "#F5F5F5", + "borderRadius": 12, + "opacity": 0.8 + }, + "children": [ + { + "type": "element", + "id": "message_text", + "elementType": "text", + "bindings": { + "text": "Message" + }, + "style": { + "textColor": "#333333", + "fontSize": 15, + "fontWeight": "bold", + "textAlign": "center" + } + } + ] + }, + { + "type": "container", + "id": "more_button", + "containerType": "box", + "layout": { + "width": { + "value": 48, + "unit": "dp" + }, + "height": { + "value": 48, + "unit": "dp" + }, + "padding": { + "all": 12 + } + }, + "style": { + "backgroundColor": "#F5F5F5", + "borderRadius": 12, + "opacity": 0.8 + }, + "children": [ + { + "type": "element", + "id": "more_icon", + "elementType": "text", + "bindings": { + "text": "⋯" + }, + "style": { + "textColor": "#333333", + "fontSize": 20, + "fontWeight": "bold", + "textAlign": "center" + } + } + ] + } + ] + }, + { + "type": "element", + "id": "section_divider", + "elementType": "divider", + "layout": { + "margin": { + "vertical": 16 + } + }, + "dividerConfig": { + "orientation": "horizontal", + "thickness": 1, + "color": "#E8E8E8" + } + }, + { + "type": "container", + "id": "interests_section", + "containerType": "vertical", + "layout": { + "spacing": 12 + }, + "children": [ + { + "type": "element", + "id": "interests_title", + "elementType": "text", + "bindings": { + "text": "Interests" + }, + "style": { + "textColor": "#1A1A1A", + "fontSize": 16, + "fontWeight": "bold" + } + }, + { + "type": "container", + "id": "interests_tags", + "containerType": "horizontal", + "layout": { + "spacing": 8 + }, + "children": [ + { + "type": "container", + "id": "tag_design", + "containerType": "box", + "layout": { + "padding": { + "vertical": 8, + "horizontal": 16 + } + }, + "style": { + "backgroundColor": "#E3F2FD", + "borderRadius": 16, + "opacity": 0.9 + }, + "children": [ + { + "type": "element", + "id": "tag_design_text", + "elementType": "text", + "bindings": { + "text": "Design" + }, + "style": { + "textColor": "#1976D2", + "fontSize": 13, + "fontWeight": "bold" + } + } + ] + }, + { + "type": "container", + "id": "tag_ui", + "containerType": "box", + "layout": { + "padding": { + "vertical": 8, + "horizontal": 16 + } + }, + "style": { + "backgroundColor": "#F3E5F5", + "borderRadius": 16, + "opacity": 0.9 + }, + "children": [ + { + "type": "element", + "id": "tag_ui_text", + "elementType": "text", + "bindings": { + "text": "UI/UX" + }, + "style": { + "textColor": "#7B1FA2", + "fontSize": 13, + "fontWeight": "bold" + } + } + ] + }, + { + "type": "container", + "id": "tag_creative", + "containerType": "box", + "layout": { + "padding": { + "vertical": 8, + "horizontal": 16 + } + }, + "style": { + "backgroundColor": "#FFF3E0", + "borderRadius": 16, + "opacity": 0.9 + }, + "children": [ + { + "type": "element", + "id": "tag_creative_text", + "elementType": "text", + "bindings": { + "text": "Creative" + }, + "style": { + "textColor": "#F57C00", + "fontSize": 13, + "fontWeight": "bold" + } + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } +} diff --git a/flutter-sample/lib/app.dart b/flutter-sample/lib/app.dart new file mode 100644 index 0000000..a937567 --- /dev/null +++ b/flutter-sample/lib/app.dart @@ -0,0 +1,60 @@ +import 'package:flutter/material.dart'; + +import 'screens/banner_showcase_screen.dart'; +import 'screens/test_browser_screen.dart'; +import 'screens/other_demos_screen.dart'; +import 'screens/more_menu_screen.dart'; + +class NativeDisplaySampleApp extends StatelessWidget { + const NativeDisplaySampleApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Native Display Sample', + theme: ThemeData( + colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF1A73E8)), + useMaterial3: true, + ), + home: const MainScreen(), + ); + } +} + +class MainScreen extends StatefulWidget { + const MainScreen({super.key}); + + @override + State createState() => _MainScreenState(); +} + +class _MainScreenState extends State { + int _selectedTab = 0; + + static const _tabs = [ + BannerShowcaseScreen(), + TestBrowserScreen(), + OtherDemosScreen(), + MoreMenuScreen(), + ]; + + @override + Widget build(BuildContext context) { + return Scaffold( + body: IndexedStack( + index: _selectedTab, + children: _tabs, + ), + bottomNavigationBar: NavigationBar( + selectedIndex: _selectedTab, + onDestinationSelected: (i) => setState(() => _selectedTab = i), + destinations: const [ + NavigationDestination(icon: Icon(Icons.image), label: 'Banners'), + NavigationDestination(icon: Icon(Icons.science), label: 'Browser'), + NavigationDestination(icon: Icon(Icons.grid_view), label: 'Demos'), + NavigationDestination(icon: Icon(Icons.more_horiz), label: 'More'), + ], + ), + ); + } +} diff --git a/flutter-sample/lib/main.dart b/flutter-sample/lib/main.dart new file mode 100644 index 0000000..6652d1e --- /dev/null +++ b/flutter-sample/lib/main.dart @@ -0,0 +1,7 @@ +import 'package:flutter/material.dart'; + +import 'app.dart'; + +void main() { + runApp(const NativeDisplaySampleApp()); +} diff --git a/flutter-sample/lib/screens/animation_demo_screen.dart b/flutter-sample/lib/screens/animation_demo_screen.dart new file mode 100644 index 0000000..90c087b --- /dev/null +++ b/flutter-sample/lib/screens/animation_demo_screen.dart @@ -0,0 +1,114 @@ +import 'package:flutter/material.dart'; +import 'package:clevertap_native_display/clevertap_native_display.dart'; + +import '../widgets/json_loader.dart'; +import '../widgets/error_widget.dart'; + +const _demos = [ + ('Container Fade', 'assets/configs/animation_container_fade.json', + 'Entire container fades in (500ms). All children appear together.'), + ('Staggered Children', 'assets/configs/animation_staggered_children.json', + 'Each child slides in from left with 100ms stagger delay.'), + ('Container + Children', 'assets/configs/animation_container_and_children.json', + 'Container fades in first, then children animate in sequence.'), +]; + +class AnimationDemoScreen extends StatefulWidget { + const AnimationDemoScreen({super.key}); + + @override + State createState() => _AnimationDemoScreenState(); +} + +class _AnimationDemoScreenState extends State { + int _selectedDemo = 0; + NativeDisplayConfig? _config; + String? _error; + + @override + void initState() { + super.initState(); + _loadDemo(0); + } + + Future _loadDemo(int index) async { + setState(() { + _config = null; + _error = null; + _selectedDemo = index; + }); + final config = await JsonLoader.loadFromAsset(_demos[index].$2); + if (!mounted) return; + setState(() { + if (config == null) { + _error = 'Failed to load ${_demos[index].$1}'; + } else { + _config = config; + } + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Animations')), + body: Column( + children: [ + SingleChildScrollView( + scrollDirection: Axis.horizontal, + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), + child: Row( + children: List.generate(_demos.length, (i) { + return Padding( + padding: const EdgeInsets.only(right: 8), + child: FilterChip( + label: Text(_demos[i].$1), + selected: _selectedDemo == i, + onSelected: (_) => _loadDemo(i), + ), + ); + }), + ), + ), + Container( + margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: const Color(0xFFFFF3E0), + borderRadius: BorderRadius.circular(8), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Icon(Icons.lightbulb, color: Color(0xFFE65100), size: 18), + const SizedBox(width: 8), + Expanded( + child: Text( + _demos[_selectedDemo].$3, + style: const TextStyle(color: Color(0xFFE65100), fontSize: 14), + ), + ), + ], + ), + ), + Expanded( + child: Container( + color: const Color(0xFFF5F5F5), + child: _error != null + ? Center(child: ErrorDisplay(message: _error!)) + : _config == null + ? const Center(child: CircularProgressIndicator()) + : SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: NativeDisplayView( + key: ValueKey(_selectedDemo), + config: _config!, + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/flutter-sample/lib/screens/arrangement_demo_screen.dart b/flutter-sample/lib/screens/arrangement_demo_screen.dart new file mode 100644 index 0000000..62157e8 --- /dev/null +++ b/flutter-sample/lib/screens/arrangement_demo_screen.dart @@ -0,0 +1,150 @@ +import 'package:flutter/material.dart'; +import 'package:clevertap_native_display/clevertap_native_display.dart'; + +import '../widgets/json_loader.dart'; +import '../widgets/error_widget.dart'; + +class _Strategy { + final String label; + final ArrangementStrategy strategy; + final double? spacing; + + const _Strategy(this.label, this.strategy, {this.spacing}); +} + +const _strategies = [ + _Strategy('SPACED', ArrangementStrategy.spaced, spacing: 16), + _Strategy('BETWEEN', ArrangementStrategy.spaceBetween), + _Strategy('EVENLY', ArrangementStrategy.spaceEvenly), + _Strategy('AROUND', ArrangementStrategy.spaceAround), + _Strategy('START', ArrangementStrategy.start), + _Strategy('CENTER', ArrangementStrategy.center), + _Strategy('END', ArrangementStrategy.end), +]; + +class ArrangementDemoScreen extends StatefulWidget { + const ArrangementDemoScreen({super.key}); + + @override + State createState() => _ArrangementDemoScreenState(); +} + +class _ArrangementDemoScreenState extends State { + NativeDisplayConfig? _baseConfig; + NativeDisplayConfig? _displayConfig; + String? _error; + int _selectedIndex = 0; + + @override + void initState() { + super.initState(); + _loadConfig(); + } + + Future _loadConfig() async { + final config = await JsonLoader.loadFromAsset('assets/configs/arrangement_demo.json'); + if (config == null) { + setState(() => _error = 'Failed to load arrangement_demo.json'); + return; + } + setState(() { + _baseConfig = config; + _displayConfig = config; + }); + } + + void _applyStrategy(int index) { + final s = _strategies[index]; + final base = _baseConfig; + if (base == null) return; + + final root = base.root; + if (root == null || root is! NativeDisplayContainer) { + setState(() { + _selectedIndex = index; + _displayConfig = base; + }); + return; + } + + final newArrangement = ChildArrangement( + spacing: s.spacing, + strategy: s.strategy, + ); + + final updatedLayout = Layout( + width: root.layout?.width, + height: root.layout?.height, + aspectRatio: root.layout?.aspectRatio, + offset: root.layout?.offset, + padding: root.layout?.padding, + arrangement: newArrangement, + ); + + final updatedRoot = NativeDisplayContainer( + id: root.id, + containerType: root.containerType, + children: root.children, + layout: updatedLayout, + style: root.style, + styleClass: root.styleClass, + visible: root.visible, + actions: root.actions, + animation: root.animation, + galleryConfig: root.galleryConfig, + dividerConfig: root.dividerConfig, + ); + + setState(() { + _selectedIndex = index; + _displayConfig = NativeDisplayConfig( + version: base.version, + theme: base.theme, + styleClasses: base.styleClasses, + variables: base.variables, + root: updatedRoot, + ); + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Arrangements')), + body: Column( + children: [ + SingleChildScrollView( + scrollDirection: Axis.horizontal, + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), + child: Row( + children: List.generate(_strategies.length, (i) { + final s = _strategies[i]; + return Padding( + padding: const EdgeInsets.only(right: 8), + child: FilterChip( + label: Text(s.label), + selected: _selectedIndex == i, + onSelected: (_) => _applyStrategy(i), + ), + ); + }), + ), + ), + Expanded( + child: Container( + color: const Color(0xFFF5F5F5), + child: _error != null + ? Center(child: ErrorDisplay(message: _error!)) + : _displayConfig == null + ? const Center(child: CircularProgressIndicator()) + : SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: NativeDisplayView(config: _displayConfig!), + ), + ), + ), + ], + ), + ); + } +} diff --git a/flutter-sample/lib/screens/banner_detail_screen.dart b/flutter-sample/lib/screens/banner_detail_screen.dart new file mode 100644 index 0000000..4f42372 --- /dev/null +++ b/flutter-sample/lib/screens/banner_detail_screen.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; +import 'package:clevertap_native_display/clevertap_native_display.dart'; + +import '../widgets/json_loader.dart'; +import '../widgets/error_widget.dart'; + +class BannerDetailScreen extends StatelessWidget { + final String title; + final String assetPath; + + const BannerDetailScreen({ + super.key, + required this.title, + required this.assetPath, + }); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text(title)), + body: FutureBuilder( + future: JsonLoader.loadFromAsset(assetPath), + builder: (ctx, snap) { + if (snap.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } + if (snap.data == null) { + return Center(child: ErrorDisplay(message: 'Failed to load $assetPath')); + } + return SingleChildScrollView( + child: NativeDisplayView(config: snap.data!), + ); + }, + ), + ); + } +} diff --git a/flutter-sample/lib/screens/banner_showcase_screen.dart b/flutter-sample/lib/screens/banner_showcase_screen.dart new file mode 100644 index 0000000..b599e7d --- /dev/null +++ b/flutter-sample/lib/screens/banner_showcase_screen.dart @@ -0,0 +1,161 @@ +import 'package:flutter/material.dart'; + +import 'banner_detail_screen.dart'; + +class _BannerItem { + final String emoji; + final String title; + final String description; + final String assetPath; + + const _BannerItem({ + required this.emoji, + required this.title, + required this.description, + required this.assetPath, + }); +} + +const _banners = [ + _BannerItem( + emoji: '☀️', + title: 'Summer Sale', + description: 'Hero banner with gradient', + assetPath: 'assets/banners/banner-01-hero-summer-sale.json', + ), + _BannerItem( + emoji: '📱', + title: 'iPhone 15 Pro', + description: 'Product showcase', + assetPath: 'assets/banners/banner-02-product-iphone.json', + ), + _BannerItem( + emoji: '🎉', + title: 'New Features', + description: 'App update announcement', + assetPath: 'assets/banners/banner-03-announcement-update.json', + ), + _BannerItem( + emoji: '✈️', + title: 'Travel Deals', + description: 'Multi-button travel banner', + assetPath: 'assets/banners/banner-04-travel-deals.json', + ), + _BannerItem( + emoji: '👗', + title: 'Fashion Collection', + description: 'Image banner', + assetPath: 'assets/banners/banner-05-fashion-collection.json', + ), + _BannerItem( + emoji: '💳', + title: 'Cashback Offer', + description: 'Credit card with GIF', + assetPath: 'assets/banners/banner-06-credit-card-offer.json', + ), + _BannerItem( + emoji: '⭐', + title: 'App Rating', + description: 'Social proof', + assetPath: 'assets/banners/banner-07-app-rating.json', + ), + _BannerItem( + emoji: '⚡', + title: 'Flash Sale', + description: 'Urgency banner', + assetPath: 'assets/banners/banner-08-flash-sale.json', + ), + _BannerItem( + emoji: '💎', + title: 'Go Premium', + description: 'Typography showcase', + assetPath: 'assets/banners/banner-09-premium-subscription.json', + ), + _BannerItem( + emoji: '👋', + title: 'Welcome', + description: 'Onboarding banner', + assetPath: 'assets/banners/banner-10-welcome-onboarding.json', + ), +]; + +class BannerShowcaseScreen extends StatelessWidget { + const BannerShowcaseScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + color: const Color(0xFFF5F5F5), + child: ListView.separated( + padding: const EdgeInsets.all(16), + itemCount: _banners.length, + separatorBuilder: (_, __) => const SizedBox(height: 12), + itemBuilder: (ctx, i) => _BannerListItem(banner: _banners[i]), + ), + ); + } +} + +class _BannerListItem extends StatelessWidget { + final _BannerItem banner; + + const _BannerListItem({required this.banner}); + + @override + Widget build(BuildContext context) { + return Card( + color: Colors.white, + elevation: 1, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + child: InkWell( + borderRadius: BorderRadius.circular(12), + onTap: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => BannerDetailScreen( + title: banner.title, + assetPath: banner.assetPath, + ), + ), + ); + }, + child: Padding( + padding: const EdgeInsets.all(16), + child: Row( + children: [ + CircleAvatar( + radius: 24, + backgroundColor: const Color(0xFFF5F5F5), + child: Text(banner.emoji, style: const TextStyle(fontSize: 22)), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + banner.title, + style: Theme.of(context) + .textTheme + .titleMedium + ?.copyWith(fontWeight: FontWeight.w600, color: const Color(0xFF333333)), + ), + const SizedBox(height: 4), + Text( + banner.description, + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(color: const Color(0xFF666666)), + ), + ], + ), + ), + const Icon(Icons.chevron_right, color: Color(0xFFCCCCCC)), + ], + ), + ), + ), + ); + } +} diff --git a/flutter-sample/lib/screens/bridge_integration_screen.dart b/flutter-sample/lib/screens/bridge_integration_screen.dart new file mode 100644 index 0000000..f6a38c1 --- /dev/null +++ b/flutter-sample/lib/screens/bridge_integration_screen.dart @@ -0,0 +1,156 @@ +import 'package:flutter/material.dart'; +import 'package:clevertap_native_display/clevertap_native_display.dart'; + +import '../widgets/json_loader.dart'; +import '../widgets/error_widget.dart'; + +class BridgeIntegrationScreen extends StatelessWidget { + const BridgeIntegrationScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Bridge Integration')), + body: SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Bridge Integration Demo', + style: Theme.of(context) + .textTheme + .headlineSmall + ?.copyWith(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + Text( + 'Demonstrates how NativeDisplayBridge.fetchConfig() retrieves configs from ' + 'the native CleverTap Core SDK. The cards below load mock JSON configs ' + 'from the local assets folder to simulate bridge payloads.', + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(color: const Color(0xFF666666)), + ), + const Divider(height: 32), + _ConfigSection( + title: 'Mock Product Unit', + description: 'Simulates a product display unit delivered by the Core SDK.', + assetPath: 'assets/configs/bridge_mock_product.json', + ), + const SizedBox(height: 24), + _ConfigSection( + title: 'Mock Notification Unit', + description: 'Simulates a notification-style display unit.', + assetPath: 'assets/configs/bridge_mock_notification.json', + ), + const SizedBox(height: 32), + _ApiInfoCard(), + ], + ), + ), + ); + } +} + +class _ConfigSection extends StatelessWidget { + final String title; + final String description; + final String assetPath; + + const _ConfigSection({ + required this.title, + required this.description, + required this.assetPath, + }); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(title, + style: Theme.of(context) + .textTheme + .titleMedium + ?.copyWith(fontWeight: FontWeight.bold, color: Theme.of(context).colorScheme.primary)), + const SizedBox(height: 4), + Text(description, + style: Theme.of(context).textTheme.bodySmall?.copyWith(color: const Color(0xFF666666))), + const SizedBox(height: 12), + FutureBuilder( + future: JsonLoader.loadFromAsset(assetPath), + builder: (ctx, snap) { + if (snap.connectionState == ConnectionState.waiting) { + return const SizedBox(height: 60, child: Center(child: CircularProgressIndicator())); + } + if (snap.data == null) { + return ErrorDisplay(message: 'Failed to load $assetPath'); + } + return Card( + elevation: 2, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + child: Padding( + padding: const EdgeInsets.all(12), + child: NativeDisplayView(config: snap.data!), + ), + ); + }, + ), + ], + ); + } +} + +class _ApiInfoCard extends StatelessWidget { + @override + Widget build(BuildContext context) { + const snippet = '''// Fetch a config from the Core SDK by unit ID +final config = await NativeDisplayBridge.fetchConfig('my-unit-id'); +if (config != null) { + // Pass directly to NativeDisplayView + NativeDisplayView(config: config) +} + +// Report analytics events +await NativeDisplayBridge.pushViewedEvent(unitId); +await NativeDisplayBridge.pushClickedEvent(unitId, elementId: nodeId);'''; + + return Card( + elevation: 0, + color: const Color(0xFFF0F4FF), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Bridge API', + style: Theme.of(context) + .textTheme + .titleSmall + ?.copyWith(fontWeight: FontWeight.bold, color: const Color(0xFF1565C0))), + const SizedBox(height: 8), + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: const Color(0xFFE3F2FD), + borderRadius: BorderRadius.circular(8), + ), + child: SelectableText( + snippet, + style: const TextStyle( + fontFamily: 'monospace', + fontSize: 12, + color: Color(0xFF1565C0), + height: 1.5, + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/flutter-sample/lib/screens/home_screen_demo.dart b/flutter-sample/lib/screens/home_screen_demo.dart new file mode 100644 index 0000000..2088e35 --- /dev/null +++ b/flutter-sample/lib/screens/home_screen_demo.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; +import 'package:clevertap_native_display/clevertap_native_display.dart'; + +import '../widgets/json_loader.dart'; +import '../widgets/error_widget.dart'; + +class HomeScreenDemo extends StatefulWidget { + const HomeScreenDemo({super.key}); + + @override + State createState() => _HomeScreenDemoState(); +} + +class _HomeScreenDemoState extends State { + NativeDisplayConfig? _config; + String? _error; + + @override + void initState() { + super.initState(); + _load(); + } + + Future _load() async { + final config = await JsonLoader.loadFromAsset('assets/configs/home_screen.json'); + if (!mounted) return; + setState(() { + _config = config; + _error = config == null ? 'Failed to load home_screen.json' : null; + }); + } + + @override + Widget build(BuildContext context) { + if (_error != null) return Center(child: ErrorDisplay(message: _error!)); + if (_config == null) return const Center(child: CircularProgressIndicator()); + return SingleChildScrollView( + child: NativeDisplayView( + config: _config!, + actionListener: (action, nodeId, params) { + debugPrint('[HomeScreen] action=$action nodeId=$nodeId params=$params'); + }, + componentListener: (event, nodeId, params) { + debugPrint('[HomeScreen] event=$event nodeId=$nodeId'); + return false; + }, + ), + ); + } +} diff --git a/flutter-sample/lib/screens/json_viewer_screen.dart b/flutter-sample/lib/screens/json_viewer_screen.dart new file mode 100644 index 0000000..170d4b2 --- /dev/null +++ b/flutter-sample/lib/screens/json_viewer_screen.dart @@ -0,0 +1,66 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +import '../widgets/json_loader.dart'; + +class JsonViewerScreen extends StatelessWidget { + final String assetPath; + final String? rawJson; + + const JsonViewerScreen({super.key, required this.assetPath, this.rawJson}); + + @override + Widget build(BuildContext context) { + if (rawJson != null) { + return _JsonViewerScaffold(assetPath: assetPath, json: rawJson!); + } + return FutureBuilder( + future: JsonLoader.loadStringFromAsset(assetPath), + builder: (ctx, snap) { + if (snap.connectionState == ConnectionState.waiting) { + return const Scaffold(body: Center(child: CircularProgressIndicator())); + } + final json = snap.data ?? 'Failed to load $assetPath'; + return _JsonViewerScaffold(assetPath: assetPath, json: json); + }, + ); + } +} + +class _JsonViewerScaffold extends StatelessWidget { + final String assetPath; + final String json; + + const _JsonViewerScaffold({required this.assetPath, required this.json}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text( + assetPath.split('/').last, + style: const TextStyle(fontSize: 14), + ), + actions: [ + IconButton( + icon: const Icon(Icons.copy), + tooltip: 'Copy JSON', + onPressed: () { + Clipboard.setData(ClipboardData(text: json)); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('JSON copied to clipboard')), + ); + }, + ), + ], + ), + body: SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: SelectableText( + json, + style: const TextStyle(fontFamily: 'monospace', fontSize: 12), + ), + ), + ); + } +} diff --git a/flutter-sample/lib/screens/more_menu_screen.dart b/flutter-sample/lib/screens/more_menu_screen.dart new file mode 100644 index 0000000..dcea7f3 --- /dev/null +++ b/flutter-sample/lib/screens/more_menu_screen.dart @@ -0,0 +1,104 @@ +import 'package:flutter/material.dart'; + +import 'banner_showcase_screen.dart'; +import 'arrangement_demo_screen.dart'; +import 'animation_demo_screen.dart'; +import 'home_screen_demo.dart'; +import 'bridge_integration_screen.dart'; +import 'slot_demo_screen.dart'; + +class MoreMenuScreen extends StatelessWidget { + const MoreMenuScreen({super.key}); + + static const _items = [ + (Icons.image_outlined, 'Banner Showcase', 'Browse all 10 pre-defined banners'), + (Icons.align_horizontal_left, 'Arrangements', 'Explore all 7 arrangement strategies'), + (Icons.auto_awesome, 'Animations', 'Container and element animations'), + (Icons.house_outlined, 'Home Screen', 'Example home screen layout'), + (Icons.link, 'Bridge Integration', 'Core SDK bridge demo with mock data'), + (Icons.pin_drop_outlined, 'Slot Demo', 'Mixed content feed with native display slots'), + ]; + + Widget _destinationFor(String title) { + return switch (title) { + 'Banner Showcase' => const _FullScreenShell(title: 'Banner Showcase', child: BannerShowcaseScreen()), + 'Arrangements' => const ArrangementDemoScreen(), + 'Animations' => const AnimationDemoScreen(), + 'Home Screen' => const _FullScreenShell(title: 'Home Screen', child: _HomeScreenShell()), + 'Bridge Integration' => const BridgeIntegrationScreen(), + 'Slot Demo' => const SlotDemoScreen(), + _ => const SizedBox.shrink(), + }; + } + + @override + Widget build(BuildContext context) { + return Container( + color: const Color(0xFFF5F5F5), + child: ListView.separated( + padding: const EdgeInsets.symmetric(vertical: 8), + itemCount: _items.length + 1, // +1 for section header + separatorBuilder: (_, __) => const Divider(height: 1, color: Color(0xFFF0F0F0)), + itemBuilder: (ctx, i) { + if (i == 0) { + return Padding( + padding: const EdgeInsets.fromLTRB(16, 12, 16, 4), + child: Text( + 'Developer Tools', + style: Theme.of(context) + .textTheme + .titleSmall + ?.copyWith(color: const Color(0xFF666666)), + ), + ); + } + final (icon, title, description) = _items[i - 1]; + return ListTile( + tileColor: Colors.white, + leading: Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primary.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(8), + ), + child: Icon(icon, color: Theme.of(context).colorScheme.primary), + ), + title: Text(title, style: const TextStyle(fontWeight: FontWeight.w500)), + subtitle: Text(description, + style: const TextStyle(fontSize: 12, color: Color(0xFF888888))), + trailing: const Icon(Icons.chevron_right, color: Color(0xFFAAAAAA)), + onTap: () { + Navigator.of(context).push( + MaterialPageRoute(builder: (_) => _destinationFor(title)), + ); + }, + ); + }, + ), + ); + } +} + +/// Wraps a widget without its own Scaffold in a Scaffold with an AppBar. +class _FullScreenShell extends StatelessWidget { + final String title; + final Widget child; + + const _FullScreenShell({required this.title, required this.child}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text(title)), + body: child, + ); + } +} + +class _HomeScreenShell extends StatelessWidget { + const _HomeScreenShell(); + + @override + Widget build(BuildContext context) => const HomeScreenDemo(); +} diff --git a/flutter-sample/lib/screens/other_demos_screen.dart b/flutter-sample/lib/screens/other_demos_screen.dart new file mode 100644 index 0000000..d4c38ea --- /dev/null +++ b/flutter-sample/lib/screens/other_demos_screen.dart @@ -0,0 +1,87 @@ +import 'package:flutter/material.dart'; +import 'package:clevertap_native_display/clevertap_native_display.dart'; + +import '../widgets/json_loader.dart'; +import '../widgets/error_widget.dart'; + +class OtherDemosScreen extends StatelessWidget { + const OtherDemosScreen({super.key}); + + @override + Widget build(BuildContext context) { + return DefaultTabController( + length: 5, + child: Column( + children: [ + const TabBar( + isScrollable: true, + tabAlignment: TabAlignment.start, + tabs: [ + Tab(text: 'Home Screen'), + Tab(text: 'Gallery'), + Tab(text: 'E-commerce'), + Tab(text: 'Social'), + Tab(text: 'Dashboard'), + ], + ), + const Expanded( + child: TabBarView( + children: [ + _JsonTab(assetPath: 'assets/configs/home_screen.json'), + _JsonTab(assetPath: 'assets/configs/gallery_three_modes.json'), + _JsonTab(assetPath: 'assets/configs/showcase_ecommerce_product.json'), + _JsonTab(assetPath: 'assets/configs/showcase_social_profile.json'), + _JsonTab(assetPath: 'assets/configs/showcase_dashboard.json'), + ], + ), + ), + ], + ), + ); + } +} + +class _JsonTab extends StatefulWidget { + final String assetPath; + + const _JsonTab({required this.assetPath}); + + @override + State<_JsonTab> createState() => _JsonTabState(); +} + +class _JsonTabState extends State<_JsonTab> with AutomaticKeepAliveClientMixin { + NativeDisplayConfig? _config; + String? _error; + bool _loading = true; + + @override + bool get wantKeepAlive => true; + + @override + void initState() { + super.initState(); + _load(); + } + + Future _load() async { + final config = await JsonLoader.loadFromAsset(widget.assetPath); + if (!mounted) return; + setState(() { + _config = config; + _error = config == null ? 'Failed to load ${widget.assetPath}' : null; + _loading = false; + }); + } + + @override + Widget build(BuildContext context) { + super.build(context); + if (_loading) return const Center(child: CircularProgressIndicator()); + if (_error != null) return Center(child: Padding(padding: const EdgeInsets.all(16), child: ErrorDisplay(message: _error!))); + return SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: NativeDisplayView(config: _config!), + ); + } +} diff --git a/flutter-sample/lib/screens/slot_demo_screen.dart b/flutter-sample/lib/screens/slot_demo_screen.dart new file mode 100644 index 0000000..63e60f7 --- /dev/null +++ b/flutter-sample/lib/screens/slot_demo_screen.dart @@ -0,0 +1,79 @@ +import 'package:flutter/material.dart'; + +import '../widgets/nd_demo_card.dart'; + +/// Slot Demo — shows how multiple display units can be placed in a mixed content feed. +class SlotDemoScreen extends StatelessWidget { + const SlotDemoScreen({super.key}); + + static const _slots = [ + ('Slot 1 — Banner', 'assets/banners/banner-01-hero-summer-sale.json'), + ('Slot 2 — Product', 'assets/banners/banner-02-product-iphone.json'), + ('Slot 3 — Promotion', 'assets/banners/banner-08-flash-sale.json'), + ]; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Slot Demo')), + body: ListView.builder( + padding: const EdgeInsets.all(16), + itemCount: _slots.length * 2 - 1, + itemBuilder: (ctx, i) { + // Interleave slot cards with mock feed items + if (i.isOdd) return _MockFeedItem(index: i ~/ 2 + 1); + final (title, assetPath) = _slots[i ~/ 2]; + return Padding( + padding: const EdgeInsets.only(bottom: 16), + child: Card( + elevation: 2, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + child: Padding( + padding: const EdgeInsets.all(12), + child: NdDemoCard(assetPath: assetPath, title: title), + ), + ), + ); + }, + ), + ); + } +} + +class _MockFeedItem extends StatelessWidget { + final int index; + + const _MockFeedItem({required this.index}); + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.only(bottom: 16), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: const Color(0xFFEEEEEE)), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + height: 12, + width: 120, + color: const Color(0xFFEEEEEE), + ), + const SizedBox(height: 8), + Container(height: 12, color: const Color(0xFFEEEEEE)), + const SizedBox(height: 6), + Container(height: 12, width: 200, color: const Color(0xFFEEEEEE)), + const SizedBox(height: 6), + Text( + 'Mock feed item #$index', + style: const TextStyle(color: Color(0xFF999999), fontSize: 11), + ), + ], + ), + ); + } +} diff --git a/flutter-sample/lib/screens/test_browser_screen.dart b/flutter-sample/lib/screens/test_browser_screen.dart new file mode 100644 index 0000000..8f04b5c --- /dev/null +++ b/flutter-sample/lib/screens/test_browser_screen.dart @@ -0,0 +1,347 @@ +import 'package:flutter/material.dart'; +import 'package:clevertap_native_display/clevertap_native_display.dart'; + +import '../widgets/json_loader.dart'; + +const _testFiles = [ + 'test-001-vertical-simple.json', + 'test-002-horizontal-simple.json', + 'test-003-box-simple.json', + 'test-004-stack-simple.json', + 'test-005-gallery-simple.json', + 'test-006-vertical-empty.json', + 'test-007-vertical-single-child.json', + 'test-008-vertical-3-children.json', + 'test-009-vertical-5-children.json', + 'test-010-vertical-10-children.json', + 'test-011-horizontal-empty.json', + 'test-012-horizontal-single-child.json', + 'test-013-horizontal-3-children.json', + 'test-014-horizontal-5-children.json', + 'test-015-horizontal-10-children.json', + 'test-016-box-empty.json', + 'test-017-box-single-child.json', + 'test-018-box-3-children.json', + 'test-019-box-5-children.json', + 'test-020-stack-empty.json', + 'test-021-stack-single-child.json', + 'test-022-stack-3-children.json', + 'test-023-stack-5-children.json', + 'test-024-gallery-empty.json', + 'test-025-gallery-single-child.json', + 'test-026-gallery-3-children-snapping.json', + 'test-027-gallery-5-children-snapping.json', + 'test-028-gallery-10-children-snapping.json', + 'test-029-gallery-3-children-free-flow.json', + 'test-030-gallery-3-children-free-flow-grid.json', + 'test-031-vertical-spaced.json', + 'test-032-vertical-space-between.json', + 'test-033-vertical-space-evenly.json', + 'test-034-vertical-space-around.json', + 'test-035-horizontal-start.json', + 'test-036-horizontal-center.json', + 'test-037-horizontal-end.json', + 'test-038-vertical-spacing-0.json', + 'test-039-vertical-spacing-8.json', + 'test-040-vertical-spacing-16.json', + 'test-041-vertical-spacing-32.json', + 'test-042-vertical-padding-uniform.json', + 'test-043-vertical-padding-individual.json', + 'test-044-horizontal-padding-asymmetric.json', + 'test-045-box-padding-large.json', + 'test-046-vertical-wrap-content.json', + 'test-047-horizontal-percent-width.json', + 'test-048-vertical-mixed-units.json', + 'test-049-nested-mixed-arrangements.json', + 'test-050-gallery-spacing-variations.json', + 'test-051-all-text-elements.json', + 'test-052-all-image-elements.json', + 'test-053-all-button-elements.json', + 'test-054-all-video-elements.json', + 'test-055-all-spacer-elements.json', + 'test-056-all-divider-elements.json', + 'test-057-product-card.json', + 'test-058-login-form.json', + 'test-059-profile-header.json', + 'test-060-media-player.json', + 'test-061-article-layout.json', + 'test-062-action-sheet.json', + 'test-063-stats-card.json', + 'test-064-gallery-item.json', + 'test-065-notification.json', + 'test-066-pricing-card.json', + 'test-067-hero-banner.json', + 'test-068-social-post.json', + 'test-069-settings-row.json', + 'test-070-feature-showcase.json', + 'test-071-text-colors.json', + 'test-072-font-sizes.json', + 'test-073-font-weights.json', + 'test-074-text-alignment.json', + 'test-075-text-decoration.json', + 'test-076-line-height.json', + 'test-077-font-families.json', + 'test-078-border-radius.json', + 'test-079-border-width-color.json', + 'test-080-shadows-light.json', + 'test-081-shadows-medium.json', + 'test-082-shadows-heavy.json', + 'test-083-opacity-variations.json', + 'test-084-combined-visual-styles.json', + 'test-085-text-style-inheritance.json', + 'test-086-style-class-usage.json', + 'test-087-inline-vs-inherited.json', + 'test-088-theme-default-styles.json', + 'test-089-styled-product-card.json', + 'test-090-styled-profile-card.json', + 'test-091-offset-percent-box-basic.json', + 'test-092-offset-percent-stack-layers.json', + 'test-093-offset-percent-negative.json', + 'test-094-offset-percent-overflow.json', + 'test-095-offset-percent-zero.json', + 'test-096-offset-percent-responsive.json', + 'test-097-offset-mixed-units.json', + 'test-098-offset-percent-nested.json', + 'test-099-offset-percent-with-padding.json', + 'test-100-offset-percent-gallery-peek.json', + 'test-101-aspect-ratio-square-fixed-width.json', + 'test-102-aspect-ratio-16-9-fixed-width.json', + 'test-103-aspect-ratio-4-3-fixed-width.json', + 'test-104-aspect-ratio-fixed-height.json', + 'test-105-aspect-ratio-percent-width.json', + 'test-106-aspect-ratio-wrap-content.json', + 'test-107-aspect-ratio-match-parent.json', + 'test-108-aspect-ratio-extreme-wide.json', + 'test-109-aspect-ratio-extreme-tall.json', + 'test-110-aspect-ratio-mixed-container.json', + 'test-111-combined-aspect-offset-box.json', + 'test-112-combined-nested-complex.json', + 'test-113-combined-gallery-aspect-peek.json', + 'test-114-combined-product-grid.json', + 'test-115-combined-showcase-all.json', + 'test-116-match-parent-comprehensive.json', + 'test-117-wrap-content-comprehensive.json', + 'test-118-mixed-special-dimensions.json', + 'test-119-match-parent-stack-box.json', + 'test-120-wrap-content-constraints.json', + 'test-121-16x9-ar-image-text-button.json', + 'test-122-1x1-ar-image-badge-rounded.json', + 'test-123-9x16-ar-video-caption.json', + 'test-124-4x3-ar-text-weights.json', + 'test-125-2x1-ar-image-split-button.json', + 'test-126-text-font-weights.json', + 'test-127-text-font-sizes.json', + 'test-128-text-alignment.json', + 'test-129-text-decoration-italic.json', + 'test-130-text-maxlines-overflow.json', + 'test-131-text-gradient.json', + 'test-132-image-fit-crop-contain.json', + 'test-133-image-gif-rounded.json', + 'test-134-image-border-radius.json', + 'test-135-images-z-order.json', + 'test-136-video-autoplay-muted.json', + 'test-137-video-with-controls.json', + 'test-138-9x16-video-button.json', + 'test-139-button-centered.json', + 'test-140-button-primary-secondary.json', + 'test-141-button-size-variants.json', + 'test-142-cta-card.json', + 'test-143-button-rounded-text.json', + 'test-144-rounded-box-text.json', + 'test-145-nested-rounded-boxes.json', + 'test-146-image-overlay-rounded.json', + 'test-147-hero-banner-complex.json', + 'test-148-product-card-complex.json', + 'test-149-notification-card.json', + 'test-150-dashboard-widget.json', + 'test-151-video-player-card.json', + 'test-152-text-corners.json', + 'test-153-image-clipped.json', + 'test-154-nested-box-deep.json', + 'test-155-all-element-types.json', + 'test-156-button-backgrounds.json', + 'test-157-gallery-box-freeflow-indicators-navbtns.json', + 'test-158-gallery-box-freeflow-indicators-only.json', + 'test-159-gallery-box-freeflow-navbtns-only.json', + 'test-160-gallery-box-freeflow-minimal.json', + 'test-161-gallery-box-freeflow-tall-images.json', + 'test-162-gallery-box-freeflow-video-items.json', + 'test-163-gallery-box-freeflow-button-items.json', + 'test-164-gallery-box-freeflow-5items.json', + 'test-165-gallery-box-grid2col-indicators-navbtns.json', + 'test-166-gallery-box-grid2col-indicators-only.json', + 'test-167-gallery-box-grid2col-navbtns-only.json', + 'test-168-gallery-box-grid2col-minimal.json', + 'test-169-gallery-box-grid3col-indicators.json', + 'test-170-gallery-box-grid3col-navbtns.json', + 'test-171-gallery-box-grid2col-video.json', + 'test-172-gallery-box-grid2col-vertical.json', + 'test-173-gallery-box-snapping-indicators-navbtns.json', + 'test-174-gallery-box-snapping-indicators-only.json', + 'test-175-gallery-box-snapping-navbtns-only.json', + 'test-176-gallery-box-snapping-minimal.json', + 'test-177-html-inline-basic.json', + 'test-178-html-with-javascript.json', + 'test-179-html-transparent-bg.json', + 'test-180-html-scrollable-content.json', + 'test-172-video-fullscreen-openurl.json', +]; + +class TestBrowserScreen extends StatefulWidget { + const TestBrowserScreen({super.key}); + + @override + State createState() => _TestBrowserScreenState(); +} + +class _TestBrowserScreenState extends State { + int _currentIndex = 0; + NativeDisplayConfig? _config; + bool _loading = false; + final ScrollController _chipScrollController = ScrollController(); + + @override + void initState() { + super.initState(); + _loadCurrent(); + } + + @override + void dispose() { + _chipScrollController.dispose(); + super.dispose(); + } + + Future _loadCurrent() async { + setState(() => _loading = true); + // Test configs are not bundled in flutter-sample; attempt to load from configs + // folder as a fallback — will show an error card if not found. + final path = 'assets/test-configs/${_testFiles[_currentIndex]}'; + final config = await JsonLoader.loadFromAsset(path); + if (!mounted) return; + setState(() { + _config = config; + _loading = false; + }); + } + + void _goTo(int index) { + setState(() => _currentIndex = index); + _loadCurrent(); + // Scroll chip strip to the selected chip + WidgetsBinding.instance.addPostFrameCallback((_) { + const chipWidth = 44.0; + final offset = (_currentIndex * chipWidth) - + (_chipScrollController.position.viewportDimension / 2 - chipWidth / 2); + _chipScrollController.animateTo( + offset.clamp(0, _chipScrollController.position.maxScrollExtent), + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + ); + }); + } + + @override + Widget build(BuildContext context) { + final filename = _testFiles[_currentIndex].replaceAll('.json', ''); + final counter = '${_currentIndex + 1}/${_testFiles.length}'; + + return Column( + children: [ + // Navigation row + ColoredBox( + color: Theme.of(context).colorScheme.surfaceContainerHighest, + child: Row( + children: [ + IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () => _goTo( + _currentIndex > 0 ? _currentIndex - 1 : _testFiles.length - 1, + ), + ), + Expanded( + child: Text( + '$filename ($counter)', + textAlign: TextAlign.center, + style: const TextStyle(fontSize: 12), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + IconButton( + icon: const Icon(Icons.arrow_forward), + onPressed: () => _goTo( + _currentIndex < _testFiles.length - 1 ? _currentIndex + 1 : 0, + ), + ), + ], + ), + ), + // Chip strip + SizedBox( + height: 44, + child: ListView.builder( + controller: _chipScrollController, + scrollDirection: Axis.horizontal, + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), + itemCount: _testFiles.length, + itemBuilder: (ctx, i) { + final selected = i == _currentIndex; + return GestureDetector( + onTap: () => _goTo(i), + child: Container( + width: 40, + margin: const EdgeInsets.only(right: 4), + decoration: BoxDecoration( + color: selected + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.surfaceContainerHighest, + borderRadius: BorderRadius.circular(4), + ), + alignment: Alignment.center, + child: Text( + '${i + 1}'.padLeft(3, '0'), + style: TextStyle( + fontSize: 10, + fontWeight: selected ? FontWeight.bold : FontWeight.normal, + color: selected + ? Theme.of(context).colorScheme.onPrimary + : Theme.of(context).colorScheme.onSurface, + ), + ), + ), + ); + }, + ), + ), + // Content + Expanded( + child: Container( + color: const Color(0xFFF5F5F5), + child: _loading + ? const Center(child: CircularProgressIndicator()) + : _config != null + ? SingleChildScrollView( + child: NativeDisplayView(config: _config!), + ) + : Center( + child: Container( + margin: const EdgeInsets.all(32), + padding: const EdgeInsets.all(24), + decoration: BoxDecoration( + color: const Color(0xFFFFEBEE), + borderRadius: BorderRadius.circular(12), + ), + child: Text( + 'Could not load ${_testFiles[_currentIndex]}\n\nTest configs are not bundled with the sample app. This browser is available for development use only.', + style: const TextStyle(color: Color(0xFFC62828)), + textAlign: TextAlign.center, + ), + ), + ), + ), + ), + ], + ); + } +} diff --git a/flutter-sample/lib/widgets/error_widget.dart b/flutter-sample/lib/widgets/error_widget.dart new file mode 100644 index 0000000..6929f32 --- /dev/null +++ b/flutter-sample/lib/widgets/error_widget.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; + +class ErrorDisplay extends StatelessWidget { + final String message; + final VoidCallback? onRetry; + + const ErrorDisplay({super.key, required this.message, this.onRetry}); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(24), + decoration: BoxDecoration( + color: const Color(0xFFFFEBEE), + borderRadius: BorderRadius.circular(12), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.error_outline, color: Color(0xFFC62828), size: 48), + const SizedBox(height: 12), + Text( + 'Error', + style: Theme.of(context) + .textTheme + .titleMedium + ?.copyWith(color: const Color(0xFFC62828), fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + Text( + message, + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(color: const Color(0xFFC62828)), + textAlign: TextAlign.center, + ), + if (onRetry != null) ...[ + const SizedBox(height: 16), + TextButton(onPressed: onRetry, child: const Text('Retry')), + ], + ], + ), + ); + } +} diff --git a/flutter-sample/lib/widgets/json_loader.dart b/flutter-sample/lib/widgets/json_loader.dart new file mode 100644 index 0000000..b934aaa --- /dev/null +++ b/flutter-sample/lib/widgets/json_loader.dart @@ -0,0 +1,27 @@ +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:clevertap_native_display/clevertap_native_display.dart'; + +class JsonLoader { + static Future loadFromAsset(String assetPath) async { + try { + final jsonStr = await rootBundle.loadString(assetPath); + final json = jsonDecode(jsonStr) as Map; + return NativeDisplayConfig.fromJson(json); + } catch (e) { + debugPrint('[JsonLoader] Failed to load $assetPath: $e'); + return null; + } + } + + static Future loadStringFromAsset(String assetPath) async { + try { + return await rootBundle.loadString(assetPath); + } catch (e) { + debugPrint('[JsonLoader] Failed to load string from $assetPath: $e'); + return null; + } + } +} diff --git a/flutter-sample/lib/widgets/nd_demo_card.dart b/flutter-sample/lib/widgets/nd_demo_card.dart new file mode 100644 index 0000000..35434e3 --- /dev/null +++ b/flutter-sample/lib/widgets/nd_demo_card.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:clevertap_native_display/clevertap_native_display.dart'; + +import 'json_loader.dart'; +import 'error_widget.dart'; +import '../screens/json_viewer_screen.dart'; + +class NdDemoCard extends StatelessWidget { + final String assetPath; + final String title; + + const NdDemoCard({super.key, required this.assetPath, required this.title}); + + void _showJsonViewer(BuildContext context, String assetPath) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => JsonViewerScreen(assetPath: assetPath), + ), + ); + } + + @override + Widget build(BuildContext context) { + return FutureBuilder( + future: JsonLoader.loadFromAsset(assetPath), + builder: (ctx, snap) { + if (snap.connectionState == ConnectionState.waiting) { + return const SizedBox( + height: 100, + child: Center(child: CircularProgressIndicator()), + ); + } + if (snap.data == null) { + return ErrorDisplay(message: 'Failed to load $assetPath'); + } + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Text( + title, + style: Theme.of(context).textTheme.titleMedium, + overflow: TextOverflow.ellipsis, + ), + ), + IconButton( + icon: const Icon(Icons.code), + tooltip: 'View JSON', + onPressed: () => _showJsonViewer(context, assetPath), + ), + ], + ), + NativeDisplayView(config: snap.data!), + ], + ); + }, + ); + } +} diff --git a/flutter-sample/pubspec.lock b/flutter-sample/pubspec.lock new file mode 100644 index 0000000..ea3fb96 --- /dev/null +++ b/flutter-sample/pubspec.lock @@ -0,0 +1,609 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 + url: "https://pub.dev" + source: hosted + version: "2.12.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + cached_network_image: + dependency: transitive + description: + name: cached_network_image + sha256: "7c1183e361e5c8b0a0f21a28401eecdbde252441106a9816400dd4c2b2424916" + url: "https://pub.dev" + source: hosted + version: "3.4.1" + cached_network_image_platform_interface: + dependency: transitive + description: + name: cached_network_image_platform_interface + sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829" + url: "https://pub.dev" + source: hosted + version: "4.1.1" + cached_network_image_web: + dependency: transitive + description: + name: cached_network_image_web + sha256: "980842f4e8e2535b8dbd3d5ca0b1f0ba66bf61d14cc3a17a9b4788a3685ba062" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + characters: + dependency: transitive + description: + name: characters + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + clevertap_native_display: + dependency: "direct main" + description: + path: "../flutter" + relative: true + source: path + version: "0.1.0" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + crypto: + dependency: transitive + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.dev" + source: hosted + version: "3.0.7" + csslib: + dependency: transitive + description: + name: csslib + sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" + url: "https://pub.dev" + source: hosted + version: "1.3.2" + ffi: + dependency: transitive + description: + name: ffi + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_cache_manager: + dependency: transitive + description: + name: flutter_cache_manager + sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386" + url: "https://pub.dev" + source: hosted + version: "3.4.1" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + html: + dependency: transitive + description: + name: html + sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" + url: "https://pub.dev" + source: hosted + version: "0.15.6" + http: + dependency: transitive + description: + name: http + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.dev" + source: hosted + version: "1.6.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec + url: "https://pub.dev" + source: hosted + version: "10.0.8" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + url: "https://pub.dev" + source: hosted + version: "3.0.9" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + lints: + dependency: transitive + description: + name: lints + sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + url: "https://pub.dev" + source: hosted + version: "0.12.17" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + url: "https://pub.dev" + source: hosted + version: "1.16.0" + octo_image: + dependency: transitive + description: + name: octo_image + sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "3b4c1fc3aa55ddc9cd4aa6759984330d5c8e66aa7702a6223c61540dc6380c37" + url: "https://pub.dev" + source: hosted + version: "2.2.19" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "16eef174aacb07e09c351502740fa6254c165757638eba1e9116b0a781201bbd" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + rxdart: + dependency: transitive + description: + name: rxdart + sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" + url: "https://pub.dev" + source: hosted + version: "0.28.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + url: "https://pub.dev" + source: hosted + version: "1.10.1" + sqflite: + dependency: transitive + description: + name: sqflite + sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03 + url: "https://pub.dev" + source: hosted + version: "2.4.2" + sqflite_android: + dependency: transitive + description: + name: sqflite_android + sha256: "2b3070c5fa881839f8b402ee4a39c1b4d561704d4ebbbcfb808a119bc2a1701b" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: "84731e8bfd8303a3389903e01fb2141b6e59b5973cacbb0929021df08dddbe8b" + url: "https://pub.dev" + source: hosted + version: "2.5.5" + sqflite_darwin: + dependency: transitive + description: + name: sqflite_darwin + sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + sqflite_platform_interface: + dependency: transitive + description: + name: sqflite_platform_interface + sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" + url: "https://pub.dev" + source: hosted + version: "2.4.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: "0669c70faae6270521ee4f05bffd2919892d42d1276e6c495be80174b6bc0ef6" + url: "https://pub.dev" + source: hosted + version: "3.3.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + url: "https://pub.dev" + source: hosted + version: "0.7.4" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + url_launcher: + dependency: transitive + description: + name: url_launcher + sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 + url: "https://pub.dev" + source: hosted + version: "6.3.2" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "81777b08c498a292d93ff2feead633174c386291e35612f8da438d6e92c4447e" + url: "https://pub.dev" + source: hosted + version: "6.3.20" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: d80b3f567a617cb923546034cc94bfe44eb15f989fe670b37f26abdb9d939cb7 + url: "https://pub.dev" + source: hosted + version: "6.3.4" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: c043a77d6600ac9c38300567f33ef12b0ef4f4783a2c1f00231d2b1941fea13f + url: "https://pub.dev" + source: hosted + version: "3.2.3" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" + url: "https://pub.dev" + source: hosted + version: "3.1.4" + uuid: + dependency: transitive + description: + name: uuid + sha256: "1fef9e8e11e2991bb773070d4656b7bd5d850967a2456cfc83cf47925ba79489" + url: "https://pub.dev" + source: hosted + version: "4.5.3" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + video_player: + dependency: transitive + description: + name: video_player + sha256: "096bc28ce10d131be80dfb00c223024eb0fba301315a406728ab43dd99c45bdf" + url: "https://pub.dev" + source: hosted + version: "2.10.1" + video_player_android: + dependency: transitive + description: + name: video_player_android + sha256: a8dc4324f67705de057678372bedb66cd08572fe7c495605ac68c5f503324a39 + url: "https://pub.dev" + source: hosted + version: "2.8.15" + video_player_avfoundation: + dependency: transitive + description: + name: video_player_avfoundation + sha256: f9a780aac57802b2892f93787e5ea53b5f43cc57dc107bee9436458365be71cd + url: "https://pub.dev" + source: hosted + version: "2.8.4" + video_player_platform_interface: + dependency: transitive + description: + name: video_player_platform_interface + sha256: "57c5d73173f76d801129d0531c2774052c5a7c11ccb962f1830630decd9f24ec" + url: "https://pub.dev" + source: hosted + version: "6.6.0" + video_player_web: + dependency: transitive + description: + name: video_player_web + sha256: "9f3c00be2ef9b76a95d94ac5119fb843dca6f2c69e6c9968f6f2b6c9e7afbdeb" + url: "https://pub.dev" + source: hosted + version: "2.4.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" + url: "https://pub.dev" + source: hosted + version: "14.3.1" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + webview_flutter: + dependency: transitive + description: + name: webview_flutter + sha256: c3e4fe614b1c814950ad07186007eff2f2e5dd2935eba7b9a9a1af8e5885f1ba + url: "https://pub.dev" + source: hosted + version: "4.13.0" + webview_flutter_android: + dependency: transitive + description: + name: webview_flutter_android + sha256: "9a25f6b4313978ba1c2cda03a242eea17848174912cfb4d2d8ee84a556f248e3" + url: "https://pub.dev" + source: hosted + version: "4.10.1" + webview_flutter_platform_interface: + dependency: transitive + description: + name: webview_flutter_platform_interface + sha256: "63d26ee3aca7256a83ccb576a50272edd7cfc80573a4305caa98985feb493ee0" + url: "https://pub.dev" + source: hosted + version: "2.14.0" + webview_flutter_wkwebview: + dependency: transitive + description: + name: webview_flutter_wkwebview + sha256: fb46db8216131a3e55bcf44040ca808423539bc6732e7ed34fb6d8044e3d512f + url: "https://pub.dev" + source: hosted + version: "3.23.0" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" +sdks: + dart: ">=3.7.0 <4.0.0" + flutter: ">=3.29.0" diff --git a/flutter-sample/pubspec.yaml b/flutter-sample/pubspec.yaml new file mode 100644 index 0000000..5c281d3 --- /dev/null +++ b/flutter-sample/pubspec.yaml @@ -0,0 +1,25 @@ +name: clevertap_native_display_sample +description: Sample app demonstrating the CleverTap Native Display Flutter plugin. +version: 1.0.0+1 +publish_to: none + +environment: + sdk: ">=3.0.0 <4.0.0" + flutter: ">=3.10.0" + +dependencies: + flutter: + sdk: flutter + clevertap_native_display: + path: ../flutter + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^4.0.0 + +flutter: + uses-material-design: true + assets: + - assets/banners/ + - assets/configs/ From f7583b739d1d7a75baadccfa396731fc9554d5ab Mon Sep 17 00:00:00 2001 From: CTLalit <144685420+CTLalit@users.noreply.github.com> Date: Tue, 26 May 2026 12:15:23 +0530 Subject: [PATCH 06/12] SDK-5835: Wire CleverTap Core SDK integration + add native projects (Flutter) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Android (flutter-sample/android/): - SampleApplication: init CleverTap, bind NativeDisplayBridge, register listener - MainActivity: EventChannel (native→Dart units_updated), MethodChannel (pushEvent) - AndroidManifest: CT account ID/token/region meta-data, INTERNET permission - build.gradle.kts: CT core SDK 8.0.0 + native-display-sdk dependencies iOS (flutter-sample/ios/): - AppDelegate: CleverTap.autoIntegrate(), NativeDisplayBridge bind + fetch - Podfile: CleverTap-iOS-SDK ~>7.0, CleverTapNativeDisplay :path Dart (flutter-sample/lib/): - CleverTapIntegrationScreen: subscribe NativeDisplayBridge.eventStream, parse units_updated events, render via NativeDisplayView, Send Event → pushEvent - app.dart: bottom nav Events/Slots/Browser/More matching Android structure - SlotDemoScreen: 15 feed items + 4 dashed-border slot placeholders - Lint fixes: prefer_const_constructors / prefer_const_literals flutter/ plugin: - android/build.gradle + AndroidManifest.xml: plugin Android library scaffold - NativeDisplayBridge: pushEvent (sample MethodChannel) + eventStream (EventChannel) - gallery_renderer.dart: LayoutBuilder fallback height for unbounded PageView Co-Authored-By: Claude Sonnet 4.6 --- .claude/agents/flutter-sample.md | 224 +++++++ .../knowledge/sample-architecture.md | 144 ++++ .claude/agents/flutter-sdk.md | 374 +++++++++++ .../examples/native_display_view.dart | 142 ++++ .../flutter-sdk/knowledge/architecture.md | 198 ++++++ .../flutter-sdk/knowledge/performance.md | 204 ++++++ .../knowledge/platform-channels.md | 286 ++++++++ .../knowledge/rendering-pipeline.md | 329 ++++++++++ flutter-sample/.gitignore | 45 ++ flutter-sample/analysis_options.yaml | 28 + flutter-sample/android/.gitignore | 14 + flutter-sample/android/app/build.gradle.kts | 51 ++ .../android/app/src/debug/AndroidManifest.xml | 7 + .../android/app/src/main/AndroidManifest.xml | 51 ++ .../MainActivity.kt | 63 ++ .../SampleApplication.kt | 65 ++ .../res/drawable-v21/launch_background.xml | 12 + .../main/res/drawable/launch_background.xml | 12 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 544 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 442 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 721 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 1031 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 1443 bytes .../app/src/main/res/values-night/styles.xml | 18 + .../app/src/main/res/values/styles.xml | 18 + .../app/src/profile/AndroidManifest.xml | 7 + flutter-sample/android/build.gradle.kts | 21 + flutter-sample/android/gradle.properties | 3 + .../gradle/wrapper/gradle-wrapper.properties | 5 + flutter-sample/android/settings.gradle.kts | 32 + flutter-sample/ios/.gitignore | 34 + .../ios/Flutter/AppFrameworkInfo.plist | 26 + flutter-sample/ios/Flutter/Debug.xcconfig | 2 + flutter-sample/ios/Flutter/Release.xcconfig | 2 + flutter-sample/ios/Podfile | 49 ++ .../ios/Runner.xcodeproj/project.pbxproj | 619 ++++++++++++++++++ .../xcshareddata/xcschemes/Runner.xcscheme | 99 +++ flutter-sample/ios/Runner/AppDelegate.swift | 29 + .../AppIcon.appiconset/Contents.json | 122 ++++ .../Icon-App-1024x1024@1x.png | Bin 0 -> 10932 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 0 -> 295 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 0 -> 406 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 0 -> 450 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 0 -> 282 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 0 -> 462 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 0 -> 704 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 0 -> 406 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 0 -> 586 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 0 -> 862 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 0 -> 862 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 0 -> 1674 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 0 -> 762 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 0 -> 1226 bytes .../Icon-App-83.5x83.5@2x.png | Bin 0 -> 1418 bytes .../LaunchImage.imageset/Contents.json | 23 + .../LaunchImage.imageset/LaunchImage.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@2x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@3x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/README.md | 5 + .../Runner/Base.lproj/LaunchScreen.storyboard | 37 ++ .../ios/Runner/Base.lproj/Main.storyboard | 26 + flutter-sample/ios/Runner/Info.plist | 49 ++ .../ios/Runner/Runner-Bridging-Header.h | 1 + .../ios/RunnerTests/RunnerTests.swift | 12 + flutter-sample/lib/app.dart | 22 +- .../screens/bridge_integration_screen.dart | 8 +- .../screens/clevertap_integration_screen.dart | 294 +++++++++ .../lib/screens/more_menu_screen.dart | 10 +- .../lib/screens/other_demos_screen.dart | 6 +- .../lib/screens/slot_demo_screen.dart | 273 ++++++-- flutter-sample/test/widget_test.dart | 14 + flutter/android/build.gradle | 48 ++ flutter/android/src/main/AndroidManifest.xml | 1 + .../lib/src/bridge/native_display_bridge.dart | 21 + .../renderer/containers/gallery_renderer.dart | 59 +- 75 files changed, 4151 insertions(+), 93 deletions(-) create mode 100644 .claude/agents/flutter-sample.md create mode 100644 .claude/agents/flutter-sample/knowledge/sample-architecture.md create mode 100644 .claude/agents/flutter-sdk.md create mode 100644 .claude/agents/flutter-sdk/examples/native_display_view.dart create mode 100644 .claude/agents/flutter-sdk/knowledge/architecture.md create mode 100644 .claude/agents/flutter-sdk/knowledge/performance.md create mode 100644 .claude/agents/flutter-sdk/knowledge/platform-channels.md create mode 100644 .claude/agents/flutter-sdk/knowledge/rendering-pipeline.md create mode 100644 flutter-sample/.gitignore create mode 100644 flutter-sample/analysis_options.yaml create mode 100644 flutter-sample/android/.gitignore create mode 100644 flutter-sample/android/app/build.gradle.kts create mode 100644 flutter-sample/android/app/src/debug/AndroidManifest.xml create mode 100644 flutter-sample/android/app/src/main/AndroidManifest.xml create mode 100644 flutter-sample/android/app/src/main/kotlin/com/clevertap/flutter/clevertap_native_display_sample/MainActivity.kt create mode 100644 flutter-sample/android/app/src/main/kotlin/com/clevertap/flutter/clevertap_native_display_sample/SampleApplication.kt create mode 100644 flutter-sample/android/app/src/main/res/drawable-v21/launch_background.xml create mode 100644 flutter-sample/android/app/src/main/res/drawable/launch_background.xml create mode 100644 flutter-sample/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 flutter-sample/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 flutter-sample/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 flutter-sample/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 flutter-sample/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 flutter-sample/android/app/src/main/res/values-night/styles.xml create mode 100644 flutter-sample/android/app/src/main/res/values/styles.xml create mode 100644 flutter-sample/android/app/src/profile/AndroidManifest.xml create mode 100644 flutter-sample/android/build.gradle.kts create mode 100644 flutter-sample/android/gradle.properties create mode 100644 flutter-sample/android/gradle/wrapper/gradle-wrapper.properties create mode 100644 flutter-sample/android/settings.gradle.kts create mode 100644 flutter-sample/ios/.gitignore create mode 100644 flutter-sample/ios/Flutter/AppFrameworkInfo.plist create mode 100644 flutter-sample/ios/Flutter/Debug.xcconfig create mode 100644 flutter-sample/ios/Flutter/Release.xcconfig create mode 100644 flutter-sample/ios/Podfile create mode 100644 flutter-sample/ios/Runner.xcodeproj/project.pbxproj create mode 100644 flutter-sample/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme create mode 100644 flutter-sample/ios/Runner/AppDelegate.swift create mode 100644 flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png create mode 100644 flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png create mode 100644 flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png create mode 100644 flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png create mode 100644 flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png create mode 100644 flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png create mode 100644 flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png create mode 100644 flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png create mode 100644 flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png create mode 100644 flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png create mode 100644 flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png create mode 100644 flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png create mode 100644 flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png create mode 100644 flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png create mode 100644 flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png create mode 100644 flutter-sample/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json create mode 100644 flutter-sample/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png create mode 100644 flutter-sample/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png create mode 100644 flutter-sample/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png create mode 100644 flutter-sample/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md create mode 100644 flutter-sample/ios/Runner/Base.lproj/LaunchScreen.storyboard create mode 100644 flutter-sample/ios/Runner/Base.lproj/Main.storyboard create mode 100644 flutter-sample/ios/Runner/Info.plist create mode 100644 flutter-sample/ios/Runner/Runner-Bridging-Header.h create mode 100644 flutter-sample/ios/RunnerTests/RunnerTests.swift create mode 100644 flutter-sample/lib/screens/clevertap_integration_screen.dart create mode 100644 flutter-sample/test/widget_test.dart create mode 100644 flutter/android/build.gradle create mode 100644 flutter/android/src/main/AndroidManifest.xml diff --git a/.claude/agents/flutter-sample.md b/.claude/agents/flutter-sample.md new file mode 100644 index 0000000..7e29b0a --- /dev/null +++ b/.claude/agents/flutter-sample.md @@ -0,0 +1,224 @@ +--- +name: flutter-sample +description: Specializes in creating and maintaining the Flutter sample application that demonstrates the Native Display SDK. Use this agent when creating new Flutter demo screens, updating the Flutter sample app, integrating new SDK features into the Flutter sample, ensuring visual parity with Android and iOS demos, or adding Flutter-specific documentation. +--- + +# Flutter Sample Agent + +You are the **Flutter Sample Agent**, specializing in creating and maintaining the Flutter sample application that demonstrates the Native Display SDK. + +**Your scope**: `flutter-sample/` — the Flutter demo app. + +## Knowledge Reference + +The system prompt below covers the patterns you need for most tasks. Reach for these when you need more detail: + +- **Sample app architecture & navigation patterns** → `.claude/agents/flutter-sample/knowledge/sample-architecture.md` +- **All SDK component capabilities** → `.claude/reference/COMPONENTS_GUIDE.md` + +## Your Expertise + +- Flutter sample app development (Dart/Flutter) +- SDK integration patterns for Flutter +- Demo UI/UX following Material Design 3 +- Flutter navigation (GoRouter or Navigator 2.0) +- Hot reload and hot restart for rapid iteration +- Cross-platform visual parity with Android and iOS samples + +## File Structure + +``` +flutter-sample/ +├── lib/ +│ ├── main.dart # App entry point +│ ├── navigation/ # Route definitions +│ ├── screens/ +│ │ ├── home_screen.dart # Demo gallery grid +│ │ ├── containers_screen.dart +│ │ ├── elements_screen.dart +│ │ ├── styles_screen.dart +│ │ └── [feature]_screen.dart +│ └── widgets/ +│ └── demo_card.dart # Reusable demo tile +├── assets/ +│ └── configs/ # JSON configurations (shared format with Android/iOS) +│ ├── product_card.json +│ ├── login_form.json +│ └── gallery_demo.json +└── pubspec.yaml +``` + +## Sample App Navigation Structure + +``` +MaterialApp (GoRouter) +└── HomeScreen (grid of demo cards) + └── NavigationBar → { + ContainersScreen, + ElementsScreen, + StylesScreen, + [Feature]Screen + } +``` + +## Demo Patterns + +### Simple Demo (load JSON from assets) +```dart +class ProductCardDemo extends StatefulWidget { + const ProductCardDemo({super.key}); + @override + State createState() => _ProductCardDemoState(); +} + +class _ProductCardDemoState extends State { + NativeDisplayConfig? _config; + + @override + void initState() { + super.initState(); + _loadConfig(); + } + + Future _loadConfig() async { + final json = await rootBundle.loadString('assets/configs/product_card.json'); + setState(() { + _config = NativeDisplayConfig.fromJson(jsonDecode(json)); + }); + } + + @override + Widget build(BuildContext context) { + if (_config == null) return const CircularProgressIndicator(); + return NativeDisplayView(config: _config!); + } +} +``` + +### Interactive Demo (mutable variables) +```dart +class InteractiveDemo extends StatefulWidget { + const InteractiveDemo({super.key}); + @override + State createState() => _InteractiveDemoState(); +} + +class _InteractiveDemoState extends State { + int _count = 0; + + @override + Widget build(BuildContext context) { + final config = NativeDisplayConfig( + variables: {'count': _count, 'label': 'Items in cart'}, + root: /* ... */, + ); + + return Column( + children: [ + NativeDisplayView(config: config), + ElevatedButton( + onPressed: () => setState(() => _count++), + child: const Text('Increment'), + ), + ], + ); + } +} +``` + +### Demo Card (home screen tile) +```dart +class DemoCard extends StatelessWidget { + final String title; + final String description; + final String routePath; + final IconData icon; + + const DemoCard({ + super.key, + required this.title, + required this.description, + required this.routePath, + required this.icon, + }); + + @override + Widget build(BuildContext context) { + return Card( + child: InkWell( + onTap: () => context.go(routePath), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon(icon, size: 32), + const SizedBox(height: 8), + Text(title, style: Theme.of(context).textTheme.titleMedium), + Text(description, style: Theme.of(context).textTheme.bodySmall), + ], + ), + ), + ), + ); + } +} +``` + +## Flutter-Specific Considerations + +- Use `const` constructors wherever possible — helps Flutter skip unnecessary rebuilds +- Use `GoRouter` for declarative navigation with deep-link support +- Follow Material Design 3 (`useMaterial3: true` in ThemeData) +- Support dark mode: use `Theme.of(context).colorScheme` not hardcoded colors +- Use `rootBundle.loadString()` for assets — always `await`, never block +- Always handle JSON parsing errors gracefully (try/catch, show error widget) +- Use `SafeArea` to respect system UI insets +- Add a "View JSON" button using `showModalBottomSheet` so users can inspect configs + +## When to Diverge from Android/iOS + +Flutter demos should feel native to Flutter/Material — some differences are intentional: +- **Navigation**: bottom NavigationBar (Flutter) vs bottom nav (Android) vs TabBar (iOS) +- **Theming**: Material 3 tokens (Flutter) vs Material You (Android) vs HIG (iOS) +- **Typography**: uses Roboto/system font — specify in JSON if cross-platform font parity matters + +Always document intentional divergences with a comment. + +## Workflow for New Demos + +1. Check if Android or iOS version exists → match design, adapt for Flutter/Material idioms +2. Confirm JSON asset is shared with Android/iOS (same file in all three sample apps) +3. Generate JSON using `/generate-json` if needed +4. Save JSON to `assets/configs/` and declare in `pubspec.yaml` +5. Implement demo screen in `lib/screens/` +6. Register route in navigation setup +7. Add `DemoCard` tile to the appropriate list screen +8. Add `"View JSON"` button to the demo screen +9. Test hot-reload works; test on both Android emulator and iOS simulator +10. Update README +11. `/build flutter` to verify, `/test flutter` to validate +12. `/review` before committing + +## Best Practices + +- Each demo in a separate file under `lib/screens/` +- Load JSON from `assets/` — never hardcode JSON strings +- Handle loading and error states in every demo +- Support both light and dark mode +- Test on multiple screen sizes (phone, tablet, foldable) +- Use `flutter_lints` and keep analysis clean +- Hot reload is your friend — design demos to be iterable without full restart + +## What You Do NOT Do + +- Modify SDK code → delegate to `flutter-sdk` agent +- Create Android/iOS samples → delegate to `android-sample` / `ios-sample` agent +- Generate test JSON directly → use `/generate-json` skill +- Make SDK architectural decisions + +## Collaboration + +- Get notified of SDK breaking changes from `flutter-sdk` agent before updating samples +- Use `testing` agent's generated JSON configs as demo starting points +- Coordinate with `android-sample` and `ios-sample` agents so all platforms have matching demos diff --git a/.claude/agents/flutter-sample/knowledge/sample-architecture.md b/.claude/agents/flutter-sample/knowledge/sample-architecture.md new file mode 100644 index 0000000..d7f2117 --- /dev/null +++ b/.claude/agents/flutter-sample/knowledge/sample-architecture.md @@ -0,0 +1,144 @@ +# Flutter Sample App Architecture + +## Overview + +The Flutter sample app (`flutter-sample/`) demonstrates the Native Display SDK with real JSON-driven UI examples. It is a standalone Flutter application, separate from the plugin package itself. + +## Navigation Architecture + +``` +MaterialApp (GoRouter) +└── ShellRoute (NavigationBar) + ├── /home → HomeScreen (demo grid) + ├── /containers → ContainersScreen + │ ├── /containers/vertical + │ ├── /containers/horizontal + │ ├── /containers/box + │ └── /containers/gallery + ├── /elements → ElementsScreen + │ ├── /elements/text + │ ├── /elements/image + │ ├── /elements/button + │ └── /elements/video + └── /styles → StylesScreen + ├── /styles/theme + ├── /styles/backgrounds + └── /styles/typography +``` + +## File Structure + +``` +flutter-sample/ +├── lib/ +│ ├── main.dart +│ ├── app.dart # MaterialApp + GoRouter setup +│ ├── navigation/ +│ │ └── app_router.dart # Route definitions +│ ├── screens/ +│ │ ├── home_screen.dart # Grid of DemoCards +│ │ ├── containers/ +│ │ │ ├── containers_screen.dart # List of container demos +│ │ │ ├── vertical_demo.dart +│ │ │ ├── horizontal_demo.dart +│ │ │ ├── box_demo.dart +│ │ │ └── gallery_demo.dart +│ │ ├── elements/ +│ │ │ └── [element]_demo.dart +│ │ └── styles/ +│ │ └── [style]_demo.dart +│ └── widgets/ +│ ├── demo_card.dart # Tile on HomeScreen +│ ├── demo_scaffold.dart # Scaffold with "View JSON" button +│ └── json_viewer.dart # Bottom sheet JSON viewer +├── assets/ +│ └── configs/ +│ ├── product_card.json +│ ├── gallery_carousel.json +│ └── ... +└── pubspec.yaml +``` + +## DemoScaffold — Standard Demo Wrapper + +Every demo screen uses `DemoScaffold` to provide consistent chrome and the "View JSON" button: + +```dart +class DemoScaffold extends StatelessWidget { + final String title; + final String assetPath; // e.g. 'assets/configs/product_card.json' + final Widget? child; // pre-built NativeDisplayView + + const DemoScaffold({super.key, required this.title, required this.assetPath, this.child}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(title), + actions: [ + IconButton( + icon: const Icon(Icons.code), + tooltip: 'View JSON', + onPressed: () => _showJson(context), + ), + ], + ), + body: child, + ); + } + + void _showJson(BuildContext context) async { + final json = await rootBundle.loadString(assetPath); + if (!context.mounted) return; + showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: (_) => JsonViewer(json: json), + ); + } +} +``` + +## Asset Registration + +All JSON files must be declared in `pubspec.yaml`: + +```yaml +flutter: + assets: + - assets/configs/ +``` + +Using a directory glob (`assets/configs/`) includes all files in the directory automatically. + +## GoRouter Setup + +```dart +final router = GoRouter( + routes: [ + ShellRoute( + builder: (context, state, child) => AppShell(child: child), + routes: [ + GoRoute(path: '/', builder: (_, __) => const HomeScreen()), + GoRoute( + path: '/containers', + builder: (_, __) => const ContainersScreen(), + routes: [ + GoRoute(path: 'gallery', builder: (_, __) => const GalleryDemo()), + GoRoute(path: 'vertical', builder: (_, __) => const VerticalDemo()), + ], + ), + // ... + ], + ), + ], +); +``` + +## Shared JSON Assets + +JSON config files in `assets/configs/` should be identical to those in `android-sample/` and `ios-sample/`. When adding a new demo: +1. Create the JSON once +2. Copy to all three sample app asset directories +3. They render the same JSON — visual differences are expected (font rendering, shadows differ by platform) diff --git a/.claude/agents/flutter-sdk.md b/.claude/agents/flutter-sdk.md new file mode 100644 index 0000000..65819d1 --- /dev/null +++ b/.claude/agents/flutter-sdk.md @@ -0,0 +1,374 @@ +--- +name: flutter-sdk +description: Specialized AI assistant with deep expertise in the Native Display SDK's Flutter plugin implementation using Dart and Flutter widgets. Use this agent when implementing Flutter SDK features, building the Dart renderer, integrating with the CleverTap Flutter plugin via platform channels, ensuring cross-platform parity with Android/iOS, writing Dart unit/widget tests, or reviewing Flutter code quality. +--- + +# Flutter SDK Agent + +You are the **Flutter SDK Agent**, a specialist in the Native Display SDK's Flutter plugin implementation. + +**Your scope**: `flutter/` — the Dart/Flutter plugin package for the Native Display SDK. + +## CRITICAL: SDK Usage Model + +The Native Display SDK is **JSON-driven**. Flutter client usage is exactly 3 steps: +1. Load JSON configuration (from CleverTap callback or local string) +2. Parse: `NativeDisplayConfig.fromJson(jsonDecode(jsonString))` +3. Render: `NativeDisplayView(config: config)` + +The Flutter plugin is a **pure Dart renderer** — it parses JSON in Dart and renders using Flutter widgets. No platform views are needed for rendering. Platform channels are used only for the CleverTap Core SDK bridge (receiving display unit data). + +## Knowledge Reference + +The system prompt below covers the rules you need for most tasks. Read these on-demand — not upfront: + +- **Plugin architecture & data flow** → `.claude/agents/flutter-sdk/knowledge/architecture.md` +- **Dart widget rendering patterns** → `.claude/agents/flutter-sdk/knowledge/rendering-pipeline.md` +- **Performance optimisation** → `.claude/agents/flutter-sdk/knowledge/performance.md` +- **Platform channel bridge (CleverTap integration)** → `.claude/agents/flutter-sdk/knowledge/platform-channels.md` +- **Concrete code examples** → `.claude/agents/flutter-sdk/examples/` +- **Primary SDK spec** → `.claude/reference/CLAUDE_CODE_REFERENCE_ACTUAL.md` +- **Android parity reference** → read `android-sdk/knowledge/` when matching behaviour +- **iOS parity reference** → read `ios-sdk/knowledge/` when matching behaviour + +## Your Expertise + +- Flutter widget system (Widget/Element/RenderObject three-tree model) +- Dart 3.x with null safety, records, patterns +- `dart:convert` JSON parsing and custom `fromJson`/`toJson` +- Flutter rendering pipeline: build → layout → paint → composite +- Performance: `const` widgets, `RepaintBoundary`, lazy builders, `InheritedWidget` +- Federated Flutter plugin architecture (`flutter_plugin_tools`) +- Platform channels (MethodChannel, EventChannel) for Android/iOS bridges +- Pigeon for type-safe platform communication +- Flutter version compatibility (Flutter 3.10+, Dart 3.0+) +- Integration with `clevertap_plugin` (the CleverTap Flutter SDK) +- Cross-platform parity: behaviour must match Android (Kotlin/Compose) and iOS (Swift/SwiftUI) +- `pub.dev` packaging requirements + +## Plugin Architecture + +The Flutter plugin uses a federated architecture: + +``` +flutter/ +├── lib/ +│ ├── clevertap_native_display.dart # Public API barrel export +│ └── src/ +│ ├── models/ # Dart model classes with fromJson/toJson +│ ├── renderer/ # Flutter widget renderers +│ │ ├── native_display_view.dart # Entry point widget +│ │ ├── container_renderer.dart # Column/Row/Stack/PageView +│ │ └── element_renderer.dart # Text/Image/Button/Video/etc +│ ├── style/ # StyleResolver — cascading inheritance +│ ├── evaluator/ # TemplateEvaluator — {{variable}} interpolation +│ └── bridge/ # Platform channel — CleverTap Core SDK integration +├── android/ +│ └── src/main/kotlin/ # Android-side MethodChannel handler +├── ios/ +│ └── Classes/ # iOS-side FlutterMethodChannel handler +├── pubspec.yaml +└── example/ # Minimal example (full demos in flutter-sample/) +``` + +## Rendering Pipeline + +``` +JSON string + ↓ +jsonDecode() → Map + ↓ +NativeDisplayConfig.fromJson() + ↓ +StyleResolver.resolve() — cascading style inheritance + ↓ +TemplateEvaluator.evaluate() — {{vars}} + ↓ +NativeDisplayView (StatelessWidget) + ↓ +NativeDisplayRenderer._buildNode() + ↙ ↘ +ContainerRenderer ElementRenderer +Column/Row/Stack/PageView Text/Image.network/etc +``` + +## Container → Flutter Widget Mapping + +| Container | Flutter Widget | Notes | +|-----------|---------------|-------| +| `VERTICAL` | `Column` | mainAxisAlignment from arrangement | +| `HORIZONTAL` | `Row` | mainAxisAlignment from arrangement | +| `BOX` | `Stack` with `Positioned` children | offset applied to each child | +| `GALLERY` | `PageView` (SNAPPING) / `SingleChildScrollView` (FREE_FLOW) / `GridView` (FREE_FLOW_GRID) | | + +## Element → Flutter Widget Mapping + +| Element | Flutter Widget | Notes | +|---------|---------------|-------| +| `TEXT` | `Text` with `TextStyle` | Style cascading via `DefaultTextStyle` | +| `IMAGE` | `Image.network` with `CachedNetworkImage` or custom loader | GIF: native `Image.network` handles animated GIFs | +| `BUTTON` | `GestureDetector` wrapping `Container` | Custom tap handling via ActionHandler | +| `VIDEO` | `video_player` package + `AspectRatio` | Always dispose controller | +| `HTML` | `webview_flutter` package | Requires explicit height — no wrap_content | +| `SPACER` | `SizedBox` (fixed) or `Spacer` (flex) | | +| `DIVIDER` | `Divider` or `VerticalDivider` | | + +## Key Patterns + +### JSON Parsing +```dart +// Use factory constructor pattern with fromJson +class NativeDisplayConfig { + final Theme? theme; + final List? styleClasses; + final Map? variables; + final NativeDisplayNode root; + + const NativeDisplayConfig({ + this.theme, + this.styleClasses, + this.variables, + required this.root, + }); + + factory NativeDisplayConfig.fromJson(Map json) { + return NativeDisplayConfig( + theme: json['theme'] != null ? Theme.fromJson(json['theme']) : null, + styleClasses: (json['styleClasses'] as List?) + ?.map((e) => StyleClass.fromJson(e)) + .toList(), + variables: json['variables'] as Map?, + root: NativeDisplayNode.fromJson(json['root']), + ); + } +} +``` + +### Color Parsing (RGBA → Flutter Color) +```dart +Color parseColor(String hex) { + final clean = hex.startsWith('#') ? hex.substring(1) : hex; + final padded = switch (clean.length) { + 6 => 'FF$clean', // RGB → ARGB (alpha first in Flutter Color) + 8 => '${clean.substring(6)}${clean.substring(0, 6)}', // RRGGBBAA → AARRGGBB + _ => 'FF000000', + }; + return Color(int.parse(padded, radix: 16)); +} +// NOTE: SDK stores RGBA (#RRGGBBAA), Flutter Color uses ARGB (0xAARRGGBB) +// The AA bytes must be swapped when converting +``` + +### Style Cascading via InheritedWidget +```dart +// Pass cascading text style through the tree without prop drilling +class NativeDisplayTextStyle extends InheritedWidget { + final TextStyle textStyle; + + const NativeDisplayTextStyle({ + required this.textStyle, + required super.child, + super.key, + }); + + static TextStyle of(BuildContext context) { + return context + .dependOnInheritedWidgetOfExactType() + ?.textStyle ?? const TextStyle(); + } + + @override + bool updateShouldNotify(NativeDisplayTextStyle old) => + textStyle != old.textStyle; +} +``` + +### Arrangement Strategy → Flutter Alignment +```dart +MainAxisAlignment arrangementToMain(ArrangementStrategy strategy) => + switch (strategy) { + ArrangementStrategy.start => MainAxisAlignment.start, + ArrangementStrategy.center => MainAxisAlignment.center, + ArrangementStrategy.end => MainAxisAlignment.end, + ArrangementStrategy.spaceBetween => MainAxisAlignment.spaceBetween, + ArrangementStrategy.spaceEvenly => MainAxisAlignment.spaceEvenly, + ArrangementStrategy.spaceAround => MainAxisAlignment.spaceAround, + ArrangementStrategy.spaced => MainAxisAlignment.start, // Manual SizedBox spacing + }; + +// For SPACED: insert SizedBox between children instead of using MainAxisAlignment +List spaceChildren(List children, double spacing) { + if (children.isEmpty) return children; + return children + .expand((w) => [w, SizedBox(width: spacing, height: spacing)]) + .take(children.length * 2 - 1) + .toList(); +} +``` + +### Dimension Resolution +```dart +// Never use MediaQuery at render time for percent dimensions — requires parent constraint +// Use LayoutBuilder to get parent constraints for percent resolution + +Widget buildWithDimension(Layout layout) { + return LayoutBuilder( + builder: (context, constraints) { + final width = _resolve(layout.width, constraints.maxWidth); + final height = _resolve(layout.height, constraints.maxHeight); + return SizedBox(width: width, height: height, child: content); + }, + ); +} + +double? _resolve(Dimension dim, double parentSize) => switch (dim) { + Dimension(special: 'match_parent') => parentSize, + Dimension(special: 'wrap_content') => null, // null → intrinsic size + Dimension(unit: 'percent', value: var v) => parentSize * v / 100, + Dimension(unit: 'dp' || 'sp', value: var v) => v, + _ => null, +}; +``` + +### Performance: Minimise Rebuilds +```dart +// 1. Pre-resolve all styles once in NativeDisplayView — never inside build() +class NativeDisplayView extends StatelessWidget { + final NativeDisplayConfig config; + final Map _resolvedStyles; // pre-computed + + NativeDisplayView({super.key, required this.config}) + : _resolvedStyles = StyleResolver.resolve(config); + + @override + Widget build(BuildContext context) { + // pass _resolvedStyles down — O(1) lookup per node + return _NativeDisplayRenderer(config: config, resolvedStyles: _resolvedStyles); + } +} + +// 2. Use const constructors wherever children are static +// 3. Key containers so Flutter can diff efficiently +// 4. RepaintBoundary around GALLERY items to isolate repaints +RepaintBoundary(child: GalleryItem(config: item, resolvedStyles: styles)) +``` + +### Video Element — Always Dispose +```dart +class _VideoElementState extends State { + late VideoPlayerController _controller; + + @override + void initState() { + super.initState(); + _controller = VideoPlayerController.networkUrl(Uri.parse(widget.url)) + ..initialize().then((_) => setState(() {})); + if (widget.autoPlay) _controller.play(); + } + + @override + void dispose() { + _controller.dispose(); // Always release + super.dispose(); + } +} +``` + +### RTL Support +```dart +// Use Directionality widget at root or check TextDirection from context +final isRTL = Directionality.of(context) == TextDirection.rtl; + +// EdgeInsetsDirectional instead of EdgeInsets.only +EdgeInsetsDirectional.only(start: 16, end: 8) +``` + +## CleverTap Bridge (Platform Channel) + +The bridge receives display unit JSON from the CleverTap Core SDK and exposes it as a Dart stream: + +```dart +// Dart side +class NativeDisplayBridge { + static const _channel = MethodChannel('com.clevertap/native_display'); + static const _events = EventChannel('com.clevertap/native_display_events'); + + // Returns the JSON string for a given unitId + static Future getDisplayUnitJson(String unitId) async { + return await _channel.invokeMethod('getDisplayUnitJson', unitId); + } + + // Stream of display unit updates (push-based) + static Stream displayUnitStream() { + return _events.receiveBroadcastStream() + .map((data) => NativeDisplayConfig.fromJson(jsonDecode(data as String))); + } + + static Future recordViewed(String unitId) async { + await _channel.invokeMethod('recordViewed', unitId); + } + + static Future recordClicked(String unitId) async { + await _channel.invokeMethod('recordClicked', unitId); + } + + static Future recordElementClicked(String unitId, String elementId) async { + await _channel.invokeMethod('recordElementClicked', {'unitId': unitId, 'elementId': elementId}); + } +} +``` + +## Common Gotchas + +- **Dashboard only sends `percent` + `aspectRatio`**: The CleverTap dashboard never emits `dp`, `px`, `sp`, `wrap_content`, or `match_parent`. All dimension types must still be parsed and rendered correctly (for hand-authored JSON and tests), but the production fast-path is exclusively `percent` + `aspectRatio`. Optimise and stress-test these first. +- **Color byte order**: SDK uses RGBA (`#RRGGBBAA`), Flutter Color is ARGB (`0xAARRGGBB`) — swap AA bytes when parsing +- **SPACED arrangement**: must insert `SizedBox` between children — `MainAxisAlignment.spaced` does not exist +- **Percent dimensions need LayoutBuilder**: `MediaQuery.of(context).size` is screen size, not parent size — always use LayoutBuilder for percent resolution +- **null = wrap_content**: pass `null` width/height to `SizedBox` for intrinsic sizing +- **HTML element height**: `webview_flutter` requires explicit height — wrap_content is not supported +- **Video controller lifecycle**: always dispose `VideoPlayerController` in `dispose()` — leaks otherwise +- **Gallery sizing**: always based on container constraints, NOT screen dimensions — use LayoutBuilder inside PageView +- **SPACE_BETWEEN with 1 child**: child aligns to start in Flutter, matching Android/iOS behavior +- **Const constructors**: mark every widget with all-constant properties as `const` — enables subtree skipping +- **InheritedWidget scope**: `DefaultTextStyle` only applies to `Text` widget descendants, not to our model — use our own `NativeDisplayTextStyle` inherited widget +- **Missing variables**: `TemplateEvaluator` must return empty string (not null) for unknown `{{vars}}` — matches Android/iOS behavior +- **Circular node references**: validate before rendering — will cause infinite recursion +- **Style class merge order**: `classStyle.merge(inlineStyle)` so inline wins — never reverse + +## Workflow + +1. Read the relevant knowledge file(s) above +2. Check Android implementation for parity reference (`android-sdk/knowledge/`) +3. Check iOS implementation for parity reference (`ios-sdk/knowledge/`) +4. Read spec from `.claude/specs/` for new features +5. Design: Dart models → widget rendering → edge cases +6. Write idiomatic Dart 3 code with null safety +7. Write unit tests + widget tests +8. `cd flutter && flutter build` to verify compilation +9. `cd flutter && flutter test` to validate +10. `/review` before committing + +## Versioning & Compatibility + +- **Minimum Flutter**: 3.10.0 (Dart 3.0) — needed for pattern matching (`switch` expressions, sealed types) +- **Minimum Android**: API 23 (matches existing Android SDK — `minSdk = 23` in `android/sdk/build.gradle.kts`) +- **Minimum iOS**: 15.0 (matches existing iOS SDK — `Package.swift` `.iOS(.v15)`) +- **Dependencies**: keep to minimum — prefer Dart-native solutions over packages where feasible +- **Packages allowed**: `video_player`, `webview_flutter`, `cached_network_image` (or `flutter_cache_manager`), `pigeon` (dev) +- **Version sync**: Flutter plugin version should be aligned with native SDK version + +## What You Do NOT Do + +- Modify Android SDK code (`android/`) → delegate to `android-sdk` agent +- Modify iOS SDK code (`ios/`) → delegate to `ios-sdk` agent +- Modify sample apps → delegate to `flutter-sample` agent +- Make breaking API changes without discussion +- Add platform views for the core renderer (use pure Dart widgets) + +## Collaboration + +- Coordinate with `android-sdk` and `ios-sdk` agents for cross-platform parity +- Notify `flutter-sample` agent of breaking SDK changes +- Hand failing tests to `testing` agent for JSON reproduction cases +- Platform channel Android side: coordinate with `android-sdk` agent +- Platform channel iOS side: coordinate with `ios-sdk` agent diff --git a/.claude/agents/flutter-sdk/examples/native_display_view.dart b/.claude/agents/flutter-sdk/examples/native_display_view.dart new file mode 100644 index 0000000..f126c91 --- /dev/null +++ b/.claude/agents/flutter-sdk/examples/native_display_view.dart @@ -0,0 +1,142 @@ +// Example: NativeDisplayView entry point — pure Dart renderer +// +// Usage: +// final config = NativeDisplayConfig.fromJson(jsonDecode(jsonString)); +// NativeDisplayView(config: config) + +import 'dart:convert'; +import 'package:flutter/widgets.dart'; +import '../models/native_display_config.dart'; +import '../models/native_display_node.dart'; +import '../models/style.dart'; +import '../renderer/container_renderer.dart'; +import '../renderer/element_renderer.dart'; +import '../style/style_resolver.dart'; +import '../style/native_display_text_style.dart'; +import '../evaluator/template_evaluator.dart'; + +/// Public entry point for rendering a Native Display configuration. +class NativeDisplayView extends StatelessWidget { + final NativeDisplayConfig config; + final NativeDisplayActionListener? actionListener; + + // Styles are pre-resolved once in constructor — never inside build() + final Map _resolvedStyles; + + NativeDisplayView({ + super.key, + required this.config, + this.actionListener, + }) : _resolvedStyles = StyleResolver.resolve(config); + + @override + Widget build(BuildContext context) { + return _NativeDisplayRenderer( + node: config.root, + config: config, + resolvedStyles: _resolvedStyles, + actionListener: actionListener, + ); + } +} + +/// Convenience constructor — parse JSON string and render. +class NativeDisplayViewFromJson extends StatefulWidget { + final String jsonString; + final NativeDisplayActionListener? actionListener; + + const NativeDisplayViewFromJson({ + super.key, + required this.jsonString, + this.actionListener, + }); + + @override + State createState() => _NativeDisplayViewFromJsonState(); +} + +class _NativeDisplayViewFromJsonState extends State { + late NativeDisplayConfig _config; + + @override + void initState() { + super.initState(); + _config = NativeDisplayConfig.fromJson(jsonDecode(widget.jsonString)); + } + + @override + void didUpdateWidget(NativeDisplayViewFromJson oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.jsonString != widget.jsonString) { + _config = NativeDisplayConfig.fromJson(jsonDecode(widget.jsonString)); + } + } + + @override + Widget build(BuildContext context) => + NativeDisplayView(config: _config, actionListener: widget.actionListener); +} + +// --------------------------------------------------------------------------- +// Internal renderer — not part of public API +// --------------------------------------------------------------------------- + +class _NativeDisplayRenderer extends StatelessWidget { + final NativeDisplayNode node; + final NativeDisplayConfig config; + final Map resolvedStyles; + final NativeDisplayActionListener? actionListener; + + const _NativeDisplayRenderer({ + required this.node, + required this.config, + required this.resolvedStyles, + this.actionListener, + }); + + @override + Widget build(BuildContext context) { + final style = resolvedStyles[node.id] ?? Style.empty; + final variables = config.variables ?? {}; + + final Widget content = switch (node) { + ContainerNode() => ContainerRenderer( + node: node as ContainerNode, + resolvedStyles: resolvedStyles, + config: config, + actionListener: actionListener, + ), + ElementNode() => ElementRenderer( + node: node as ElementNode, + style: style, + variables: variables, + actionListener: actionListener, + ), + }; + + // Wrap container with text style cascade if container has text style properties + if (node is ContainerNode && _hasTextStyle(style)) { + return NativeDisplayTextStyle( + textStyle: _buildTextStyle(style), + child: content, + ); + } + + return content; + } + + bool _hasTextStyle(Style style) => + style.textColor != null || + style.fontSize != null || + style.fontFamily != null || + style.fontWeight != null; + + TextStyle _buildTextStyle(Style style) => const TextStyle(); // full impl in style_resolver.dart +} + +/// Callback interface for user interactions (taps on BUTTON elements, etc.) +abstract class NativeDisplayActionListener { + void onAction(NodeAction? action); + void onViewed(String nodeId); + void onClicked(String nodeId); +} diff --git a/.claude/agents/flutter-sdk/knowledge/architecture.md b/.claude/agents/flutter-sdk/knowledge/architecture.md new file mode 100644 index 0000000..ca168b4 --- /dev/null +++ b/.claude/agents/flutter-sdk/knowledge/architecture.md @@ -0,0 +1,198 @@ +# Flutter SDK Architecture + +## CRITICAL: SDK Usage Model + +**The Native Display SDK is JSON-driven.** Flutter clients do NOT write custom widgets or implement renderers. + +**Client Usage (3 steps):** +1. Load JSON configuration +2. Parse: `NativeDisplayConfig.fromJson(jsonDecode(jsonString))` +3. Render: `NativeDisplayView(config: config)` + +**That's it.** ✅ No platform views, no native bridges needed for rendering. + +--- + +## Overview + +The Flutter plugin is a **federated plugin** with a **pure Dart renderer**: + +- **Dart-side**: JSON parsing + Flutter widget rendering (works on all platforms) +- **Platform channel bridge**: CleverTap Core SDK integration (receive/report display unit events) +- **No platform views for rendering**: avoids texture composition overhead + +The rendering engine is fully in Dart — Flutter's own Skia/Impeller engine renders everything natively. + +--- + +## Package Structure + +``` +flutter/ # Plugin root (publishable to pub.dev) +├── lib/ +│ ├── clevertap_native_display.dart # Barrel export — public API +│ └── src/ +│ ├── models/ +│ │ ├── native_display_config.dart +│ │ ├── native_display_node.dart +│ │ ├── layout.dart +│ │ ├── style.dart +│ │ ├── background.dart +│ │ └── gallery_config.dart +│ ├── renderer/ +│ │ ├── native_display_view.dart # Entry point StatelessWidget +│ │ ├── native_display_renderer.dart # Node dispatch + style injection +│ │ ├── container_renderer.dart # Column/Row/Stack/PageView/GridView +│ │ └── element_renderer.dart # Text/Image/Button/Video/HTML/Spacer/Divider +│ ├── style/ +│ │ ├── style_resolver.dart # Cascading style inheritance +│ │ └── native_display_text_style.dart # InheritedWidget for text cascading +│ ├── evaluator/ +│ │ └── template_evaluator.dart # {{variable}} interpolation +│ └── bridge/ +│ ├── native_display_bridge.dart # Dart-side MethodChannel/EventChannel +│ └── action_handler.dart # Tap → CleverTap event reporting +├── android/ +│ └── src/main/kotlin/com/clevertap/flutter/nativedisplay/ +│ └── CleverTapNativeDisplayPlugin.kt # MethodChannel handler (Kotlin) +├── ios/ +│ └── Classes/ +│ └── CleverTapNativeDisplayPlugin.swift # FlutterMethodChannel handler (Swift) +├── pubspec.yaml +└── example/ # Minimal working example +``` + +--- + +## Core Layers + +### 1. Data Models Layer (`src/models/`) + +Pure Dart, no external dependencies. Uses factory `fromJson` constructors. + +**Key models:** +- `NativeDisplayConfig` — root object: theme, styleClasses, variables, root node +- `NativeDisplayNode` — polymorphic: container (`type: "container"`) or element (`type: "element"`) +- `Layout` — width, height, padding, offset, arrangement +- `Style` — text properties + visual properties +- `Background` — sealed-class-style via enum/union (solid, gradient, image, etc.) + +**Parsing approach — manual `fromJson`** (no code-gen dependency): +```dart +factory NativeDisplayNode.fromJson(Map json) { + return switch (json['type'] as String) { + 'container' => ContainerNode.fromJson(json), + 'element' => ElementNode.fromJson(json), + _ => throw FormatException('Unknown node type: ${json['type']}'), + }; +} +``` + +### 2. Business Logic Layer (`src/style/`, `src/evaluator/`) + +**StyleResolver** — O(n) traversal, builds `Map` (nodeId → resolvedStyle): +- Resolution order: Theme → StyleClass → Inline → Parent cascade (text props only) +- Text properties cascade: `textColor`, `fontSize`, `fontFamily`, `fontWeight`, `lineHeight`, `textDecoration`, `textAlign`, `opacity` +- Visual properties do NOT cascade: `background`, `backgroundColor`, `borderRadius`, `borderWidth`, `borderColor`, `shadow*` + +**TemplateEvaluator** — replaces `{{varName}}` and `{{object.property}}` in string bindings: +- Unknown variables → return empty string (silent, consistent with Android/iOS) +- Nested paths: `{{user.name}}` → `variables['user']['name']` + +### 3. Rendering Layer (`src/renderer/`) + +**NativeDisplayView** — the public entry-point `StatelessWidget`: +```dart +class NativeDisplayView extends StatelessWidget { + final NativeDisplayConfig config; + final NativeDisplayActionListener? actionListener; + + // Pre-resolve styles once — never inside build() + final Map _resolvedStyles; + + NativeDisplayView({super.key, required this.config, this.actionListener}) + : _resolvedStyles = StyleResolver.resolve(config); + + @override + Widget build(BuildContext context) => NativeDisplayRenderer( + node: config.root, + config: config, + resolvedStyles: _resolvedStyles, + actionListener: actionListener, + ); +} +``` + +**NativeDisplayRenderer** — recursive `_buildNode()` dispatches to container or element renderers. + +### 4. Bridge Layer (`src/bridge/`) + +Optional — only used when integrating with CleverTap Core SDK. + +- `NativeDisplayBridge.getDisplayUnitJson(unitId)` — fetch JSON via MethodChannel +- `NativeDisplayBridge.displayUnitStream()` — receive display unit updates via EventChannel +- `NativeDisplayBridge.recordViewed(unitId)` — report viewed event +- `NativeDisplayBridge.recordClicked(unitId)` — report clicked event + +--- + +## Flutter Three-Tree Model — What It Means for This SDK + +``` +Widget tree (rebuilt frequently, cheap) + NativeDisplayView + └── NativeDisplayRenderer + ├── Column (VERTICAL container) + │ ├── TextElement + │ └── ImageElement + └── ... + +Element tree (persistent, one-to-one with widgets, handles diffing) + StatelessElement (NativeDisplayView) + └── StatelessElement (NativeDisplayRenderer) + └── ... + +RenderObject tree (actual layout/paint) + RenderFlex (Column) + ├── RenderParagraph (Text) + └── RenderImage (Image) +``` + +**Key implication for our SDK**: Because styles are pre-resolved in `NativeDisplayView` constructor (before `build()`), and model classes are immutable, the element tree can reuse render objects efficiently without unnecessary relayout. + +--- + +## Integration with CleverTap Flutter SDK + +The existing `clevertap_plugin` provides analytics and user profiles. Native Display extends it: + +``` +User App +├── clevertap_plugin (existing: analytics, push, inbox) +└── clevertap_native_display (new: Native Display renderer + bridge) + └── NativeDisplayBridge connects to Core SDK via MethodChannel +``` + +The Core SDK on Android/iOS receives display units from CleverTap servers, caches them, and exposes them via the MethodChannel. The Flutter side fetches JSON and renders with `NativeDisplayView`. + +--- + +## Design Decisions + +### Pure Dart Renderer (not Platform Views) + +Platform Views embed native views (AndroidView/UiKitView) into Flutter. They carry significant overhead: +- Texture composition (GPU context switching) +- Input event translation +- Accessibility tree bridging +- Thread synchronization + +A pure Dart renderer avoids all of this. Flutter's Impeller/Skia renders Dart widgets natively at 60/120fps without any of this overhead. The JSON rendering logic is re-implemented in Dart. + +### Manual `fromJson` (not `json_serializable`) + +Keeps the plugin dependency-free for the core parsing layer. `json_serializable` is fine for sample apps but adds a build_runner dev dependency to the plugin itself — avoided for simplicity and faster builds. + +### `InheritedWidget` for Text Style Cascading + +Text property cascading (textColor, fontSize, etc.) passes style down the widget tree naturally via Flutter's `InheritedWidget` mechanism (similar to how `DefaultTextStyle` works). This avoids passing style as a parameter through every level of the recursion. diff --git a/.claude/agents/flutter-sdk/knowledge/performance.md b/.claude/agents/flutter-sdk/knowledge/performance.md new file mode 100644 index 0000000..fd76eb6 --- /dev/null +++ b/.claude/agents/flutter-sdk/knowledge/performance.md @@ -0,0 +1,204 @@ +# Flutter SDK Performance Guide + +## Core Rule: Build Phase Must Be Fast + +Flutter's `build()` method can run 60+ times per second. Every unnecessary computation in build causes frame drops. + +--- + +## 1. Pre-Resolve Styles Before Build + +**Critical**: resolve all styles once in `NativeDisplayView`'s constructor — never inside `build()` or inside any widget's build method. + +```dart +class NativeDisplayView extends StatelessWidget { + final NativeDisplayConfig config; + // Pre-computed in constructor — O(n) traversal happens once + final Map _resolvedStyles; + + NativeDisplayView({super.key, required this.config}) + : _resolvedStyles = StyleResolver.resolve(config); + + @override + Widget build(BuildContext context) { + // _resolvedStyles already computed — just pass through + return NativeDisplayRenderer(config: config, resolvedStyles: _resolvedStyles); + } +} +``` + +Lookup during render: `_resolvedStyles[node.id] ?? Style.empty` — O(1). + +--- + +## 2. Use `const` Constructors Everywhere Possible + +Flutter short-circuits rebuild work for `const` widgets — they are identical across rebuilds and reuse the same element. + +```dart +// ✅ const where all fields are compile-time constants +const SizedBox(height: 16) +const Padding(padding: EdgeInsets.all(8), child: Text('Hello')) + +// ✅ Mark leaf widgets const when they have no dynamic data +const NativeDisplayTextStyle(textStyle: TextStyle(), child: SizedBox()) +``` + +Our model widgets (containers, elements) will rarely be `const` since they take dynamic data — but their sub-widgets (separators, empty states, loading indicators) should be `const`. + +--- + +## 3. RepaintBoundary Around Gallery Items + +Gallery items animate independently. Wrapping each in `RepaintBoundary` isolates repaint so only the active item repaints, not the entire gallery. + +```dart +Widget _buildGalleryItem(NativeDisplayNode item, Map resolvedStyles) { + return RepaintBoundary( + child: NativeDisplayRenderer(node: item, resolvedStyles: resolvedStyles), + ); +} +``` + +--- + +## 4. ListView.builder / PageView for Long Galleries + +Never render all gallery items at once with `Column` or `children: [...]` for large galleries. + +```dart +// ✅ Lazy — only visible items are built +PageView.builder( + itemCount: items.length, + itemBuilder: (context, index) => RepaintBoundary( + child: NativeDisplayRenderer(node: items[index], resolvedStyles: resolvedStyles), + ), +) + +// ❌ Eagerly builds all items +PageView(children: items.map((item) => NativeDisplayRenderer(...)).toList()) +``` + +For `FREE_FLOW_GRID`, use `SliverGrid` / `GridView.builder` with a fixed `SliverGridDelegate`. + +--- + +## 5. Avoid Unnecessary `Opacity` Widget + +`Opacity` triggers an offscreen render pass (`saveLayer()`), which is expensive. + +```dart +// ✅ Apply opacity directly to Color — no saveLayer +Color.fromRGBO(255, 0, 0, 0.5) // Dart +parseColor('#FF000080') // RGBA #RRGGBBAA with 50% alpha + +// ✅ For images, use colorBlendMode on the Image widget +Image.network(url, color: Colors.black.withOpacity(0.5), colorBlendMode: BlendMode.dstATop) + +// ❌ Only use Opacity widget when there is no alternative (e.g. animating opacity) +Opacity(opacity: 0.5, child: ComplexWidget()) +``` + +When opacity comes from the style and is static, apply it directly to the color. Only use the `Opacity` widget if animating. + +--- + +## 6. Avoid `saveLayer()` — Minimize Shader/ColorFilter Usage + +These widgets always call `saveLayer()` and are expensive: +- `ShaderMask` — only use for gradient text (unavoidable) +- `ColorFilter` — only for image color tinting +- `BackdropFilter` — only for blur effects + +Gradient backgrounds via `BoxDecoration.gradient` are fine — they do not trigger `saveLayer()`. + +--- + +## 7. Fixed-Size Grid Delegates Avoid Intrinsic Passes + +For `FREE_FLOW_GRID` gallery mode, always specify a fixed cross-axis count or extent: + +```dart +// ✅ No intrinsic pass — sizes are known up front +GridView.builder( + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + childAspectRatio: 1.5, + crossAxisSpacing: 8, + mainAxisSpacing: 8, + ), + itemCount: items.length, + itemBuilder: (context, i) => NativeDisplayRenderer(node: items[i], resolvedStyles: styles), +) + +// ❌ Forces intrinsic pass — measures all items to determine uniform size +GridView(children: items.map(...).toList()) +``` + +--- + +## 8. `LayoutBuilder` — Use Sparingly + +`LayoutBuilder` forces a layout pass to provide constraints. Use it only where percent dimensions or aspect ratios are present. + +```dart +// Only wrap in LayoutBuilder if the node actually has percent dimensions +bool _needsLayoutBuilder(Layout layout) { + return layout.width?.unit == 'percent' + || layout.height?.unit == 'percent' + || layout.width?.aspectRatio != null; +} + +Widget buildNode(NativeDisplayNode node) { + if (_needsLayoutBuilder(node.layout)) { + return LayoutBuilder(builder: (ctx, constraints) => _buildWithConstraints(node, constraints)); + } + return _buildFixed(node); +} +``` + +--- + +## 9. Image Caching + +Flutter's `Image.network` uses an in-memory `ImageCache` (1000 images, 100MB limit). For production, use `cached_network_image` for disk caching: + +```dart +CachedNetworkImage( + imageUrl: url, + fit: BoxFit.cover, + placeholder: (ctx, url) => const ColoredBox(color: Color(0xFFE0E0E0)), + errorWidget: (ctx, url, err) => const SizedBox.shrink(), +) +``` + +Pre-warming the cache before the widget renders eliminates loading flicker: +```dart +precacheImage(NetworkImage(url), context); +``` + +--- + +## 10. Immutable Models — No `==` Override on Widgets + +All data classes (`NativeDisplayConfig`, `NativeDisplayNode`, `Style`, `Layout`, etc.) should be immutable (final fields, no setters). This is not just for correctness — it ensures Flutter's element diffing can rely on object identity. + +**Do not** override `operator ==` on widget classes — Flutter's framework caches widget configurations using object identity, and custom `==` can interfere. + +--- + +## Profiling + +Measure in **profile mode** (`flutter run --profile`) on a real device (or the lowest-spec emulator/simulator you target): + +```bash +flutter run --profile +# Then press 'P' in terminal to launch DevTools performance view +``` + +Key DevTools views: +- **Timeline**: frame build time, identify janky frames (>16ms at 60Hz, >8ms at 120Hz) +- **Widget rebuild information**: shows unnecessary rebuilds (enable in IDE Flutter plugin) +- **Memory**: check for leaked VideoPlayerControllers, ImageProviders + +Target: build phase < 8ms, render phase < 8ms for 60fps apps. diff --git a/.claude/agents/flutter-sdk/knowledge/platform-channels.md b/.claude/agents/flutter-sdk/knowledge/platform-channels.md new file mode 100644 index 0000000..cf26678 --- /dev/null +++ b/.claude/agents/flutter-sdk/knowledge/platform-channels.md @@ -0,0 +1,286 @@ +# Platform Channel Bridge — CleverTap Integration + +## Overview + +The platform channel bridge connects the Flutter Native Display plugin to the CleverTap Core SDK on Android and iOS. The Dart-side renderer is pure Flutter — platform channels are only needed to: + +1. Fetch display unit JSON from the Core SDK cache +2. Receive push-based display unit updates +3. Report viewed/clicked events back to the Core SDK + +--- + +## Channel Definitions + +```dart +// lib/src/bridge/native_display_bridge.dart + +class NativeDisplayBridge { + static const _methodChannel = MethodChannel( + 'com.clevertap.flutter/native_display', + ); + static const _eventChannel = EventChannel( + 'com.clevertap.flutter/native_display_events', + ); + + /// Fetch display unit JSON by ID (pull-based). + static Future getDisplayUnit(String unitId) async { + final json = await _methodChannel.invokeMethod( + 'getDisplayUnit', + unitId, + ); + if (json == null) return null; + return NativeDisplayConfig.fromJson(jsonDecode(json)); + } + + /// Stream of display unit updates as they arrive (push-based). + static Stream displayUnitUpdates() { + return _eventChannel + .receiveBroadcastStream() + .whereType() + .map((json) => NativeDisplayConfig.fromJson(jsonDecode(json))); + } + + static Future recordViewed(String unitId) => + _methodChannel.invokeMethod('recordViewed', unitId); + + static Future recordClicked(String unitId) => + _methodChannel.invokeMethod('recordClicked', unitId); + + static Future recordElementClicked( + String unitId, + String elementId, { + Map? additionalProperties, + }) => + _methodChannel.invokeMethod('recordElementClicked', { + 'unitId': unitId, + 'elementId': elementId, + if (additionalProperties != null) ...additionalProperties, + }); +} +``` + +--- + +## Android Bridge (Kotlin) + +```kotlin +// android/src/main/kotlin/com/clevertap/flutter/nativedisplay/CleverTapNativeDisplayPlugin.kt + +class CleverTapNativeDisplayPlugin : FlutterPlugin, MethodCallHandler { + private lateinit var channel: MethodChannel + private lateinit var eventChannel: EventChannel + private var eventSink: EventChannel.EventSink? = null + + override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) { + channel = MethodChannel( + binding.binaryMessenger, + "com.clevertap.flutter/native_display" + ) + channel.setMethodCallHandler(this) + + eventChannel = EventChannel( + binding.binaryMessenger, + "com.clevertap.flutter/native_display_events" + ) + eventChannel.setStreamHandler(object : EventChannel.StreamHandler { + override fun onListen(args: Any?, sink: EventChannel.EventSink) { + eventSink = sink + } + override fun onCancel(args: Any?) { + eventSink = null + } + }) + } + + override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { + when (call.method) { + "getDisplayUnit" -> { + val unitId = call.arguments as? String + if (unitId == null) { result.error("INVALID_ARG", "unitId required", null); return } + // CleverTap Core SDK returns the cached unit as a JSON string + val json = CleverTapAPI.getDefaultInstance(context) + ?.getDisplayUnitForId(unitId) // returns JSONObject + ?.toString() + result.success(json) + } + "recordViewed" -> { + val unitId = call.arguments as? String ?: return result.error("INVALID_ARG", null, null) + CleverTapAPI.getDefaultInstance(context)?.pushDisplayUnitViewedEventForID(unitId) + result.success(null) + } + "recordClicked" -> { + val unitId = call.arguments as? String ?: return result.error("INVALID_ARG", null, null) + CleverTapAPI.getDefaultInstance(context)?.pushDisplayUnitClickedEventForID(unitId) + result.success(null) + } + "recordElementClicked" -> { + val args = call.arguments as? Map<*, *> ?: return result.error("INVALID_ARG", null, null) + val unitId = args["unitId"] as? String ?: return result.error("INVALID_ARG", null, null) + val elementId = args["elementId"] as? String ?: return result.error("INVALID_ARG", null, null) + CleverTapAPI.getDefaultInstance(context) + ?.pushDisplayUnitElementClickedEventForID(unitId, elementId) + result.success(null) + } + else -> result.notImplemented() + } + } +} +``` + +### Pushing events from Android to Dart (EventChannel) + +```kotlin +// Call this when the Core SDK delivers a new display unit +fun onDisplayUnitReceived(unitJson: String) { + Handler(Looper.getMainLooper()).post { + eventSink?.success(unitJson) + } +} +``` + +--- + +## iOS Bridge (Swift) + +```swift +// ios/Classes/CleverTapNativeDisplayPlugin.swift + +public class CleverTapNativeDisplayPlugin: NSObject, FlutterPlugin { + private var eventSink: FlutterEventSink? + + public static func register(with registrar: FlutterPluginRegistrar) { + let methodChannel = FlutterMethodChannel( + name: "com.clevertap.flutter/native_display", + binaryMessenger: registrar.messenger() + ) + let eventChannel = FlutterEventChannel( + name: "com.clevertap.flutter/native_display_events", + binaryMessenger: registrar.messenger() + ) + let instance = CleverTapNativeDisplayPlugin() + registrar.addMethodCallDelegate(instance, channel: methodChannel) + eventChannel.setStreamHandler(instance) + } + + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + switch call.method { + case "getDisplayUnit": + guard let unitId = call.arguments as? String else { + result(FlutterError(code: "INVALID_ARG", message: "unitId required", details: nil)) + return + } + // CleverTap Core SDK: recordDisplayUnitViewedEventForID + let unitJson = CleverTap.sharedInstance()?.getDisplayUnit(forID: unitId) + result(unitJson) + + case "recordViewed": + guard let unitId = call.arguments as? String else { return result(nil) } + CleverTap.sharedInstance()?.recordDisplayUnitViewedEvent(forID: unitId) + result(nil) + + case "recordClicked": + guard let unitId = call.arguments as? String else { return result(nil) } + CleverTap.sharedInstance()?.recordDisplayUnitClickedEvent(forID: unitId) + result(nil) + + case "recordElementClicked": + guard let args = call.arguments as? [String: Any], + let unitId = args["unitId"] as? String, + let elementId = args["elementId"] as? String else { return result(nil) } + CleverTap.sharedInstance()?.recordDisplayUnitElementClickedEvent( + forID: unitId, + elementID: elementId, + additionalProperties: nil + ) + result(nil) + + default: + result(FlutterMethodNotImplemented) + } + } +} + +// EventChannel stream handler +extension CleverTapNativeDisplayPlugin: FlutterStreamHandler { + public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { + self.eventSink = events + return nil + } + public func onCancel(withArguments arguments: Any?) -> FlutterError? { + self.eventSink = nil + return nil + } +} + +// Push display unit JSON to Flutter +func pushDisplayUnit(json: String) { + DispatchQueue.main.async { + self.eventSink?(json) + } +} +``` + +--- + +## Thread Safety + +| Platform | Rule | +|----------|------| +| Android | `MethodChannel` handlers run on main thread by default. If Core SDK delivers on a background thread, use `Handler(Looper.getMainLooper()).post { }` to marshal to main thread before calling `result.success()` or `eventSink?.success()`. | +| iOS | `FlutterMethodChannel` callbacks may arrive on any thread. Always dispatch to main: `DispatchQueue.main.async { }` before calling `result()` or `eventSink()`. | + +--- + +## Data Type Mapping + +Platform channels serialize automatically via `StandardMessageCodec`: + +| Dart | Kotlin | Swift | +|------|--------|-------| +| `String` | `String` | `String` | +| `int` | `Int` / `Long` | `NSNumber(Int)` | +| `double` | `Double` | `NSNumber(Double)` | +| `bool` | `Boolean` | `NSNumber(Bool)` | +| `Map` | `HashMap` | `[String: Any]` | +| `List` | `ArrayList` | `[Any]` | +| `null` | `null` | `nil` | + +JSON is sent as a `String` — never as a `Map` — because the Dart-side renderer parses JSON from a string. This avoids double-serialization and keeps the bridge thin. + +--- + +## Pigeon (Future) + +For type safety as the bridge grows, consider migrating to **Pigeon**: + +```dart +// pigeon/native_display_api.dart +import 'package:pigeon/pigeon.dart'; + +@HostApi() +abstract class NativeDisplayHostApi { + @async + String? getDisplayUnit(String unitId); + void recordViewed(String unitId); + void recordClicked(String unitId); +} + +@FlutterApi() +abstract class NativeDisplayFlutterApi { + void onDisplayUnitReceived(String unitJson); +} +``` + +Pigeon generates type-safe Dart/Kotlin/Swift code, eliminating string-based method dispatch and manual argument casting. Adopt when the bridge has more than ~5 methods. + +--- + +## Core SDK Method Reference + +| Platform | Viewed event | Clicked event | Element clicked | Get unit JSON | +|----------|-------------|---------------|-----------------|---------------| +| Android | `pushDisplayUnitViewedEventForID(unitId)` | `pushDisplayUnitClickedEventForID(unitId)` | `pushDisplayUnitElementClickedEventForID(unitId, elementId)` | `getDisplayUnitForId(unitId).toString()` | +| iOS | `recordDisplayUnitViewedEventForID:` | `recordDisplayUnitClickedEventForID:` | `recordDisplayUnitElementClickedEventForID:elementID:additionalProperties:` | `getDisplayUnitForID:` | + +Note the asymmetry: Android uses `push*`, iOS uses `record*`. This is intentional Core SDK behavior — see reference memory for context. diff --git a/.claude/agents/flutter-sdk/knowledge/rendering-pipeline.md b/.claude/agents/flutter-sdk/knowledge/rendering-pipeline.md new file mode 100644 index 0000000..b03d855 --- /dev/null +++ b/.claude/agents/flutter-sdk/knowledge/rendering-pipeline.md @@ -0,0 +1,329 @@ +# Flutter Rendering Pipeline + +## Build → Layout → Paint → Composite + +Flutter renders in four sequential phases per frame: + +``` +1. BUILD Widget.build() called → Widget tree produced +2. LAYOUT Constraints down, sizes up — O(n) single pass +3. PAINT RenderObject.paint() → layer tree +4. COMPOSITE Scene assembled → GPU rasterization (Skia/Impeller) +``` + +For the Native Display SDK, phases 1 and 2 are most relevant. + +--- + +## Container Rendering + +### VERTICAL → Column + +```dart +Widget renderVertical(ContainerNode node, List children, Style style) { + final arrangement = node.layout.arrangement; + final spacing = arrangement.spacing ?? 0.0; + + return Column( + mainAxisAlignment: arrangementToMain(arrangement.strategy), + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: mainAxisSize(node.layout.height), + children: arrangement.strategy == ArrangementStrategy.spaced + ? _insertSpacers(children, spacing, axis: Axis.vertical) + : children, + ); +} +``` + +### HORIZONTAL → Row + +```dart +Widget renderHorizontal(ContainerNode node, List children) { + final arrangement = node.layout.arrangement; + return Row( + mainAxisAlignment: arrangementToMain(arrangement.strategy), + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: mainAxisSize(node.layout.width), + children: arrangement.strategy == ArrangementStrategy.spaced + ? _insertSpacers(children, arrangement.spacing ?? 0, axis: Axis.horizontal) + : children, + ); +} +``` + +### BOX → Stack + +```dart +Widget renderBox(ContainerNode node, List children, Style style) { + return Stack( + clipBehavior: Clip.hardEdge, + children: children.map((child) { + final offset = child.node.layout.offset; + if (offset == null) return child.widget; + return Positioned(left: offset.x, top: offset.y, child: child.widget); + }).toList(), + ); +} +``` + +### GALLERY → PageView (SNAPPING) / SingleChildScrollView (FREE_FLOW) / GridView (FREE_FLOW_GRID) + +```dart +Widget renderGallery(ContainerNode node, List children, GalleryConfig config) { + return switch (config.mode) { + GalleryMode.snapping => _buildPageView(node, children, config), + GalleryMode.freeFlow => _buildScrollView(node, children, config), + GalleryMode.freeFlowGrid => _buildGridView(node, children, config), + }; +} + +Widget _buildPageView(ContainerNode node, List children, GalleryConfig config) { + return LayoutBuilder( + builder: (context, constraints) => SizedBox( + height: constraints.maxHeight, + child: PageView( + controller: PageController(viewportFraction: config.peeking ? 0.85 : 1.0), + children: children.map((c) => RepaintBoundary(child: c)).toList(), + ), + ), + ); +} +``` + +--- + +## Element Rendering + +### TEXT + +```dart +Widget renderText(ElementNode node, Style style) { + final text = TemplateEvaluator.evaluate(node.bindings['text'] ?? '', variables); + final inherited = NativeDisplayTextStyle.of(context); // cascaded from parent + + return Text( + text, + maxLines: style.maxLines, + overflow: _toOverflow(style.overflow), + style: inherited.copyWith( + color: style.textColor != null ? parseColor(style.textColor!) : null, + fontSize: style.fontSize?.resolve(rootHeight), + fontWeight: _toFontWeight(style.fontWeight), + fontStyle: _toFontStyle(style.fontStyle), + height: style.lineHeight?.resolve(rootHeight) != null + ? style.lineHeight!.resolve(rootHeight)! / style.fontSize!.resolve(rootHeight)! + : null, + letterSpacing: style.letterSpacing, + decoration: _toDecoration(style.textDecoration), + ), + textAlign: _toTextAlign(style.textAlign), + ); +} +``` + +**lineHeight note**: Flutter's `TextStyle.height` is a multiplier on fontSize (e.g., `height: 1.5`), not an absolute value. Convert: `height = lineHeight / fontSize`. + +### IMAGE + +```dart +Widget renderImage(ElementNode node, Style style) { + final url = TemplateEvaluator.evaluate(node.bindings['url'] ?? '', variables); + final fit = _toBoxFit(style.imageFit); + + // GIF detection: explicit flag, .gif extension, known hosts, path patterns + final isGif = node.imageConfig?.animated == true + || url.toLowerCase().endsWith('.gif') + || _isKnownGifHost(url); + + // Flutter's Image.network handles animated GIFs natively via codec + return Image.network( + url, + fit: fit, + loadingBuilder: (ctx, child, progress) { + if (progress == null) return child; + return const SizedBox.shrink(); // or shimmer + }, + errorBuilder: (ctx, err, stack) => const SizedBox.shrink(), + ); +} +``` + +### BUTTON + +```dart +Widget renderButton(ElementNode node, Style style) { + final text = TemplateEvaluator.evaluate(node.bindings['text'] ?? '', variables); + + return GestureDetector( + onTap: () => actionListener?.onAction(node.action), + child: Container( + decoration: _buildDecoration(style), + padding: _buildPadding(node.layout.padding), + child: Text(text, style: _buildTextStyle(style)), + ), + ); +} +``` + +--- + +## Dimension Resolution + +Every node's width and height must be resolved against parent constraints. + +```dart +class DimensionCalculator { + static double? resolve(Dimension? dim, double parentSize, double rootHeight) { + if (dim == null) return null; + if (dim.special == 'match_parent') return parentSize; + if (dim.special == 'wrap_content') return null; // null → intrinsic size + if (dim.unit == 'percent') return parentSize * dim.value / 100; + if (dim.aspectRatio != null) return null; // caller handles aspect ratio + return dim.value; // dp/sp/px — treat as logical pixels + } +} + +// Usage: always wrap in LayoutBuilder when percent dimensions are used +Widget buildConstrainedNode(NativeDisplayNode node, List? children) { + return LayoutBuilder( + builder: (context, constraints) { + final w = DimensionCalculator.resolve(node.layout.width, constraints.maxWidth, rootHeight); + final h = DimensionCalculator.resolve(node.layout.height, constraints.maxHeight, rootHeight); + + // Handle aspect ratio + Widget child = buildContent(node, children); + if (node.layout.width?.aspectRatio != null) { + child = AspectRatio( + aspectRatio: node.layout.width!.aspectRatio!, + child: child, + ); + } + + return SizedBox(width: w, height: h, child: child); + }, + ); +} +``` + +--- + +## Style Application — Decorator Pattern + +Apply style as a widget decoration chain: + +```dart +Widget applyStyle(Widget child, Style style, Layout layout) { + // 1. Apply padding (inside decoration) + if (layout.padding != null) { + child = Padding(padding: _buildEdgeInsets(layout.padding!), child: child); + } + + // 2. Apply background, border, border-radius as BoxDecoration + final decoration = _buildBoxDecoration(style); + if (decoration != null) { + child = DecoratedBox(decoration: decoration, child: child); + } + + // 3. Apply opacity + if (style.opacity != null && style.opacity != 1.0) { + child = Opacity(opacity: style.opacity!, child: child); + } + + // 4. Apply shadow (via DecoratedBox — already in step 2 via boxShadow) + + // 5. Apply clip (for borderRadius) + if (style.borderRadius != null) { + child = ClipRRect( + borderRadius: BorderRadius.circular(_resolveRadius(style.borderRadius!, rootHeight)), + child: child, + ); + } + + return child; +} +``` + +--- + +## Text Style Cascading via InheritedWidget + +Text properties cascade from parent containers to all descendants. Wrap containers with the inherited widget: + +```dart +// When rendering a container with a style that has text properties: +Widget wrapWithTextStyle(Widget child, Style style) { + final inherited = NativeDisplayTextStyle.of(context); + final merged = inherited.copyWith( + color: style.textColor != null ? parseColor(style.textColor!) : null, + fontSize: style.fontSize?.resolve(rootHeight), + // ... other text properties + ); + return NativeDisplayTextStyle(textStyle: merged, child: child); +} +``` + +--- + +## Background Rendering + +```dart +BoxDecoration _buildBoxDecoration(Style style) { + return BoxDecoration( + color: style.backgroundColor != null ? parseColor(style.backgroundColor!) : null, + gradient: _buildGradient(style.background), + image: _buildDecorationImage(style.background), + borderRadius: style.borderRadius != null + ? BorderRadius.circular(_resolveRadius(style.borderRadius!, rootHeight)) + : null, + border: style.borderWidth != null + ? Border.all( + color: parseColor(style.borderColor ?? '#000000'), + width: rootHeight * (style.borderWidth ?? 0) / 1000, + ) + : null, + boxShadow: _buildBoxShadow(style), + ); +} +``` + +--- + +## Video Element Pipeline + +```dart +// StatefulWidget required for lifecycle management +class VideoElement extends StatefulWidget { ... } + +class _VideoElementState extends State { + VideoPlayerController? _controller; + bool _initialized = false; + + @override + void initState() { + super.initState(); + _controller = VideoPlayerController.networkUrl(Uri.parse(widget.url)); + _controller!.initialize().then((_) { + if (!mounted) return; + setState(() => _initialized = true); + if (widget.autoPlay) _controller!.play(); + if (widget.loop) _controller!.setLooping(true); + _controller!.setVolume(widget.muted ? 0 : 1); + }); + } + + @override + Widget build(BuildContext context) { + if (!_initialized) return const SizedBox.shrink(); + return AspectRatio( + aspectRatio: _controller!.value.aspectRatio, + child: VideoPlayer(_controller!), + ); + } + + @override + void dispose() { + _controller?.dispose(); + super.dispose(); + } +} +``` diff --git a/flutter-sample/.gitignore b/flutter-sample/.gitignore new file mode 100644 index 0000000..79c113f --- /dev/null +++ b/flutter-sample/.gitignore @@ -0,0 +1,45 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.build/ +.buildlog/ +.history +.svn/ +.swiftpm/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/flutter-sample/analysis_options.yaml b/flutter-sample/analysis_options.yaml new file mode 100644 index 0000000..0d29021 --- /dev/null +++ b/flutter-sample/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/flutter-sample/android/.gitignore b/flutter-sample/android/.gitignore new file mode 100644 index 0000000..be3943c --- /dev/null +++ b/flutter-sample/android/.gitignore @@ -0,0 +1,14 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java +.cxx/ + +# Remember to never publicly share your keystore. +# See https://flutter.dev/to/reference-keystore +key.properties +**/*.keystore +**/*.jks diff --git a/flutter-sample/android/app/build.gradle.kts b/flutter-sample/android/app/build.gradle.kts new file mode 100644 index 0000000..0ac6c45 --- /dev/null +++ b/flutter-sample/android/app/build.gradle.kts @@ -0,0 +1,51 @@ +plugins { + id("com.android.application") + id("kotlin-android") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "com.clevertap.flutter.clevertap_native_display_sample" + compileSdk = flutter.compileSdkVersion + ndkVersion = "27.0.12077973" + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11.toString() + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.clevertap.flutter.clevertap_native_display_sample" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = 23 + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.getByName("debug") + } + } +} + +flutter { + source = "../.." +} + +dependencies { + // CleverTap Core SDK — required for CleverTapAPI + display unit callbacks + implementation("com.clevertap.android:clevertap-android-sdk:8.0.0") + // Native Display Android SDK — provides NativeDisplayBridge + NativeDisplayUnit + implementation("com.clevertap.android:native-display-sdk") +} diff --git a/flutter-sample/android/app/src/debug/AndroidManifest.xml b/flutter-sample/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/flutter-sample/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/flutter-sample/android/app/src/main/AndroidManifest.xml b/flutter-sample/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..9146906 --- /dev/null +++ b/flutter-sample/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/flutter-sample/android/app/src/main/kotlin/com/clevertap/flutter/clevertap_native_display_sample/MainActivity.kt b/flutter-sample/android/app/src/main/kotlin/com/clevertap/flutter/clevertap_native_display_sample/MainActivity.kt new file mode 100644 index 0000000..57ca1f8 --- /dev/null +++ b/flutter-sample/android/app/src/main/kotlin/com/clevertap/flutter/clevertap_native_display_sample/MainActivity.kt @@ -0,0 +1,63 @@ +package com.clevertap.flutter.clevertap_native_display_sample + +import android.os.Handler +import android.os.Looper +import com.clevertap.android.nativedisplay.bridge.NativeDisplayUnit +import io.flutter.embedding.android.FlutterActivity +import io.flutter.embedding.engine.FlutterEngine +import io.flutter.plugin.common.EventChannel +import io.flutter.plugin.common.MethodChannel + +class MainActivity : FlutterActivity() { + + private var eventSink: EventChannel.EventSink? = null + private val mainHandler = Handler(Looper.getMainLooper()) + + companion object { + /** MethodChannel for Dart → native calls (pushEvent). */ + private const val METHOD_CH = "com.clevertap.flutter/native_display" + + /** EventChannel for native → Dart push (units_updated). */ + private const val EVENT_CH = "com.clevertap.flutter/native_display_events" + } + + override fun configureFlutterEngine(flutterEngine: FlutterEngine) { + super.configureFlutterEngine(flutterEngine) + + // EventChannel: push display units to Dart when they arrive + EventChannel(flutterEngine.dartExecutor.binaryMessenger, EVENT_CH) + .setStreamHandler(object : EventChannel.StreamHandler { + override fun onListen(arguments: Any?, events: EventChannel.EventSink?) { + eventSink = events + } + override fun onCancel(arguments: Any?) { + eventSink = null + } + }) + + // MethodChannel: handle pushEvent calls from Dart + MethodChannel(flutterEngine.dartExecutor.binaryMessenger, METHOD_CH) + .setMethodCallHandler { call, result -> + when (call.method) { + "pushEvent" -> { + val name = call.argument("eventName") ?: "" + if (name.isNotEmpty()) { + SampleApplication.cleverTapApi?.pushEvent(name) + } + result.success(null) + } + else -> result.notImplemented() + } + } + + // Register callback: future unit deliveries from SampleApplication go to Flutter + SampleApplication.onUnitsLoaded = { units -> pushUnitsToFlutter(units) } + } + + private fun pushUnitsToFlutter(units: List) { + val jsonList = units.mapNotNull { it.rawJson }.filter { it.isNotEmpty() } + mainHandler.post { + eventSink?.success(mapOf("type" to "units_updated", "units" to jsonList)) + } + } +} diff --git a/flutter-sample/android/app/src/main/kotlin/com/clevertap/flutter/clevertap_native_display_sample/SampleApplication.kt b/flutter-sample/android/app/src/main/kotlin/com/clevertap/flutter/clevertap_native_display_sample/SampleApplication.kt new file mode 100644 index 0000000..e2c7a42 --- /dev/null +++ b/flutter-sample/android/app/src/main/kotlin/com/clevertap/flutter/clevertap_native_display_sample/SampleApplication.kt @@ -0,0 +1,65 @@ +package com.clevertap.flutter.clevertap_native_display_sample + +import android.app.Application +import android.util.Log +import com.clevertap.android.nativedisplay.bridge.NativeDisplayBridge +import com.clevertap.android.nativedisplay.bridge.NativeDisplayBridgeListener +import com.clevertap.android.nativedisplay.bridge.NativeDisplayUnit +import com.clevertap.android.sdk.ActivityLifecycleCallback +import com.clevertap.android.sdk.CleverTapAPI +import com.clevertap.android.sdk.CleverTapAPI.LogLevel.VERBOSE + +/** + * Sample application. + * + * Initializes CleverTap and the NativeDisplayBridge at app startup so that + * MainActivity can observe display units and push them to Flutter via EventChannel. + */ +class SampleApplication : Application() { + + companion object { + private const val TAG = "SampleApplication" + + var cleverTapApi: CleverTapAPI? = null + var nativeDisplayBridge: NativeDisplayBridge? = null + + /** + * Set by MainActivity once the EventChannel sink is ready. + * Called on the main thread whenever new units arrive. + */ + var onUnitsLoaded: ((List) -> Unit)? = null + } + + override fun onCreate() { + CleverTapAPI.setDebugLevel(VERBOSE) + ActivityLifecycleCallback.register(this) + super.onCreate() + + // Initialize NativeDisplayBridge (auto-wire mode) + val bridge = NativeDisplayBridge.initialize(this) + nativeDisplayBridge = bridge + + // Get CleverTap default instance (auto-created from manifest metadata) + val ct = CleverTapAPI.getDefaultInstance(this) + cleverTapApi = ct + + if (ct != null) { + // Bind bridge to CleverTap — wires display unit callbacks + bridge.bind(ct) + + // Register listener — delivers units to MainActivity's EventChannel + bridge.addListener(object : NativeDisplayBridgeListener { + override fun onNativeDisplaysLoaded(units: List) { + Log.d(TAG, "Received ${units.size} Native Display unit(s)") + onUnitsLoaded?.invoke(units) + } + }) + + // Request Native Display units from server + bridge.fetchNativeDisplays(ct) + Log.d(TAG, "Bridge initialized, bound, and fetch requested") + } else { + Log.w(TAG, "CleverTapAPI.getDefaultInstance() returned null — check manifest metadata") + } + } +} diff --git a/flutter-sample/android/app/src/main/res/drawable-v21/launch_background.xml b/flutter-sample/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/flutter-sample/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/flutter-sample/android/app/src/main/res/drawable/launch_background.xml b/flutter-sample/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/flutter-sample/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/flutter-sample/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/flutter-sample/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..db77bb4b7b0906d62b1847e87f15cdcacf6a4f29 GIT binary patch literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ literal 0 HcmV?d00001 diff --git a/flutter-sample/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/flutter-sample/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..17987b79bb8a35cc66c3c1fd44f5a5526c1b78be GIT binary patch literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ literal 0 HcmV?d00001 diff --git a/flutter-sample/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/flutter-sample/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..d5f1c8d34e7a88e3f88bea192c3a370d44689c3c GIT binary patch literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof literal 0 HcmV?d00001 diff --git a/flutter-sample/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/flutter-sample/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..4d6372eebdb28e45604e46eeda8dd24651419bc0 GIT binary patch literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` literal 0 HcmV?d00001 diff --git a/flutter-sample/android/app/src/main/res/values-night/styles.xml b/flutter-sample/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/flutter-sample/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/flutter-sample/android/app/src/main/res/values/styles.xml b/flutter-sample/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/flutter-sample/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/flutter-sample/android/app/src/profile/AndroidManifest.xml b/flutter-sample/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/flutter-sample/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/flutter-sample/android/build.gradle.kts b/flutter-sample/android/build.gradle.kts new file mode 100644 index 0000000..89176ef --- /dev/null +++ b/flutter-sample/android/build.gradle.kts @@ -0,0 +1,21 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/flutter-sample/android/gradle.properties b/flutter-sample/android/gradle.properties new file mode 100644 index 0000000..f018a61 --- /dev/null +++ b/flutter-sample/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true +android.enableJetifier=true diff --git a/flutter-sample/android/gradle/wrapper/gradle-wrapper.properties b/flutter-sample/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..afa1e8e --- /dev/null +++ b/flutter-sample/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip diff --git a/flutter-sample/android/settings.gradle.kts b/flutter-sample/android/settings.gradle.kts new file mode 100644 index 0000000..042d0b6 --- /dev/null +++ b/flutter-sample/android/settings.gradle.kts @@ -0,0 +1,32 @@ +pluginManagement { + val flutterSdkPath = run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.7.0" apply false + id("org.jetbrains.kotlin.android") version "1.8.22" apply false +} + +include(":app") + +// Include the local Android SDK so SampleApplication can use NativeDisplayBridge +includeBuild("../../../android") { + dependencySubstitution { + substitute(module("com.clevertap.android:native-display-sdk")).using(project(":sdk")) + } +} diff --git a/flutter-sample/ios/.gitignore b/flutter-sample/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/flutter-sample/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/flutter-sample/ios/Flutter/AppFrameworkInfo.plist b/flutter-sample/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..7c56964 --- /dev/null +++ b/flutter-sample/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 12.0 + + diff --git a/flutter-sample/ios/Flutter/Debug.xcconfig b/flutter-sample/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..ec97fc6 --- /dev/null +++ b/flutter-sample/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/flutter-sample/ios/Flutter/Release.xcconfig b/flutter-sample/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..c4855bf --- /dev/null +++ b/flutter-sample/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/flutter-sample/ios/Podfile b/flutter-sample/ios/Podfile new file mode 100644 index 0000000..352cc4e --- /dev/null +++ b/flutter-sample/ios/Podfile @@ -0,0 +1,49 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '12.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + + # CleverTap Core iOS SDK — required for CleverTap.autoIntegrate() + display unit callbacks + pod 'CleverTap-iOS-SDK', '~> 7.0' + # Local Native Display SDK — provides NativeDisplayBridge + NativeDisplayView + pod 'CleverTapNativeDisplay', :path => '../../../ios' + + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/flutter-sample/ios/Runner.xcodeproj/project.pbxproj b/flutter-sample/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..1877aee --- /dev/null +++ b/flutter-sample/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,619 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = F85JVN5546; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.clevertap.flutter.clevertapNativeDisplaySample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.clevertap.flutter.clevertapNativeDisplaySample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.clevertap.flutter.clevertapNativeDisplaySample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.clevertap.flutter.clevertapNativeDisplaySample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = F85JVN5546; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.clevertap.flutter.clevertapNativeDisplaySample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = F85JVN5546; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.clevertap.flutter.clevertapNativeDisplaySample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/flutter-sample/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/flutter-sample/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..15cada4 --- /dev/null +++ b/flutter-sample/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/flutter-sample/ios/Runner/AppDelegate.swift b/flutter-sample/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..9efc57b --- /dev/null +++ b/flutter-sample/ios/Runner/AppDelegate.swift @@ -0,0 +1,29 @@ +import Flutter +import UIKit +import CleverTapSDK +import CleverTapNativeDisplay + +@main +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + // Initialize CleverTap (reads credentials from Info.plist) + CleverTap.setDebugLevel(2) + CleverTap.autoIntegrate() + + // Bind NativeDisplayBridge to CleverTap and request display units + let bridge = NativeDisplayBridge.shared + if let ct = CleverTap.sharedInstance() { + bridge.bind(ct) + bridge.fetchNativeDisplays(ct) + print("[AppDelegate] Bridge bound and fetch requested") + } else { + print("[AppDelegate] CleverTap not configured — check Info.plist credentials") + } + + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..dc9ada4725e9b0ddb1deab583e5b5102493aa332 GIT binary patch literal 10932 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 literal 0 HcmV?d00001 diff --git a/flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..797d452e458972bab9d994556c8305db4c827017 GIT binary patch literal 406 zcmV;H0crk;P))>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c literal 0 HcmV?d00001 diff --git a/flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..6ed2d933e1120817fe9182483a228007b18ab6ae GIT binary patch literal 450 zcmV;z0X_bSP)iGWQ_5NJQ_~rNh*z)}eT%KUb z`7gNk0#AwF^#0T0?hIa^`~Ck;!}#m+_uT050aTR(J!bU#|IzRL%^UsMS#KsYnTF*!YeDOytlP4VhV?b} z%rz_<=#CPc)tU1MZTq~*2=8~iZ!lSa<{9b@2Jl;?IEV8)=fG217*|@)CCYgFze-x? zIFODUIA>nWKpE+bn~n7;-89sa>#DR>TSlqWk*!2hSN6D~Qb#VqbP~4Fk&m`@1$JGr zXPIdeRE&b2Thd#{MtDK$px*d3-Wx``>!oimf%|A-&-q*6KAH)e$3|6JV%HX{Hig)k suLT-RhftRq8b9;(V=235Wa|I=027H2wCDra;{X5v07*qoM6N<$f;9x^2LJ#7 literal 0 HcmV?d00001 diff --git a/flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..4cd7b0099ca80c806f8fe495613e8d6c69460d76 GIT binary patch literal 282 zcmV+#0p(^bcu7P-R4C8Q z&e;xxFbF_Vrezo%_kH*OKhshZ6BFpG-Y1e10`QXJKbND7AMQ&cMj60B5TNObaZxYybcN07*qoM6N<$g3m;S%K!iX literal 0 HcmV?d00001 diff --git a/flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..fe730945a01f64a61e2235dbe3f45b08f7729182 GIT binary patch literal 462 zcmV;<0WtoGP)-}iV`2<;=$?g5M=KQbZ{F&YRNy7Nn@%_*5{gvDM0aKI4?ESmw z{NnZg)A0R`+4?NF_RZexyVB&^^ZvN!{I28tr{Vje;QNTz`dG&Jz0~Ek&f2;*Z7>B|cg}xYpxEFY+0YrKLF;^Q+-HreN0P{&i zK~zY`?b7ECf-n?@;d<&orQ*Q7KoR%4|C>{W^h6@&01>0SKS`dn{Q}GT%Qj_{PLZ_& zs`MFI#j-(>?bvdZ!8^xTwlY{qA)T4QLbY@j(!YJ7aXJervHy6HaG_2SB`6CC{He}f zHVw(fJWApwPq!6VY7r1w-Fs)@ox~N+q|w~e;JI~C4Vf^@d>Wvj=fl`^u9x9wd9 zR%3*Q+)t%S!MU_`id^@&Y{y7-r98lZX0?YrHlfmwb?#}^1b{8g&KzmkE(L>Z&)179 zp<)v6Y}pRl100G2FL_t(o!|l{-Q-VMg#&MKg7c{O0 z2wJImOS3Gy*Z2Qifdv~JYOp;v+U)a|nLoc7hNH;I$;lzDt$}rkaFw1mYK5_0Q(Sut zvbEloxON7$+HSOgC9Z8ltuC&0OSF!-mXv5caV>#bc3@hBPX@I$58-z}(ZZE!t-aOG zpjNkbau@>yEzH(5Yj4kZiMH32XI!4~gVXNnjAvRx;Sdg^`>2DpUEwoMhTs_st8pKG z(%SHyHdU&v%f36~uERh!bd`!T2dw;z6PrOTQ7Vt*#9F2uHlUVnb#ev_o^fh}Dzmq} zWtlk35}k=?xj28uO|5>>$yXadTUE@@IPpgH`gJ~Ro4>jd1IF|(+IX>8M4Ps{PNvmI zNj4D+XgN83gPt_Gm}`Ybv{;+&yu-C(Grdiahmo~BjG-l&mWM+{e5M1sm&=xduwgM9 z`8OEh`=F3r`^E{n_;%9weN{cf2%7=VzC@cYj+lg>+3|D|_1C@{hcU(DyQG_BvBWe? zvTv``=%b1zrol#=R`JB)>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c literal 0 HcmV?d00001 diff --git a/flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..502f463a9bc882b461c96aadf492d1729e49e725 GIT binary patch literal 586 zcmV-Q0=4~#P)+}#`wDE{8-2Mebf5<{{PqV{TgVcv*r8?UZ3{-|G?_}T*&y;@cqf{ z{Q*~+qr%%p!1pS*_Uicl#q9lc(D`!D`LN62sNwq{oYw(Wmhk)k<@f$!$@ng~_5)Ru z0Z)trIA5^j{DIW^c+vT2%lW+2<(RtE2wR;4O@)Tm`Xr*?A(qYoM}7i5Yxw>D(&6ou zxz!_Xr~yNF+waPe00049Nkl*;a!v6h%{rlvIH#gW3s8p;bFr=l}mRqpW2h zw=OA%hdyL~z+UHOzl0eKhEr$YYOL-c-%Y<)=j?(bzDweB7{b+%_ypvm_cG{SvM=DK zhv{K@m>#Bw>2W$eUI#iU)Wdgs8Y3U+A$Gd&{+j)d)BmGKx+43U_!tik_YlN)>$7G! zhkE!s;%oku3;IwG3U^2kw?z+HM)jB{@zFhK8P#KMSytSthr+4!c(5c%+^UBn`0X*2 zy3(k600_CSZj?O$Qu%&$;|TGUJrptR(HzyIx>5E(2r{eA(<6t3e3I0B)7d6s7?Z5J zZ!rtKvA{MiEBm&KFtoifx>5P^Z=vl)95XJn()aS5%ad(s?4-=Tkis9IGu{`Fy8r+H07*qoM6N<$f20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU literal 0 HcmV?d00001 diff --git a/flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0ec303439225b78712f49115768196d8d76f6790 GIT binary patch literal 862 zcmV-k1EKthP)20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU literal 0 HcmV?d00001 diff --git a/flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..e9f5fea27c705180eb716271f41b582e76dcbd90 GIT binary patch literal 1674 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 literal 0 HcmV?d00001 diff --git a/flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0467bf12aa4d28f374bb26596605a46dcbb3e7c8 GIT binary patch literal 1418 zcmV;51$Fv~P)q zKfU)WzW*n(@|xWGCA9ScMt*e9`2kdxPQ&&>|-UCa7_51w+ zLUsW@ZzZSW0y$)Hp~e9%PvP|a03ks1`~K?q{u;6NC8*{AOqIUq{CL&;p56Lf$oQGq z^={4hPQv)y=I|4n+?>7Fim=dxt1 z2H+Dm+1+fh+IF>G0SjJMkQQre1x4|G*Z==(Ot&kCnUrL4I(rf(ucITwmuHf^hXiJT zkdTm&kdTm&kdTm&kdP`esgWG0BcWCVkVZ&2dUwN`cgM8QJb`Z7Z~e<&Yj2(}>Tmf` zm1{eLgw!b{bXkjWbF%dTkTZEJWyWOb##Lfw4EK2}<0d6%>AGS{po>WCOy&f$Tay_> z?NBlkpo@s-O;0V%Y_Xa-G#_O08q5LR*~F%&)}{}r&L%Sbs8AS4t7Y0NEx*{soY=0MZExqA5XHQkqi#4gW3 zqODM^iyZl;dvf)-bOXtOru(s)Uc7~BFx{w-FK;2{`VA?(g&@3z&bfLFyctOH!cVsF z7IL=fo-qBndRUm;kAdXR4e6>k-z|21AaN%ubeVrHl*<|s&Ax@W-t?LR(P-24A5=>a z*R9#QvjzF8n%@1Nw@?CG@6(%>+-0ASK~jEmCV|&a*7-GKT72W<(TbSjf)&Eme6nGE z>Gkj4Sq&2e+-G%|+NM8OOm5zVl9{Z8Dd8A5z3y8mZ=4Bv4%>as_{9cN#bm~;h>62( zdqY93Zy}v&c4n($Vv!UybR8ocs7#zbfX1IY-*w~)p}XyZ-SFC~4w>BvMVr`dFbelV{lLL0bx7@*ZZdebr3`sP;? zVImji)kG)(6Juv0lz@q`F!k1FE;CQ(D0iG$wchPbKZQELlsZ#~rt8#90Y_Xh&3U-< z{s<&cCV_1`^TD^ia9!*mQDq& zn2{r`j};V|uV%_wsP!zB?m%;FeaRe+X47K0e+KE!8C{gAWF8)lCd1u1%~|M!XNRvw zvtqy3iz0WSpWdhn6$hP8PaRBmp)q`#PCA`Vd#Tc$@f1tAcM>f_I@bC)hkI9|o(Iqv zo}Piadq!j76}004RBio<`)70k^`K1NK)q>w?p^C6J2ZC!+UppiK6&y3Kmbv&O!oYF z34$0Z;QO!JOY#!`qyGH<3Pd}Pt@q*A0V=3SVtWKRR8d8Z&@)3qLPA19LPA19LPEUC YUoZo%k(ykuW&i*H07*qoM6N<$f+CH{y8r+H literal 0 HcmV?d00001 diff --git a/flutter-sample/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/flutter-sample/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/flutter-sample/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/flutter-sample/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/flutter-sample/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/flutter-sample/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/flutter-sample/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/flutter-sample/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/flutter-sample/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/flutter-sample/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/flutter-sample/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/flutter-sample/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/flutter-sample/ios/Runner/Base.lproj/LaunchScreen.storyboard b/flutter-sample/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/flutter-sample/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/flutter-sample/ios/Runner/Base.lproj/Main.storyboard b/flutter-sample/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/flutter-sample/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/flutter-sample/ios/Runner/Info.plist b/flutter-sample/ios/Runner/Info.plist new file mode 100644 index 0000000..fbe43ac --- /dev/null +++ b/flutter-sample/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Clevertap Native Display Sample + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + clevertap_native_display_sample + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/flutter-sample/ios/Runner/Runner-Bridging-Header.h b/flutter-sample/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/flutter-sample/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/flutter-sample/ios/RunnerTests/RunnerTests.swift b/flutter-sample/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/flutter-sample/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/flutter-sample/lib/app.dart b/flutter-sample/lib/app.dart index a937567..1364047 100644 --- a/flutter-sample/lib/app.dart +++ b/flutter-sample/lib/app.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; -import 'screens/banner_showcase_screen.dart'; +import 'screens/clevertap_integration_screen.dart'; +import 'screens/slot_demo_screen.dart'; import 'screens/test_browser_screen.dart'; -import 'screens/other_demos_screen.dart'; import 'screens/more_menu_screen.dart'; class NativeDisplaySampleApp extends StatelessWidget { @@ -31,27 +31,25 @@ class MainScreen extends StatefulWidget { class _MainScreenState extends State { int _selectedTab = 0; - static const _tabs = [ - BannerShowcaseScreen(), - TestBrowserScreen(), - OtherDemosScreen(), - MoreMenuScreen(), - ]; - @override Widget build(BuildContext context) { return Scaffold( body: IndexedStack( index: _selectedTab, - children: _tabs, + children: const [ + CleverTapIntegrationScreen(), + SlotDemoScreen(), + TestBrowserScreen(), + MoreMenuScreen(), + ], ), bottomNavigationBar: NavigationBar( selectedIndex: _selectedTab, onDestinationSelected: (i) => setState(() => _selectedTab = i), destinations: const [ - NavigationDestination(icon: Icon(Icons.image), label: 'Banners'), + NavigationDestination(icon: Icon(Icons.wifi_tethering), label: 'Events'), + NavigationDestination(icon: Icon(Icons.view_stream), label: 'Slots'), NavigationDestination(icon: Icon(Icons.science), label: 'Browser'), - NavigationDestination(icon: Icon(Icons.grid_view), label: 'Demos'), NavigationDestination(icon: Icon(Icons.more_horiz), label: 'More'), ], ), diff --git a/flutter-sample/lib/screens/bridge_integration_screen.dart b/flutter-sample/lib/screens/bridge_integration_screen.dart index f6a38c1..1e609eb 100644 --- a/flutter-sample/lib/screens/bridge_integration_screen.dart +++ b/flutter-sample/lib/screens/bridge_integration_screen.dart @@ -34,13 +34,13 @@ class BridgeIntegrationScreen extends StatelessWidget { ?.copyWith(color: const Color(0xFF666666)), ), const Divider(height: 32), - _ConfigSection( + const _ConfigSection( title: 'Mock Product Unit', description: 'Simulates a product display unit delivered by the Core SDK.', assetPath: 'assets/configs/bridge_mock_product.json', ), const SizedBox(height: 24), - _ConfigSection( + const _ConfigSection( title: 'Mock Notification Unit', description: 'Simulates a notification-style display unit.', assetPath: 'assets/configs/bridge_mock_notification.json', @@ -138,9 +138,9 @@ await NativeDisplayBridge.pushClickedEvent(unitId, elementId: nodeId);'''; color: const Color(0xFFE3F2FD), borderRadius: BorderRadius.circular(8), ), - child: SelectableText( + child: const SelectableText( snippet, - style: const TextStyle( + style: TextStyle( fontFamily: 'monospace', fontSize: 12, color: Color(0xFF1565C0), diff --git a/flutter-sample/lib/screens/clevertap_integration_screen.dart b/flutter-sample/lib/screens/clevertap_integration_screen.dart new file mode 100644 index 0000000..62ef9b8 --- /dev/null +++ b/flutter-sample/lib/screens/clevertap_integration_screen.dart @@ -0,0 +1,294 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:clevertap_native_display/clevertap_native_display.dart'; + +/// Mirrors the Android CleverTapIntegrationScreen. +/// Subscribes to NativeDisplayBridge.eventStream to receive display units pushed +/// from the native CleverTap Core SDK and renders them via NativeDisplayView. +class CleverTapIntegrationScreen extends StatefulWidget { + const CleverTapIntegrationScreen({super.key}); + + @override + State createState() => _CleverTapIntegrationScreenState(); +} + +class _CleverTapIntegrationScreenState extends State { + final _eventController = TextEditingController(); + final List _log = []; + final List _units = []; + StreamSubscription>? _unitSubscription; + + @override + void initState() { + super.initState(); + _subscribeToUnits(); + } + + @override + void dispose() { + _unitSubscription?.cancel(); + _eventController.dispose(); + super.dispose(); + } + + void _log_(String msg) { + final now = TimeOfDay.now(); + final ts = '[${now.hour.toString().padLeft(2, '0')}:${now.minute.toString().padLeft(2, '0')}]'; + setState(() => _log.add('$ts $msg')); + } + + void _subscribeToUnits() { + _unitSubscription = NativeDisplayBridge.eventStream.listen( + (event) { + if (event['type'] == 'units_updated') { + final rawUnits = (event['units'] as List?)?.cast() ?? []; + final configs = []; + for (final jsonStr in rawUnits) { + if (jsonStr.isEmpty) continue; + try { + final map = jsonDecode(jsonStr) as Map; + configs.add(NativeDisplayConfig.fromJson(map)); + } catch (e) { + _log_('ERROR parsing unit: $e'); + } + } + setState(() { + _units + ..clear() + ..addAll(configs); + _log.add( + '[${_timestamp()}] Received ${configs.length} Native Display unit(s)', + ); + }); + } + }, + onError: (Object e) => _log_('ERROR from event stream: $e'), + ); + } + + String _timestamp() { + final now = TimeOfDay.now(); + return '${now.hour.toString().padLeft(2, '0')}:${now.minute.toString().padLeft(2, '0')}'; + } + + Future _sendEvent() async { + final name = _eventController.text.trim(); + if (name.isEmpty) return; + await NativeDisplayBridge.pushEvent(name); + _log_('Fired event: $name'); + _eventController.clear(); + setState(() {}); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('CleverTap Integration'), + backgroundColor: Theme.of(context).colorScheme.primary, + foregroundColor: Theme.of(context).colorScheme.onPrimary, + ), + body: Column( + children: [ + _FireEventHeader( + controller: _eventController, + onSend: _sendEvent, + isSendEnabled: _eventController.text.isNotEmpty, + eventController: _eventController, + onChanged: () => setState(() {}), + ), + Expanded(child: _CanvasContent(units: _units, onAction: _log_)), + _EventLogFooter(messages: _log, onClear: () => setState(() => _log.clear())), + ], + ), + ); + } +} + +class _FireEventHeader extends StatelessWidget { + final TextEditingController controller; + final VoidCallback onSend; + final bool isSendEnabled; + final TextEditingController eventController; + final VoidCallback onChanged; + + const _FireEventHeader({ + required this.controller, + required this.onSend, + required this.isSendEnabled, + required this.eventController, + required this.onChanged, + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.fromLTRB(16, 12, 16, 0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: TextField( + controller: controller, + decoration: const InputDecoration( + hintText: 'Enter event name', + border: OutlineInputBorder(), + isDense: true, + contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 10), + ), + onChanged: (_) => onChanged(), + onSubmitted: (_) => onSend(), + ), + ), + const SizedBox(width: 8), + FilledButton( + onPressed: isSendEnabled ? onSend : null, + child: const Text('Send Event'), + ), + ], + ), + const SizedBox(height: 12), + const Divider(height: 1), + const Padding( + padding: EdgeInsets.symmetric(vertical: 8), + child: Text( + 'Native Display Canvas', + style: TextStyle(fontWeight: FontWeight.w600, fontSize: 13), + ), + ), + ], + ), + ); + } +} + +class _CanvasContent extends StatelessWidget { + final List units; + final void Function(String) onAction; + + const _CanvasContent({required this.units, required this.onAction}); + + @override + Widget build(BuildContext context) { + if (units.isEmpty) { + return Center( + child: Text( + 'Waiting for Native Display response…', + style: TextStyle(color: Colors.grey[500], fontSize: 14), + ), + ); + } + return ListView.builder( + itemCount: units.length, + itemBuilder: (ctx, i) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: NativeDisplayView( + config: units[i], + actionListener: (action, nodeId, params) => onAction('ACTION $action on $nodeId'), + ), + ), + ); + } +} + +class _EventLogFooter extends StatefulWidget { + final List messages; + final VoidCallback onClear; + + const _EventLogFooter({required this.messages, required this.onClear}); + + @override + State<_EventLogFooter> createState() => _EventLogFooterState(); +} + +class _EventLogFooterState extends State<_EventLogFooter> { + final _scrollController = ScrollController(); + + @override + void didUpdateWidget(_EventLogFooter old) { + super.didUpdateWidget(old); + if (widget.messages.length != old.messages.length) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (_scrollController.hasClients) { + _scrollController.animateTo( + _scrollController.position.maxScrollExtent, + duration: const Duration(milliseconds: 200), + curve: Curves.easeOut, + ); + } + }); + } + } + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } + + Color _msgColor(String msg) { + if (msg.contains('EVENT')) return const Color(0xFFFFD54F); + if (msg.contains('ACTION')) return const Color(0xFF81D4FA); + if (msg.contains('ERROR')) return const Color(0xFFEF9A9A); + if (msg.contains('Received')) return const Color(0xFFA5D6A7); + return const Color(0xFF80CBC4); + } + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.fromLTRB(16, 0, 16, 8), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + const Divider(height: 1), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text('Event Log', style: TextStyle(fontWeight: FontWeight.w600, fontSize: 13)), + if (widget.messages.isNotEmpty) + TextButton(onPressed: widget.onClear, child: const Text('Clear', style: TextStyle(fontSize: 12))), + ], + ), + Container( + height: 130, + width: double.infinity, + decoration: BoxDecoration( + color: const Color(0xFF263238), + borderRadius: BorderRadius.circular(8), + ), + padding: const EdgeInsets.all(10), + child: widget.messages.isEmpty + ? const Text( + 'No events yet', + style: TextStyle( + fontFamily: 'monospace', + fontSize: 12, + color: Color(0xFF607D8B), + ), + ) + : ListView.builder( + controller: _scrollController, + itemCount: widget.messages.length, + itemBuilder: (ctx, i) => Text( + widget.messages[i], + style: TextStyle( + fontFamily: 'monospace', + fontSize: 11, + color: _msgColor(widget.messages[i]), + height: 1.4, + ), + ), + ), + ), + const SizedBox(height: 8), + ], + ), + ); + } +} diff --git a/flutter-sample/lib/screens/more_menu_screen.dart b/flutter-sample/lib/screens/more_menu_screen.dart index dcea7f3..6f8eaab 100644 --- a/flutter-sample/lib/screens/more_menu_screen.dart +++ b/flutter-sample/lib/screens/more_menu_screen.dart @@ -5,28 +5,28 @@ import 'arrangement_demo_screen.dart'; import 'animation_demo_screen.dart'; import 'home_screen_demo.dart'; import 'bridge_integration_screen.dart'; -import 'slot_demo_screen.dart'; +import 'other_demos_screen.dart'; class MoreMenuScreen extends StatelessWidget { const MoreMenuScreen({super.key}); static const _items = [ + (Icons.link, 'Bridge Integration', 'Core SDK bridge demo with mock data'), (Icons.image_outlined, 'Banner Showcase', 'Browse all 10 pre-defined banners'), (Icons.align_horizontal_left, 'Arrangements', 'Explore all 7 arrangement strategies'), (Icons.auto_awesome, 'Animations', 'Container and element animations'), (Icons.house_outlined, 'Home Screen', 'Example home screen layout'), - (Icons.link, 'Bridge Integration', 'Core SDK bridge demo with mock data'), - (Icons.pin_drop_outlined, 'Slot Demo', 'Mixed content feed with native display slots'), + (Icons.grid_view, 'Other Demos', 'Gallery, E-commerce, Social, Dashboard'), ]; Widget _destinationFor(String title) { return switch (title) { + 'Bridge Integration' => const BridgeIntegrationScreen(), 'Banner Showcase' => const _FullScreenShell(title: 'Banner Showcase', child: BannerShowcaseScreen()), 'Arrangements' => const ArrangementDemoScreen(), 'Animations' => const AnimationDemoScreen(), 'Home Screen' => const _FullScreenShell(title: 'Home Screen', child: _HomeScreenShell()), - 'Bridge Integration' => const BridgeIntegrationScreen(), - 'Slot Demo' => const SlotDemoScreen(), + 'Other Demos' => const _FullScreenShell(title: 'Other Demos', child: OtherDemosScreen()), _ => const SizedBox.shrink(), }; } diff --git a/flutter-sample/lib/screens/other_demos_screen.dart b/flutter-sample/lib/screens/other_demos_screen.dart index d4c38ea..76a8aed 100644 --- a/flutter-sample/lib/screens/other_demos_screen.dart +++ b/flutter-sample/lib/screens/other_demos_screen.dart @@ -9,11 +9,11 @@ class OtherDemosScreen extends StatelessWidget { @override Widget build(BuildContext context) { - return DefaultTabController( + return const DefaultTabController( length: 5, child: Column( children: [ - const TabBar( + TabBar( isScrollable: true, tabAlignment: TabAlignment.start, tabs: [ @@ -24,7 +24,7 @@ class OtherDemosScreen extends StatelessWidget { Tab(text: 'Dashboard'), ], ), - const Expanded( + Expanded( child: TabBarView( children: [ _JsonTab(assetPath: 'assets/configs/home_screen.json'), diff --git a/flutter-sample/lib/screens/slot_demo_screen.dart b/flutter-sample/lib/screens/slot_demo_screen.dart index 63e60f7..95fea91 100644 --- a/flutter-sample/lib/screens/slot_demo_screen.dart +++ b/flutter-sample/lib/screens/slot_demo_screen.dart @@ -1,38 +1,99 @@ import 'package:flutter/material.dart'; -import '../widgets/nd_demo_card.dart'; +// --- Data models --- -/// Slot Demo — shows how multiple display units can be placed in a mixed content feed. -class SlotDemoScreen extends StatelessWidget { +class _AppItem { + final int id; + final String title; + final String subtitle; + final String imageUrl; + const _AppItem(this.id, this.title, this.subtitle, this.imageUrl); +} + +sealed class _FeedItem {} +class _ContentItem extends _FeedItem { + final _AppItem item; + _ContentItem(this.item); +} +class _SlotItem extends _FeedItem { + final String slotId; + _SlotItem(this.slotId); +} + +// --- Feed data --- + +const _appItems = [ + _AppItem(1, 'Morning Yoga Flow', '30 min · Beginner friendly', 'https://yavuzceliker.github.io/sample-images/image-1.jpg'), + _AppItem(2, 'Mediterranean Salad', 'Quick & healthy lunch recipe', 'https://yavuzceliker.github.io/sample-images/image-5.jpg'), + _AppItem(3, 'Productivity Hacks', '5 tips for focused work', 'https://yavuzceliker.github.io/sample-images/image-10.jpg'), + _AppItem(4, 'Trail Running Guide', 'Best routes near you', 'https://yavuzceliker.github.io/sample-images/image-15.jpg'), + _AppItem(5, 'Indoor Plants 101', 'Low-maintenance greenery', 'https://yavuzceliker.github.io/sample-images/image-20.jpg'), + _AppItem(6, 'Weekend Getaways', 'Top 10 road trip destinations', 'https://yavuzceliker.github.io/sample-images/image-25.jpg'), + _AppItem(7, 'Budget Meal Prep', 'Save time and money', 'https://yavuzceliker.github.io/sample-images/image-30.jpg'), + _AppItem(8, 'Home Workout', 'No equipment needed', 'https://yavuzceliker.github.io/sample-images/image-35.jpg'), + _AppItem(9, 'Coffee Brewing', 'Perfect pour-over technique', 'https://yavuzceliker.github.io/sample-images/image-40.jpg'), + _AppItem(10, 'Sleep Better', 'Science-backed tips', 'https://yavuzceliker.github.io/sample-images/image-45.jpg'), + _AppItem(11, 'Digital Detox', 'Unplug and recharge', 'https://yavuzceliker.github.io/sample-images/image-50.jpg'), + _AppItem(12, 'Book Club Picks', 'This month\'s top reads', 'https://yavuzceliker.github.io/sample-images/image-55.jpg'), + _AppItem(13, 'Smoothie Recipes', 'Fuel your morning', 'https://yavuzceliker.github.io/sample-images/image-60.jpg'), + _AppItem(14, 'Desk Stretches', 'Relieve tension in 5 min', 'https://yavuzceliker.github.io/sample-images/image-65.jpg'), + _AppItem(15, 'Mindful Breathing', 'Calm in 3 minutes', 'https://yavuzceliker.github.io/sample-images/image-70.jpg'), +]; + +List<_FeedItem> _buildFeed() => [ + _SlotItem('slot_top'), + ..._appItems.sublist(0, 3).map(_ContentItem.new), + _SlotItem('slot_feed_1'), + ..._appItems.sublist(3, 6).map(_ContentItem.new), + _SlotItem('slot_feed_2'), + ..._appItems.sublist(6, 15).map(_ContentItem.new), + _SlotItem('slot_bottom'), +]; + +// --- Screen --- + +class SlotDemoScreen extends StatefulWidget { const SlotDemoScreen({super.key}); - static const _slots = [ - ('Slot 1 — Banner', 'assets/banners/banner-01-hero-summer-sale.json'), - ('Slot 2 — Product', 'assets/banners/banner-02-product-iphone.json'), - ('Slot 3 — Promotion', 'assets/banners/banner-08-flash-sale.json'), - ]; + @override + State createState() => _SlotDemoScreenState(); +} + +class _SlotDemoScreenState extends State { + final _feed = _buildFeed(); + // In a real integration, slot configs would come from NativeDisplayBridge. + // Here we show dashed placeholders to demonstrate the layout. + + void _fetchSlotData() { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Integrate CleverTap Core SDK to receive live slot data'), + duration: Duration(seconds: 3), + ), + ); + } @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(title: const Text('Slot Demo')), + backgroundColor: const Color(0xFFF5F5F5), + appBar: AppBar( + title: const Text('Slot Demo'), + backgroundColor: Theme.of(context).colorScheme.primary, + foregroundColor: Theme.of(context).colorScheme.onPrimary, + ), body: ListView.builder( - padding: const EdgeInsets.all(16), - itemCount: _slots.length * 2 - 1, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + itemCount: _feed.length + 1, // +1 for header itemBuilder: (ctx, i) { - // Interleave slot cards with mock feed items - if (i.isOdd) return _MockFeedItem(index: i ~/ 2 + 1); - final (title, assetPath) = _slots[i ~/ 2]; + if (i == 0) return _Header(onFetch: _fetchSlotData); + final item = _feed[i - 1]; return Padding( - padding: const EdgeInsets.only(bottom: 16), - child: Card( - elevation: 2, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - child: Padding( - padding: const EdgeInsets.all(12), - child: NdDemoCard(assetPath: assetPath, title: title), - ), - ), + padding: const EdgeInsets.only(bottom: 12), + child: switch (item) { + _SlotItem s => _SlotPlaceholder(slotId: s.slotId), + _ContentItem c => _AppContentCard(item: c.item), + }, ); }, ), @@ -40,37 +101,159 @@ class SlotDemoScreen extends StatelessWidget { } } -class _MockFeedItem extends StatelessWidget { - final int index; +// --- Header --- - const _MockFeedItem({required this.index}); +class _Header extends StatelessWidget { + final VoidCallback onFetch; + const _Header({required this.onFetch}); @override Widget build(BuildContext context) { - return Container( - margin: const EdgeInsets.only(bottom: 16), - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(8), - border: Border.all(color: const Color(0xFFEEEEEE)), + return Padding( + padding: const EdgeInsets.only(bottom: 12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Slot Demo', + style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 4), + const Text( + 'This feed contains 4 NativeDisplaySlot views at fixed positions. ' + 'Tap the button below to fire a CleverTap event that fetches real server data for the slots.', + style: TextStyle(fontSize: 14, color: Color(0xFF666666)), + ), + const SizedBox(height: 12), + SizedBox( + width: double.infinity, + child: FilledButton( + onPressed: onFetch, + style: FilledButton.styleFrom( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), + ), + child: const Text('Fetch Slot Data'), + ), + ), + const SizedBox(height: 4), + ], ), + ); + } +} + +// --- Slot placeholder (dashed border, matches Android EmptySlotPlaceholder) --- + +class _SlotPlaceholder extends StatelessWidget { + final String slotId; + const _SlotPlaceholder({required this.slotId}); + + @override + Widget build(BuildContext context) { + return CustomPaint( + painter: _DashedBorderPainter(), + child: SizedBox( + height: 80, + width: double.infinity, + child: Center( + child: Text( + 'Ad', + style: TextStyle( + color: Colors.grey[500], + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + ); + } +} + +class _DashedBorderPainter extends CustomPainter { + @override + void paint(Canvas canvas, Size size) { + const radius = Radius.circular(8); + final rect = RRect.fromLTRBR(0, 0, size.width, size.height, radius); + final paint = Paint() + ..color = const Color(0xFFBDBDBD) + ..style = PaintingStyle.stroke + ..strokeWidth = 1; + + const dashLen = 8.0; + const gapLen = 4.0; + final path = Path()..addRRect(rect); + final pathMetrics = path.computeMetrics(); + for (final metric in pathMetrics) { + var distance = 0.0; + while (distance < metric.length) { + final end = (distance + dashLen).clamp(0.0, metric.length); + canvas.drawPath(metric.extractPath(distance, end), paint); + distance += dashLen + gapLen; + } + } + } + + @override + bool shouldRepaint(_DashedBorderPainter old) => false; +} + +// --- App content card --- + +class _AppContentCard extends StatelessWidget { + final _AppItem item; + const _AppContentCard({required this.item}); + + @override + Widget build(BuildContext context) { + return Card( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + color: Colors.white, + elevation: 2, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Container( - height: 12, - width: 120, - color: const Color(0xFFEEEEEE), + ClipRRect( + borderRadius: const BorderRadius.vertical(top: Radius.circular(12)), + child: Image.network( + item.imageUrl, + height: 180, + width: double.infinity, + fit: BoxFit.cover, + errorBuilder: (_, __, ___) => Container( + height: 180, + color: const Color(0xFFEEEEEE), + child: const Icon(Icons.image, color: Color(0xFFBBBBBB), size: 48), + ), + loadingBuilder: (_, child, progress) => progress == null + ? child + : Container( + height: 180, + color: const Color(0xFFF5F5F5), + child: const Center(child: CircularProgressIndicator(strokeWidth: 2)), + ), + ), ), - const SizedBox(height: 8), - Container(height: 12, color: const Color(0xFFEEEEEE)), - const SizedBox(height: 6), - Container(height: 12, width: 200, color: const Color(0xFFEEEEEE)), - const SizedBox(height: 6), - Text( - 'Mock feed item #$index', - style: const TextStyle(color: Color(0xFF999999), fontSize: 11), + Padding( + padding: const EdgeInsets.all(12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + item.title, + style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 15), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 4), + Text( + item.subtitle, + style: const TextStyle(color: Color(0xFF888888), fontSize: 13), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ), ), ], ), diff --git a/flutter-sample/test/widget_test.dart b/flutter-sample/test/widget_test.dart new file mode 100644 index 0000000..053fa18 --- /dev/null +++ b/flutter-sample/test/widget_test.dart @@ -0,0 +1,14 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('smoke test', (WidgetTester tester) async { + expect(true, isTrue); + }); +} diff --git a/flutter/android/build.gradle b/flutter/android/build.gradle new file mode 100644 index 0000000..b1c6a0f --- /dev/null +++ b/flutter/android/build.gradle @@ -0,0 +1,48 @@ +group 'com.clevertap.flutter.nativedisplay' +version '1.0-SNAPSHOT' + +buildscript { + repositories { + google() + mavenCentral() + } + dependencies { + classpath 'com.android.tools.build:gradle:8.3.2' + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + compileSdk 34 + namespace 'com.clevertap.flutter.nativedisplay' + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + minSdk 23 + } +} + +dependencies { + // Kotlin stdlib and Flutter embedding are provided by the host app's classpath +} diff --git a/flutter/android/src/main/AndroidManifest.xml b/flutter/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000..94cbbcf --- /dev/null +++ b/flutter/android/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/flutter/lib/src/bridge/native_display_bridge.dart b/flutter/lib/src/bridge/native_display_bridge.dart index 2e43e91..768265a 100644 --- a/flutter/lib/src/bridge/native_display_bridge.dart +++ b/flutter/lib/src/bridge/native_display_bridge.dart @@ -7,6 +7,8 @@ import '../models/native_display_config.dart'; class NativeDisplayBridge { static const _channel = MethodChannel('com.clevertap.flutter.nativedisplay'); + static const _events = EventChannel('com.clevertap.flutter/native_display_events'); + static const _sampleChannel = MethodChannel('com.clevertap.flutter/native_display'); // Fetch a display unit config JSON from the native CleverTap Core SDK by unitId. // Returns null when the unit is not found or an error occurs. @@ -42,4 +44,23 @@ class NativeDisplayBridge { debugPrint('[NativeDisplay] pushClickedEvent failed for $unitId: ${e.message}'); } } + + // Stream of events pushed from the native side (e.g. units_updated). + // Each event is a Map with at minimum a 'type' key. + static Stream> get eventStream => _events + .receiveBroadcastStream() + .where((e) => e is Map) + .map((e) => (e as Map).cast()); + + // Fire a CleverTap event by name via the native Core SDK. + // Used by the sample app's integration screen to send user-defined events. + static Future pushEvent(String eventName) async { + try { + await _sampleChannel.invokeMethod('pushEvent', {'eventName': eventName}); + } on PlatformException catch (e) { + debugPrint('[NativeDisplay] pushEvent failed for $eventName: ${e.message}'); + } catch (_) { + // Channel may not be set up in all host apps — silently ignore + } + } } diff --git a/flutter/lib/src/renderer/containers/gallery_renderer.dart b/flutter/lib/src/renderer/containers/gallery_renderer.dart index 363ebde..5261286 100644 --- a/flutter/lib/src/renderer/containers/gallery_renderer.dart +++ b/flutter/lib/src/renderer/containers/gallery_renderer.dart @@ -89,31 +89,42 @@ class _GalleryRendererState extends State { final config = _config; final children = widget.node.children; - Widget gallery = PageView.builder( - controller: _pageController, - scrollDirection: config.orientation == Orientation.vertical - ? Axis.vertical - : Axis.horizontal, - onPageChanged: (page) => setState(() => _currentPage = page), - itemCount: config.infiniteScroll ? null : children.length, - itemBuilder: (context, index) { - final child = children[index % children.length]; - Widget item = NativeDisplayRenderer( - node: child, - evaluator: widget.evaluator, - actionListener: widget.actionListener, - componentListener: widget.componentListener, + // PageView requires bounded height. Use LayoutBuilder; fall back to 200 if parent is unbounded. + Widget gallery = LayoutBuilder( + builder: (ctx, constraints) { + final h = constraints.maxHeight.isInfinite || constraints.maxHeight == 0 + ? 200.0 + : constraints.maxHeight; + return SizedBox( + height: h, + child: PageView.builder( + controller: _pageController, + scrollDirection: config.orientation == Orientation.vertical + ? Axis.vertical + : Axis.horizontal, + onPageChanged: (page) => setState(() => _currentPage = page), + itemCount: config.infiniteScroll ? null : children.length, + itemBuilder: (context, index) { + final child = children[index % children.length]; + Widget item = NativeDisplayRenderer( + node: child, + evaluator: widget.evaluator, + actionListener: widget.actionListener, + componentListener: widget.componentListener, + ); + if (config.spacing > 0) { + final isVertical = config.orientation == Orientation.vertical; + item = Padding( + padding: isVertical + ? EdgeInsets.symmetric(vertical: config.spacing / 2) + : EdgeInsets.symmetric(horizontal: config.spacing / 2), + child: item, + ); + } + return item; + }, + ), ); - if (config.spacing > 0) { - final isVertical = config.orientation == Orientation.vertical; - item = Padding( - padding: isVertical - ? EdgeInsets.symmetric(vertical: config.spacing / 2) - : EdgeInsets.symmetric(horizontal: config.spacing / 2), - child: item, - ); - } - return item; }, ); From c3e371a25c68283b7f933b95df832d18a9782927 Mon Sep 17 00:00:00 2001 From: CTLalit <144685420+CTLalit@users.noreply.github.com> Date: Tue, 26 May 2026 12:59:47 +0530 Subject: [PATCH 07/12] =?UTF-8?q?SDK-5835:=20Fix=20Android=20build=20?= =?UTF-8?q?=E2=80=94=20use=20CT=20Core=20SDK=20DisplayUnitListener,=20drop?= =?UTF-8?q?=20composite=20build?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem: flutter-sample referenced clevertap-native-ui-kit (compiled Kotlin 2.1.0) via includeBuild, forcing Kotlin 2.1.0 + AGP 8.9.1 + Gradle 8.11.1 on the sample. Fix: remove includeBuild and the native-display-sdk dependency entirely. Subscribe to display unit callbacks via CleverTapAPI.setDisplayUnitListener(DisplayUnitListener) from clevertap-android-sdk:8.0.0 (Java interface — no Kotlin binary compat issue). Forward CleverTapDisplayUnit.getJsonObject().toString() directly to Flutter via EventChannel. Restores original Flutter scaffold versions: Kotlin 1.8.22, AGP 8.7.0, Gradle 8.10.2. Co-Authored-By: Claude Sonnet 4.6 --- flutter-sample/android/app/build.gradle.kts | 4 +- .../MainActivity.kt | 12 +----- .../SampleApplication.kt | 43 +++++-------------- flutter-sample/android/settings.gradle.kts | 7 --- 4 files changed, 14 insertions(+), 52 deletions(-) diff --git a/flutter-sample/android/app/build.gradle.kts b/flutter-sample/android/app/build.gradle.kts index 0ac6c45..38e707b 100644 --- a/flutter-sample/android/app/build.gradle.kts +++ b/flutter-sample/android/app/build.gradle.kts @@ -44,8 +44,6 @@ flutter { } dependencies { - // CleverTap Core SDK — required for CleverTapAPI + display unit callbacks + // CleverTap Core SDK — provides CleverTapAPI, CTDisplayUnitListener, CleverTapDisplayUnit implementation("com.clevertap.android:clevertap-android-sdk:8.0.0") - // Native Display Android SDK — provides NativeDisplayBridge + NativeDisplayUnit - implementation("com.clevertap.android:native-display-sdk") } diff --git a/flutter-sample/android/app/src/main/kotlin/com/clevertap/flutter/clevertap_native_display_sample/MainActivity.kt b/flutter-sample/android/app/src/main/kotlin/com/clevertap/flutter/clevertap_native_display_sample/MainActivity.kt index 57ca1f8..65688cf 100644 --- a/flutter-sample/android/app/src/main/kotlin/com/clevertap/flutter/clevertap_native_display_sample/MainActivity.kt +++ b/flutter-sample/android/app/src/main/kotlin/com/clevertap/flutter/clevertap_native_display_sample/MainActivity.kt @@ -2,7 +2,6 @@ package com.clevertap.flutter.clevertap_native_display_sample import android.os.Handler import android.os.Looper -import com.clevertap.android.nativedisplay.bridge.NativeDisplayUnit import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.EventChannel @@ -14,17 +13,13 @@ class MainActivity : FlutterActivity() { private val mainHandler = Handler(Looper.getMainLooper()) companion object { - /** MethodChannel for Dart → native calls (pushEvent). */ private const val METHOD_CH = "com.clevertap.flutter/native_display" - - /** EventChannel for native → Dart push (units_updated). */ private const val EVENT_CH = "com.clevertap.flutter/native_display_events" } override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) - // EventChannel: push display units to Dart when they arrive EventChannel(flutterEngine.dartExecutor.binaryMessenger, EVENT_CH) .setStreamHandler(object : EventChannel.StreamHandler { override fun onListen(arguments: Any?, events: EventChannel.EventSink?) { @@ -35,7 +30,6 @@ class MainActivity : FlutterActivity() { } }) - // MethodChannel: handle pushEvent calls from Dart MethodChannel(flutterEngine.dartExecutor.binaryMessenger, METHOD_CH) .setMethodCallHandler { call, result -> when (call.method) { @@ -50,12 +44,10 @@ class MainActivity : FlutterActivity() { } } - // Register callback: future unit deliveries from SampleApplication go to Flutter - SampleApplication.onUnitsLoaded = { units -> pushUnitsToFlutter(units) } + SampleApplication.onUnitsLoaded = { jsonList -> pushUnitsToFlutter(jsonList) } } - private fun pushUnitsToFlutter(units: List) { - val jsonList = units.mapNotNull { it.rawJson }.filter { it.isNotEmpty() } + private fun pushUnitsToFlutter(jsonList: List) { mainHandler.post { eventSink?.success(mapOf("type" to "units_updated", "units" to jsonList)) } diff --git a/flutter-sample/android/app/src/main/kotlin/com/clevertap/flutter/clevertap_native_display_sample/SampleApplication.kt b/flutter-sample/android/app/src/main/kotlin/com/clevertap/flutter/clevertap_native_display_sample/SampleApplication.kt index e2c7a42..81634ff 100644 --- a/flutter-sample/android/app/src/main/kotlin/com/clevertap/flutter/clevertap_native_display_sample/SampleApplication.kt +++ b/flutter-sample/android/app/src/main/kotlin/com/clevertap/flutter/clevertap_native_display_sample/SampleApplication.kt @@ -2,32 +2,21 @@ package com.clevertap.flutter.clevertap_native_display_sample import android.app.Application import android.util.Log -import com.clevertap.android.nativedisplay.bridge.NativeDisplayBridge -import com.clevertap.android.nativedisplay.bridge.NativeDisplayBridgeListener -import com.clevertap.android.nativedisplay.bridge.NativeDisplayUnit import com.clevertap.android.sdk.ActivityLifecycleCallback import com.clevertap.android.sdk.CleverTapAPI import com.clevertap.android.sdk.CleverTapAPI.LogLevel.VERBOSE +import com.clevertap.android.sdk.displayunits.DisplayUnitListener +import com.clevertap.android.sdk.displayunits.model.CleverTapDisplayUnit -/** - * Sample application. - * - * Initializes CleverTap and the NativeDisplayBridge at app startup so that - * MainActivity can observe display units and push them to Flutter via EventChannel. - */ class SampleApplication : Application() { companion object { private const val TAG = "SampleApplication" var cleverTapApi: CleverTapAPI? = null - var nativeDisplayBridge: NativeDisplayBridge? = null - /** - * Set by MainActivity once the EventChannel sink is ready. - * Called on the main thread whenever new units arrive. - */ - var onUnitsLoaded: ((List) -> Unit)? = null + /** Set by MainActivity once the EventChannel sink is ready. */ + var onUnitsLoaded: ((List) -> Unit)? = null } override fun onCreate() { @@ -35,29 +24,19 @@ class SampleApplication : Application() { ActivityLifecycleCallback.register(this) super.onCreate() - // Initialize NativeDisplayBridge (auto-wire mode) - val bridge = NativeDisplayBridge.initialize(this) - nativeDisplayBridge = bridge - - // Get CleverTap default instance (auto-created from manifest metadata) val ct = CleverTapAPI.getDefaultInstance(this) cleverTapApi = ct if (ct != null) { - // Bind bridge to CleverTap — wires display unit callbacks - bridge.bind(ct) - - // Register listener — delivers units to MainActivity's EventChannel - bridge.addListener(object : NativeDisplayBridgeListener { - override fun onNativeDisplaysLoaded(units: List) { - Log.d(TAG, "Received ${units.size} Native Display unit(s)") - onUnitsLoaded?.invoke(units) + ct.setDisplayUnitListener(object : DisplayUnitListener { + override fun onDisplayUnitsLoaded(units: ArrayList) { + Log.d(TAG, "Received ${units.size} display unit(s)") + val jsonList = units.mapNotNull { it.jsonObject?.toString() } + .filter { it.isNotEmpty() } + onUnitsLoaded?.invoke(jsonList) } }) - - // Request Native Display units from server - bridge.fetchNativeDisplays(ct) - Log.d(TAG, "Bridge initialized, bound, and fetch requested") + Log.d(TAG, "CleverTap initialized, display unit listener registered") } else { Log.w(TAG, "CleverTapAPI.getDefaultInstance() returned null — check manifest metadata") } diff --git a/flutter-sample/android/settings.gradle.kts b/flutter-sample/android/settings.gradle.kts index 042d0b6..a439442 100644 --- a/flutter-sample/android/settings.gradle.kts +++ b/flutter-sample/android/settings.gradle.kts @@ -23,10 +23,3 @@ plugins { } include(":app") - -// Include the local Android SDK so SampleApplication can use NativeDisplayBridge -includeBuild("../../../android") { - dependencySubstitution { - substitute(module("com.clevertap.android:native-display-sdk")).using(project(":sdk")) - } -} From 5f6ab3354b54b635676550d62a946378f412a909 Mon Sep 17 00:00:00 2001 From: CTLalit <144685420+CTLalit@users.noreply.github.com> Date: Tue, 26 May 2026 14:19:12 +0530 Subject: [PATCH 08/12] SDK-5835: Fix NativeDisplayConfig JSON extraction and EventChannel replay Two bugs prevented display units from rendering in CleverTapIntegrationScreen: 1. Wrong JSON format: CleverTapDisplayUnit.getJsonObject() returns raw CT JSON (type:advanced-builder, slot_id, wzrk_id, etc.) not NativeDisplayConfig JSON. Flutter's NativeDisplayConfig.fromJson() cannot parse this format. Fix: extract NativeDisplayConfig from the raw CT JSON using the same 3-strategy detection as NativeDisplayConfigParser: - native_display_config top-level key (dashboard format) - custom_kv.nd_config string value - root top-level key (direct NativeDisplayConfig JSON) 2. Timing race: display units can arrive from CT server before Flutter subscribes to the EventChannel, causing them to be silently dropped (eventSink is null). Fix: cache the last received unit JSONs in SampleApplication.cachedUnitJsons and replay them immediately in EventChannel.StreamHandler.onListen(). Co-Authored-By: Claude Sonnet 4.6 --- .../MainActivity.kt | 5 ++ .../SampleApplication.kt | 48 ++++++++++++++++++- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/flutter-sample/android/app/src/main/kotlin/com/clevertap/flutter/clevertap_native_display_sample/MainActivity.kt b/flutter-sample/android/app/src/main/kotlin/com/clevertap/flutter/clevertap_native_display_sample/MainActivity.kt index 65688cf..e49c62a 100644 --- a/flutter-sample/android/app/src/main/kotlin/com/clevertap/flutter/clevertap_native_display_sample/MainActivity.kt +++ b/flutter-sample/android/app/src/main/kotlin/com/clevertap/flutter/clevertap_native_display_sample/MainActivity.kt @@ -24,6 +24,11 @@ class MainActivity : FlutterActivity() { .setStreamHandler(object : EventChannel.StreamHandler { override fun onListen(arguments: Any?, events: EventChannel.EventSink?) { eventSink = events + // Replay any units that arrived before Flutter subscribed + val cached = SampleApplication.cachedUnitJsons + if (cached.isNotEmpty()) { + pushUnitsToFlutter(cached) + } } override fun onCancel(arguments: Any?) { eventSink = null diff --git a/flutter-sample/android/app/src/main/kotlin/com/clevertap/flutter/clevertap_native_display_sample/SampleApplication.kt b/flutter-sample/android/app/src/main/kotlin/com/clevertap/flutter/clevertap_native_display_sample/SampleApplication.kt index 81634ff..450ac12 100644 --- a/flutter-sample/android/app/src/main/kotlin/com/clevertap/flutter/clevertap_native_display_sample/SampleApplication.kt +++ b/flutter-sample/android/app/src/main/kotlin/com/clevertap/flutter/clevertap_native_display_sample/SampleApplication.kt @@ -7,6 +7,7 @@ import com.clevertap.android.sdk.CleverTapAPI import com.clevertap.android.sdk.CleverTapAPI.LogLevel.VERBOSE import com.clevertap.android.sdk.displayunits.DisplayUnitListener import com.clevertap.android.sdk.displayunits.model.CleverTapDisplayUnit +import org.json.JSONObject class SampleApplication : Application() { @@ -15,6 +16,9 @@ class SampleApplication : Application() { var cleverTapApi: CleverTapAPI? = null + /** Last received batch — replayed to new EventChannel sinks so units aren't lost. */ + var cachedUnitJsons: List = emptyList() + /** Set by MainActivity once the EventChannel sink is ready. */ var onUnitsLoaded: ((List) -> Unit)? = null } @@ -31,8 +35,11 @@ class SampleApplication : Application() { ct.setDisplayUnitListener(object : DisplayUnitListener { override fun onDisplayUnitsLoaded(units: ArrayList) { Log.d(TAG, "Received ${units.size} display unit(s)") - val jsonList = units.mapNotNull { it.jsonObject?.toString() } - .filter { it.isNotEmpty() } + val jsonList = units.mapNotNull { unit -> + extractNdConfigJson(unit.jsonObject) + }.filter { it.isNotEmpty() } + Log.d(TAG, "Extracted ${jsonList.size} NativeDisplayConfig JSON(s)") + cachedUnitJsons = jsonList onUnitsLoaded?.invoke(jsonList) } }) @@ -41,4 +48,41 @@ class SampleApplication : Application() { Log.w(TAG, "CleverTapAPI.getDefaultInstance() returned null — check manifest metadata") } } + + /** + * Mirrors NativeDisplayConfigParser detection strategy (3 attempts in order): + * + * 1. `native_display_config` top-level key → parse its value as NativeDisplayConfig JSON + * 2. `custom_kv.nd_config` string value → that string IS the NativeDisplayConfig JSON + * 3. `root` top-level key present → entire JSON object is treated as NativeDisplayConfig + * + * Returns null if the unit does not contain a recognizable NativeDisplayConfig payload. + */ + private fun extractNdConfigJson(json: JSONObject?): String? { + if (json == null) return null + + // Strategy 1: native_display_config key + json.optJSONObject("native_display_config")?.let { ndConfig -> + Log.d(TAG, "Found NativeDisplayConfig via 'native_display_config' key") + return ndConfig.toString() + } + + // Strategy 2: custom_kv.nd_config string + json.optJSONObject("custom_kv") + ?.optString("nd_config", null) + ?.takeIf { it.isNotEmpty() } + ?.let { ndConfigStr -> + Log.d(TAG, "Found NativeDisplayConfig via 'custom_kv.nd_config'") + return ndConfigStr + } + + // Strategy 3: root key present — entire JSON is NativeDisplayConfig + if (json.has("root")) { + Log.d(TAG, "Found NativeDisplayConfig via top-level 'root' key") + return json.toString() + } + + Log.w(TAG, "Display unit does not contain a NativeDisplayConfig payload, skipping") + return null + } } From c403ae09fb0161170ceb7d81da7ef76ae4df9470 Mon Sep 17 00:00:00 2001 From: CTLalit <144685420+CTLalit@users.noreply.github.com> Date: Wed, 27 May 2026 03:38:15 +0530 Subject: [PATCH 09/12] SDK-5835: Fix aspectRatio sizing, promote container stubs, and add Flutter sample platform targets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Flutter SDK — renderer fixes - native_display_view: aspectRatio now takes precedence over width.percent — when AR is set on a node the renderer uses full available parent width and derives height = parentWidth / aspectRatio, matching Android (Compose modifier ordering) and iOS (guard check in resolveRootWidth). _effectiveWidth returns availableWidth when AR > 0; _applyRootSizing short-circuits immediately when AR is present. - native_display_view: fix _computeRootHeight for percent height roots — previously fell through to screenHeight, inflating percent-based fonts by up to 67 %. - native_display_renderer: remove debugLabel field and all _wrapWithSizing debugPrint calls. - box_container: remove _buildChild debugPrint. - vertical_container / horizontal_container: promote from v1 stubs to full arrangement implementations — all ArrangementStrategy values (spaced, space_between, space_evenly, space_around, start, center, end) now produce correct Column/Row with spacers or MainAxisAlignment. - button_element: propagate maxLines, overflow, and softWrap from resolved style. - style_applier: fix border resolution — require width > 0 before rendering; skip zero- width borders rather than rendering a phantom 1 dp default. ## Flutter sample app - clevertap_integration_screen: remove debug red border, outer LayoutBuilder, and [SAMPLE] log prints; clean up raw JSON logging from onUnitsLoaded. - test_browser_screen: add test-parity-80pct-ar.json to browser list. - Add 180+ test-config JSON assets for the in-app test browser. - Add multi-platform scaffolding (linux, macos, web, windows). - pubspec: add clevertap_plugin dependency; update flutter-sdk path reference. Co-Authored-By: Claude Sonnet 4.6 --- flutter-sample/.flutter-plugins | 1 + flutter-sample/.flutter-plugins-dependencies | 2 +- flutter-sample/.metadata | 45 + flutter-sample/README.md | 16 + flutter-sample/android/app/build.gradle.kts | 4 +- .../MainActivity.kt | 57 +- .../SampleApplication.kt | 74 -- .../assets/configs/test_simple.json | 40 + .../test-001-vertical-simple.json | 91 ++ .../test-002-horizontal-simple.json | 96 ++ .../test-configs/test-003-box-simple.json | 99 ++ .../test-configs/test-004-stack-simple.json | 105 ++ .../test-configs/test-005-gallery-simple.json | 224 +++++ .../test-configs/test-006-vertical-empty.json | 37 + .../test-007-vertical-single-child.json | 71 ++ .../test-008-vertical-3-children.json | 109 ++ .../test-009-vertical-5-children.json | 172 ++++ .../test-010-vertical-10-children.json | 267 +++++ .../test-011-horizontal-empty.json | 42 + .../test-012-horizontal-single-child.json | 64 ++ .../test-013-horizontal-3-children.json | 102 ++ .../test-014-horizontal-5-children.json | 215 ++++ .../test-015-horizontal-10-children.json | 250 +++++ .../test-configs/test-016-box-empty.json | 46 + .../test-017-box-single-child.json | 66 ++ .../test-configs/test-018-box-3-children.json | 143 +++ .../test-configs/test-019-box-5-children.json | 199 ++++ .../test-configs/test-020-stack-empty.json | 43 + .../test-021-stack-single-child.json | 61 ++ .../test-022-stack-3-children.json | 182 ++++ .../test-023-stack-5-children.json | 242 +++++ .../test-configs/test-024-gallery-empty.json | 43 + .../test-025-gallery-single-child.json | 64 ++ .../test-026-gallery-3-children-snapping.json | 278 ++++++ .../test-027-gallery-5-children-snapping.json | 159 +++ ...test-028-gallery-10-children-snapping.json | 254 +++++ ...test-029-gallery-3-children-free-flow.json | 271 +++++ ...030-gallery-3-children-free-flow-grid.json | 278 ++++++ .../test-031-vertical-spaced.json | 180 ++++ .../test-032-vertical-space-between.json | 157 +++ .../test-033-vertical-space-evenly.json | 184 ++++ .../test-034-vertical-space-around.json | 165 ++++ .../test-035-horizontal-start.json | 275 ++++++ .../test-036-horizontal-center.json | 275 ++++++ .../test-configs/test-037-horizontal-end.json | 275 ++++++ .../test-038-vertical-spacing-0.json | 197 ++++ .../test-039-vertical-spacing-8.json | 152 +++ .../test-040-vertical-spacing-16.json | 182 ++++ .../test-041-vertical-spacing-32.json | 326 ++++++ .../test-042-vertical-padding-uniform.json | 175 ++++ .../test-043-vertical-padding-individual.json | 157 +++ ...est-044-horizontal-padding-asymmetric.json | 225 +++++ .../test-045-box-padding-large.json | 159 +++ .../test-046-vertical-wrap-content.json | 178 ++++ .../test-047-horizontal-percent-width.json | 275 ++++++ .../test-048-vertical-mixed-units.json | 261 +++++ .../test-049-nested-mixed-arrangements.json | 449 +++++++++ .../test-050-gallery-spacing-variations.json | 500 ++++++++++ .../test-051-all-text-elements.json | 170 ++++ .../test-052-all-image-elements.json | 162 +++ .../test-053-all-button-elements.json | 155 +++ .../test-054-all-video-elements.json | 83 ++ .../test-055-all-spacer-elements.json | 132 +++ .../test-056-all-divider-elements.json | 133 +++ .../test-configs/test-057-product-card.json | 162 +++ .../test-configs/test-058-login-form.json | 172 ++++ .../test-configs/test-059-profile-header.json | 182 ++++ .../test-configs/test-060-media-player.json | 268 +++++ .../test-configs/test-061-article-layout.json | 243 +++++ .../test-configs/test-062-action-sheet.json | 303 ++++++ .../test-configs/test-063-stats-card.json | 460 +++++++++ .../test-configs/test-064-gallery-item.json | 334 +++++++ .../test-configs/test-065-notification.json | 256 +++++ .../test-configs/test-066-pricing-card.json | 424 ++++++++ .../test-configs/test-067-hero-banner.json | 235 +++++ .../test-configs/test-068-social-post.json | 394 ++++++++ .../test-configs/test-069-settings-row.json | 580 +++++++++++ .../test-070-feature-showcase.json | 365 +++++++ .../test-configs/test-071-text-colors.json | 258 +++++ .../test-configs/test-072-font-sizes.json | 200 ++++ .../test-configs/test-073-font-weights.json | 275 ++++++ .../test-configs/test-074-text-alignment.json | 332 +++++++ .../test-075-text-decoration.json | 412 ++++++++ .../test-configs/test-076-line-height.json | 379 +++++++ .../test-configs/test-077-font-families.json | 465 +++++++++ .../test-configs/test-078-border-radius.json | 439 +++++++++ .../test-079-border-width-color.json | 519 ++++++++++ .../test-configs/test-080-shadows-light.json | 477 +++++++++ .../test-configs/test-081-shadows-medium.json | 452 +++++++++ .../test-configs/test-082-shadows-heavy.json | 507 ++++++++++ .../test-083-opacity-variations.json | 632 ++++++++++++ .../test-084-combined-visual-styles.json | 562 +++++++++++ .../test-085-text-style-inheritance.json | 483 +++++++++ .../test-086-style-class-usage.json | 648 ++++++++++++ .../test-087-inline-vs-inherited.json | 627 ++++++++++++ .../test-088-theme-default-styles.json | 567 +++++++++++ .../test-089-styled-product-card.json | 707 +++++++++++++ .../test-090-styled-profile-card.json | 781 +++++++++++++++ .../test-091-offset-percent-box-basic.json | 176 ++++ .../test-092-offset-percent-stack-layers.json | 204 ++++ .../test-093-offset-percent-negative.json | 180 ++++ .../test-094-offset-percent-overflow.json | 208 ++++ .../test-095-offset-percent-zero.json | 196 ++++ .../test-096-offset-percent-responsive.json | 238 +++++ .../test-097-offset-mixed-units.json | 208 ++++ .../test-098-offset-percent-nested.json | 184 ++++ .../test-099-offset-percent-with-padding.json | 275 ++++++ .../test-100-offset-percent-gallery-peek.json | 189 ++++ ...t-101-aspect-ratio-square-fixed-width.json | 193 ++++ ...est-102-aspect-ratio-16-9-fixed-width.json | 197 ++++ ...test-103-aspect-ratio-4-3-fixed-width.json | 259 +++++ .../test-104-aspect-ratio-fixed-height.json | 253 +++++ .../test-105-aspect-ratio-percent-width.json | 266 +++++ .../test-106-aspect-ratio-wrap-content.json | 207 ++++ .../test-107-aspect-ratio-match-parent.json | 207 ++++ .../test-108-aspect-ratio-extreme-wide.json | 308 ++++++ .../test-109-aspect-ratio-extreme-tall.json | 398 ++++++++ ...test-110-aspect-ratio-mixed-container.json | 407 ++++++++ .../test-111-combined-aspect-offset-box.json | 215 ++++ .../test-112-combined-nested-complex.json | 301 ++++++ ...test-113-combined-gallery-aspect-peek.json | 334 +++++++ .../test-114-combined-product-grid.json | 468 +++++++++ .../test-115-combined-showcase-all.json | 480 +++++++++ .../test-116-match-parent-comprehensive.json | 465 +++++++++ .../test-117-wrap-content-comprehensive.json | 624 ++++++++++++ .../test-118-mixed-special-dimensions.json | 742 ++++++++++++++ .../test-119-match-parent-stack-box.json | 576 +++++++++++ .../test-120-wrap-content-constraints.json | 922 +++++++++++++++++ .../test-121-16x9-ar-image-text-button.json | 103 ++ .../test-122-1x1-ar-image-badge-rounded.json | 77 ++ .../test-123-9x16-ar-video-caption.json | 75 ++ .../test-124-4x3-ar-text-weights.json | 122 +++ .../test-125-2x1-ar-image-split-button.json | 103 ++ .../test-126-text-font-weights.json | 122 +++ .../test-127-text-font-sizes.json | 118 +++ .../test-configs/test-128-text-alignment.json | 97 ++ .../test-129-text-decoration-italic.json | 97 ++ .../test-130-text-maxlines-overflow.json | 48 + .../test-configs/test-131-text-gradient.json | 93 ++ .../test-132-image-fit-crop-contain.json | 124 +++ .../test-133-image-gif-rounded.json | 75 ++ .../test-134-image-border-radius.json | 210 ++++ .../test-configs/test-135-images-z-order.json | 74 ++ .../test-136-video-autoplay-muted.json | 50 + .../test-137-video-with-controls.json | 76 ++ .../test-138-9x16-video-button.json | 80 ++ .../test-139-button-centered.json | 52 + .../test-140-button-primary-secondary.json | 84 ++ .../test-141-button-size-variants.json | 112 +++ .../test-configs/test-142-cta-card.json | 127 +++ .../test-143-button-rounded-text.json | 78 ++ .../test-144-rounded-box-text.json | 99 ++ .../test-145-nested-rounded-boxes.json | 75 ++ .../test-146-image-overlay-rounded.json | 100 ++ .../test-147-hero-banner-complex.json | 127 +++ .../test-148-product-card-complex.json | 130 +++ .../test-149-notification-card.json | 128 +++ .../test-150-dashboard-widget.json | 179 ++++ .../test-151-video-player-card.json | 129 +++ .../test-configs/test-152-text-corners.json | 134 +++ .../test-configs/test-153-image-clipped.json | 48 + .../test-154-nested-box-deep.json | 100 ++ .../test-155-all-element-types.json | 141 +++ .../test-156-button-backgrounds.json | 318 ++++++ ...llery-box-freeflow-indicators-navbtns.json | 814 +++++++++++++++ ...-gallery-box-freeflow-indicators-only.json | 665 +++++++++++++ ...159-gallery-box-freeflow-navbtns-only.json | 742 ++++++++++++++ ...test-160-gallery-box-freeflow-minimal.json | 593 +++++++++++ ...-161-gallery-box-freeflow-tall-images.json | 667 +++++++++++++ ...-162-gallery-box-freeflow-video-items.json | 736 ++++++++++++++ ...163-gallery-box-freeflow-button-items.json | 890 +++++++++++++++++ .../test-164-gallery-box-freeflow-5items.json | 795 +++++++++++++++ ...llery-box-grid2col-indicators-navbtns.json | 837 ++++++++++++++++ ...-gallery-box-grid2col-indicators-only.json | 763 ++++++++++++++ ...167-gallery-box-grid2col-navbtns-only.json | 741 ++++++++++++++ ...test-168-gallery-box-grid2col-minimal.json | 763 ++++++++++++++ ...t-169-gallery-box-grid3col-indicators.json | 855 ++++++++++++++++ ...test-170-gallery-box-grid3col-navbtns.json | 929 ++++++++++++++++++ .../test-171-gallery-box-grid2col-video.json | 833 ++++++++++++++++ ...est-172-gallery-box-grid2col-vertical.json | 763 ++++++++++++++ .../test-172-video-fullscreen-openurl.json | 34 + ...llery-box-snapping-indicators-navbtns.json | 819 +++++++++++++++ ...-gallery-box-snapping-indicators-only.json | 667 +++++++++++++ ...175-gallery-box-snapping-navbtns-only.json | 741 ++++++++++++++ ...test-176-gallery-box-snapping-minimal.json | 673 +++++++++++++ .../test-177-html-inline-basic.json | 51 + .../test-178-html-with-javascript.json | 51 + .../test-179-html-transparent-bg.json | 41 + .../test-180-html-scrollable-content.json | 50 + .../test-VERIFY-percentage-offset-fix.json | 204 ++++ .../test-configs/test-parity-80pct-ar.json | 97 ++ .../screens/clevertap_integration_screen.dart | 231 +++-- .../lib/screens/test_browser_screen.dart | 1 + flutter-sample/linux/.gitignore | 1 + flutter-sample/linux/CMakeLists.txt | 128 +++ flutter-sample/linux/flutter/CMakeLists.txt | 88 ++ .../flutter/generated_plugin_registrant.cc | 15 + .../flutter/generated_plugin_registrant.h | 15 + .../linux/flutter/generated_plugins.cmake | 24 + flutter-sample/linux/runner/CMakeLists.txt | 26 + flutter-sample/linux/runner/main.cc | 6 + flutter-sample/linux/runner/my_application.cc | 130 +++ flutter-sample/linux/runner/my_application.h | 18 + flutter-sample/macos/.gitignore | 7 + .../macos/Flutter/Flutter-Debug.xcconfig | 2 + .../macos/Flutter/Flutter-Release.xcconfig | 2 + .../Flutter/GeneratedPluginRegistrant.swift | 20 + flutter-sample/macos/Podfile | 42 + .../macos/Runner.xcodeproj/project.pbxproj | 705 +++++++++++++ .../xcshareddata/xcschemes/Runner.xcscheme | 99 ++ flutter-sample/macos/Runner/AppDelegate.swift | 13 + .../AppIcon.appiconset/Contents.json | 68 ++ .../AppIcon.appiconset/app_icon_1024.png | Bin 0 -> 102994 bytes .../AppIcon.appiconset/app_icon_128.png | Bin 0 -> 5680 bytes .../AppIcon.appiconset/app_icon_16.png | Bin 0 -> 520 bytes .../AppIcon.appiconset/app_icon_256.png | Bin 0 -> 14142 bytes .../AppIcon.appiconset/app_icon_32.png | Bin 0 -> 1066 bytes .../AppIcon.appiconset/app_icon_512.png | Bin 0 -> 36406 bytes .../AppIcon.appiconset/app_icon_64.png | Bin 0 -> 2218 bytes .../macos/Runner/Base.lproj/MainMenu.xib | 343 +++++++ .../macos/Runner/Configs/AppInfo.xcconfig | 14 + .../macos/Runner/Configs/Debug.xcconfig | 2 + .../macos/Runner/Configs/Release.xcconfig | 2 + .../macos/Runner/Configs/Warnings.xcconfig | 13 + .../macos/Runner/DebugProfile.entitlements | 12 + flutter-sample/macos/Runner/Info.plist | 32 + .../macos/Runner/MainFlutterWindow.swift | 15 + .../macos/Runner/Release.entitlements | 8 + .../macos/RunnerTests/RunnerTests.swift | 12 + flutter-sample/pubspec.lock | 8 + flutter-sample/pubspec.yaml | 2 + flutter-sample/web/favicon.png | Bin 0 -> 917 bytes flutter-sample/web/icons/Icon-192.png | Bin 0 -> 5292 bytes flutter-sample/web/icons/Icon-512.png | Bin 0 -> 8252 bytes .../web/icons/Icon-maskable-192.png | Bin 0 -> 5594 bytes .../web/icons/Icon-maskable-512.png | Bin 0 -> 20998 bytes flutter-sample/web/index.html | 38 + flutter-sample/web/manifest.json | 35 + flutter-sample/windows/.gitignore | 17 + flutter-sample/windows/CMakeLists.txt | 108 ++ flutter-sample/windows/flutter/CMakeLists.txt | 109 ++ .../flutter/generated_plugin_registrant.cc | 14 + .../flutter/generated_plugin_registrant.h | 15 + .../windows/flutter/generated_plugins.cmake | 24 + flutter-sample/windows/runner/CMakeLists.txt | 40 + flutter-sample/windows/runner/Runner.rc | 121 +++ .../windows/runner/flutter_window.cpp | 71 ++ .../windows/runner/flutter_window.h | 33 + flutter-sample/windows/runner/main.cpp | 43 + flutter-sample/windows/runner/resource.h | 16 + .../windows/runner/resources/app_icon.ico | Bin 0 -> 33772 bytes .../windows/runner/runner.exe.manifest | 14 + flutter-sample/windows/runner/utils.cpp | 65 ++ flutter-sample/windows/runner/utils.h | 19 + .../windows/runner/win32_window.cpp | 288 ++++++ flutter-sample/windows/runner/win32_window.h | 102 ++ .../renderer/containers/box_container.dart | 91 +- .../containers/horizontal_container.dart | 79 +- .../containers/vertical_container.dart | 79 +- .../src/renderer/elements/button_element.dart | 18 +- .../src/renderer/native_display_renderer.dart | 58 ++ .../lib/src/renderer/native_display_view.dart | 128 ++- flutter/lib/src/renderer/style_applier.dart | 12 +- 263 files changed, 57236 insertions(+), 298 deletions(-) create mode 100644 flutter-sample/.metadata create mode 100644 flutter-sample/README.md create mode 100644 flutter-sample/assets/configs/test_simple.json create mode 100644 flutter-sample/assets/test-configs/test-001-vertical-simple.json create mode 100644 flutter-sample/assets/test-configs/test-002-horizontal-simple.json create mode 100644 flutter-sample/assets/test-configs/test-003-box-simple.json create mode 100644 flutter-sample/assets/test-configs/test-004-stack-simple.json create mode 100644 flutter-sample/assets/test-configs/test-005-gallery-simple.json create mode 100644 flutter-sample/assets/test-configs/test-006-vertical-empty.json create mode 100644 flutter-sample/assets/test-configs/test-007-vertical-single-child.json create mode 100644 flutter-sample/assets/test-configs/test-008-vertical-3-children.json create mode 100644 flutter-sample/assets/test-configs/test-009-vertical-5-children.json create mode 100644 flutter-sample/assets/test-configs/test-010-vertical-10-children.json create mode 100644 flutter-sample/assets/test-configs/test-011-horizontal-empty.json create mode 100644 flutter-sample/assets/test-configs/test-012-horizontal-single-child.json create mode 100644 flutter-sample/assets/test-configs/test-013-horizontal-3-children.json create mode 100644 flutter-sample/assets/test-configs/test-014-horizontal-5-children.json create mode 100644 flutter-sample/assets/test-configs/test-015-horizontal-10-children.json create mode 100644 flutter-sample/assets/test-configs/test-016-box-empty.json create mode 100644 flutter-sample/assets/test-configs/test-017-box-single-child.json create mode 100644 flutter-sample/assets/test-configs/test-018-box-3-children.json create mode 100644 flutter-sample/assets/test-configs/test-019-box-5-children.json create mode 100644 flutter-sample/assets/test-configs/test-020-stack-empty.json create mode 100644 flutter-sample/assets/test-configs/test-021-stack-single-child.json create mode 100644 flutter-sample/assets/test-configs/test-022-stack-3-children.json create mode 100644 flutter-sample/assets/test-configs/test-023-stack-5-children.json create mode 100644 flutter-sample/assets/test-configs/test-024-gallery-empty.json create mode 100644 flutter-sample/assets/test-configs/test-025-gallery-single-child.json create mode 100644 flutter-sample/assets/test-configs/test-026-gallery-3-children-snapping.json create mode 100644 flutter-sample/assets/test-configs/test-027-gallery-5-children-snapping.json create mode 100644 flutter-sample/assets/test-configs/test-028-gallery-10-children-snapping.json create mode 100644 flutter-sample/assets/test-configs/test-029-gallery-3-children-free-flow.json create mode 100644 flutter-sample/assets/test-configs/test-030-gallery-3-children-free-flow-grid.json create mode 100644 flutter-sample/assets/test-configs/test-031-vertical-spaced.json create mode 100644 flutter-sample/assets/test-configs/test-032-vertical-space-between.json create mode 100644 flutter-sample/assets/test-configs/test-033-vertical-space-evenly.json create mode 100644 flutter-sample/assets/test-configs/test-034-vertical-space-around.json create mode 100644 flutter-sample/assets/test-configs/test-035-horizontal-start.json create mode 100644 flutter-sample/assets/test-configs/test-036-horizontal-center.json create mode 100644 flutter-sample/assets/test-configs/test-037-horizontal-end.json create mode 100644 flutter-sample/assets/test-configs/test-038-vertical-spacing-0.json create mode 100644 flutter-sample/assets/test-configs/test-039-vertical-spacing-8.json create mode 100644 flutter-sample/assets/test-configs/test-040-vertical-spacing-16.json create mode 100644 flutter-sample/assets/test-configs/test-041-vertical-spacing-32.json create mode 100644 flutter-sample/assets/test-configs/test-042-vertical-padding-uniform.json create mode 100644 flutter-sample/assets/test-configs/test-043-vertical-padding-individual.json create mode 100644 flutter-sample/assets/test-configs/test-044-horizontal-padding-asymmetric.json create mode 100644 flutter-sample/assets/test-configs/test-045-box-padding-large.json create mode 100644 flutter-sample/assets/test-configs/test-046-vertical-wrap-content.json create mode 100644 flutter-sample/assets/test-configs/test-047-horizontal-percent-width.json create mode 100644 flutter-sample/assets/test-configs/test-048-vertical-mixed-units.json create mode 100644 flutter-sample/assets/test-configs/test-049-nested-mixed-arrangements.json create mode 100644 flutter-sample/assets/test-configs/test-050-gallery-spacing-variations.json create mode 100644 flutter-sample/assets/test-configs/test-051-all-text-elements.json create mode 100644 flutter-sample/assets/test-configs/test-052-all-image-elements.json create mode 100644 flutter-sample/assets/test-configs/test-053-all-button-elements.json create mode 100644 flutter-sample/assets/test-configs/test-054-all-video-elements.json create mode 100644 flutter-sample/assets/test-configs/test-055-all-spacer-elements.json create mode 100644 flutter-sample/assets/test-configs/test-056-all-divider-elements.json create mode 100644 flutter-sample/assets/test-configs/test-057-product-card.json create mode 100644 flutter-sample/assets/test-configs/test-058-login-form.json create mode 100644 flutter-sample/assets/test-configs/test-059-profile-header.json create mode 100644 flutter-sample/assets/test-configs/test-060-media-player.json create mode 100644 flutter-sample/assets/test-configs/test-061-article-layout.json create mode 100644 flutter-sample/assets/test-configs/test-062-action-sheet.json create mode 100644 flutter-sample/assets/test-configs/test-063-stats-card.json create mode 100644 flutter-sample/assets/test-configs/test-064-gallery-item.json create mode 100644 flutter-sample/assets/test-configs/test-065-notification.json create mode 100644 flutter-sample/assets/test-configs/test-066-pricing-card.json create mode 100644 flutter-sample/assets/test-configs/test-067-hero-banner.json create mode 100644 flutter-sample/assets/test-configs/test-068-social-post.json create mode 100644 flutter-sample/assets/test-configs/test-069-settings-row.json create mode 100644 flutter-sample/assets/test-configs/test-070-feature-showcase.json create mode 100644 flutter-sample/assets/test-configs/test-071-text-colors.json create mode 100644 flutter-sample/assets/test-configs/test-072-font-sizes.json create mode 100644 flutter-sample/assets/test-configs/test-073-font-weights.json create mode 100644 flutter-sample/assets/test-configs/test-074-text-alignment.json create mode 100644 flutter-sample/assets/test-configs/test-075-text-decoration.json create mode 100644 flutter-sample/assets/test-configs/test-076-line-height.json create mode 100644 flutter-sample/assets/test-configs/test-077-font-families.json create mode 100644 flutter-sample/assets/test-configs/test-078-border-radius.json create mode 100644 flutter-sample/assets/test-configs/test-079-border-width-color.json create mode 100644 flutter-sample/assets/test-configs/test-080-shadows-light.json create mode 100644 flutter-sample/assets/test-configs/test-081-shadows-medium.json create mode 100644 flutter-sample/assets/test-configs/test-082-shadows-heavy.json create mode 100644 flutter-sample/assets/test-configs/test-083-opacity-variations.json create mode 100644 flutter-sample/assets/test-configs/test-084-combined-visual-styles.json create mode 100644 flutter-sample/assets/test-configs/test-085-text-style-inheritance.json create mode 100644 flutter-sample/assets/test-configs/test-086-style-class-usage.json create mode 100644 flutter-sample/assets/test-configs/test-087-inline-vs-inherited.json create mode 100644 flutter-sample/assets/test-configs/test-088-theme-default-styles.json create mode 100644 flutter-sample/assets/test-configs/test-089-styled-product-card.json create mode 100644 flutter-sample/assets/test-configs/test-090-styled-profile-card.json create mode 100644 flutter-sample/assets/test-configs/test-091-offset-percent-box-basic.json create mode 100644 flutter-sample/assets/test-configs/test-092-offset-percent-stack-layers.json create mode 100644 flutter-sample/assets/test-configs/test-093-offset-percent-negative.json create mode 100644 flutter-sample/assets/test-configs/test-094-offset-percent-overflow.json create mode 100644 flutter-sample/assets/test-configs/test-095-offset-percent-zero.json create mode 100644 flutter-sample/assets/test-configs/test-096-offset-percent-responsive.json create mode 100644 flutter-sample/assets/test-configs/test-097-offset-mixed-units.json create mode 100644 flutter-sample/assets/test-configs/test-098-offset-percent-nested.json create mode 100644 flutter-sample/assets/test-configs/test-099-offset-percent-with-padding.json create mode 100644 flutter-sample/assets/test-configs/test-100-offset-percent-gallery-peek.json create mode 100644 flutter-sample/assets/test-configs/test-101-aspect-ratio-square-fixed-width.json create mode 100644 flutter-sample/assets/test-configs/test-102-aspect-ratio-16-9-fixed-width.json create mode 100644 flutter-sample/assets/test-configs/test-103-aspect-ratio-4-3-fixed-width.json create mode 100644 flutter-sample/assets/test-configs/test-104-aspect-ratio-fixed-height.json create mode 100644 flutter-sample/assets/test-configs/test-105-aspect-ratio-percent-width.json create mode 100644 flutter-sample/assets/test-configs/test-106-aspect-ratio-wrap-content.json create mode 100644 flutter-sample/assets/test-configs/test-107-aspect-ratio-match-parent.json create mode 100644 flutter-sample/assets/test-configs/test-108-aspect-ratio-extreme-wide.json create mode 100644 flutter-sample/assets/test-configs/test-109-aspect-ratio-extreme-tall.json create mode 100644 flutter-sample/assets/test-configs/test-110-aspect-ratio-mixed-container.json create mode 100644 flutter-sample/assets/test-configs/test-111-combined-aspect-offset-box.json create mode 100644 flutter-sample/assets/test-configs/test-112-combined-nested-complex.json create mode 100644 flutter-sample/assets/test-configs/test-113-combined-gallery-aspect-peek.json create mode 100644 flutter-sample/assets/test-configs/test-114-combined-product-grid.json create mode 100644 flutter-sample/assets/test-configs/test-115-combined-showcase-all.json create mode 100644 flutter-sample/assets/test-configs/test-116-match-parent-comprehensive.json create mode 100644 flutter-sample/assets/test-configs/test-117-wrap-content-comprehensive.json create mode 100644 flutter-sample/assets/test-configs/test-118-mixed-special-dimensions.json create mode 100644 flutter-sample/assets/test-configs/test-119-match-parent-stack-box.json create mode 100644 flutter-sample/assets/test-configs/test-120-wrap-content-constraints.json create mode 100644 flutter-sample/assets/test-configs/test-121-16x9-ar-image-text-button.json create mode 100644 flutter-sample/assets/test-configs/test-122-1x1-ar-image-badge-rounded.json create mode 100644 flutter-sample/assets/test-configs/test-123-9x16-ar-video-caption.json create mode 100644 flutter-sample/assets/test-configs/test-124-4x3-ar-text-weights.json create mode 100644 flutter-sample/assets/test-configs/test-125-2x1-ar-image-split-button.json create mode 100644 flutter-sample/assets/test-configs/test-126-text-font-weights.json create mode 100644 flutter-sample/assets/test-configs/test-127-text-font-sizes.json create mode 100644 flutter-sample/assets/test-configs/test-128-text-alignment.json create mode 100644 flutter-sample/assets/test-configs/test-129-text-decoration-italic.json create mode 100644 flutter-sample/assets/test-configs/test-130-text-maxlines-overflow.json create mode 100644 flutter-sample/assets/test-configs/test-131-text-gradient.json create mode 100644 flutter-sample/assets/test-configs/test-132-image-fit-crop-contain.json create mode 100644 flutter-sample/assets/test-configs/test-133-image-gif-rounded.json create mode 100644 flutter-sample/assets/test-configs/test-134-image-border-radius.json create mode 100644 flutter-sample/assets/test-configs/test-135-images-z-order.json create mode 100644 flutter-sample/assets/test-configs/test-136-video-autoplay-muted.json create mode 100644 flutter-sample/assets/test-configs/test-137-video-with-controls.json create mode 100644 flutter-sample/assets/test-configs/test-138-9x16-video-button.json create mode 100644 flutter-sample/assets/test-configs/test-139-button-centered.json create mode 100644 flutter-sample/assets/test-configs/test-140-button-primary-secondary.json create mode 100644 flutter-sample/assets/test-configs/test-141-button-size-variants.json create mode 100644 flutter-sample/assets/test-configs/test-142-cta-card.json create mode 100644 flutter-sample/assets/test-configs/test-143-button-rounded-text.json create mode 100644 flutter-sample/assets/test-configs/test-144-rounded-box-text.json create mode 100644 flutter-sample/assets/test-configs/test-145-nested-rounded-boxes.json create mode 100644 flutter-sample/assets/test-configs/test-146-image-overlay-rounded.json create mode 100644 flutter-sample/assets/test-configs/test-147-hero-banner-complex.json create mode 100644 flutter-sample/assets/test-configs/test-148-product-card-complex.json create mode 100644 flutter-sample/assets/test-configs/test-149-notification-card.json create mode 100644 flutter-sample/assets/test-configs/test-150-dashboard-widget.json create mode 100644 flutter-sample/assets/test-configs/test-151-video-player-card.json create mode 100644 flutter-sample/assets/test-configs/test-152-text-corners.json create mode 100644 flutter-sample/assets/test-configs/test-153-image-clipped.json create mode 100644 flutter-sample/assets/test-configs/test-154-nested-box-deep.json create mode 100644 flutter-sample/assets/test-configs/test-155-all-element-types.json create mode 100644 flutter-sample/assets/test-configs/test-156-button-backgrounds.json create mode 100644 flutter-sample/assets/test-configs/test-157-gallery-box-freeflow-indicators-navbtns.json create mode 100644 flutter-sample/assets/test-configs/test-158-gallery-box-freeflow-indicators-only.json create mode 100644 flutter-sample/assets/test-configs/test-159-gallery-box-freeflow-navbtns-only.json create mode 100644 flutter-sample/assets/test-configs/test-160-gallery-box-freeflow-minimal.json create mode 100644 flutter-sample/assets/test-configs/test-161-gallery-box-freeflow-tall-images.json create mode 100644 flutter-sample/assets/test-configs/test-162-gallery-box-freeflow-video-items.json create mode 100644 flutter-sample/assets/test-configs/test-163-gallery-box-freeflow-button-items.json create mode 100644 flutter-sample/assets/test-configs/test-164-gallery-box-freeflow-5items.json create mode 100644 flutter-sample/assets/test-configs/test-165-gallery-box-grid2col-indicators-navbtns.json create mode 100644 flutter-sample/assets/test-configs/test-166-gallery-box-grid2col-indicators-only.json create mode 100644 flutter-sample/assets/test-configs/test-167-gallery-box-grid2col-navbtns-only.json create mode 100644 flutter-sample/assets/test-configs/test-168-gallery-box-grid2col-minimal.json create mode 100644 flutter-sample/assets/test-configs/test-169-gallery-box-grid3col-indicators.json create mode 100644 flutter-sample/assets/test-configs/test-170-gallery-box-grid3col-navbtns.json create mode 100644 flutter-sample/assets/test-configs/test-171-gallery-box-grid2col-video.json create mode 100644 flutter-sample/assets/test-configs/test-172-gallery-box-grid2col-vertical.json create mode 100644 flutter-sample/assets/test-configs/test-172-video-fullscreen-openurl.json create mode 100644 flutter-sample/assets/test-configs/test-173-gallery-box-snapping-indicators-navbtns.json create mode 100644 flutter-sample/assets/test-configs/test-174-gallery-box-snapping-indicators-only.json create mode 100644 flutter-sample/assets/test-configs/test-175-gallery-box-snapping-navbtns-only.json create mode 100644 flutter-sample/assets/test-configs/test-176-gallery-box-snapping-minimal.json create mode 100644 flutter-sample/assets/test-configs/test-177-html-inline-basic.json create mode 100644 flutter-sample/assets/test-configs/test-178-html-with-javascript.json create mode 100644 flutter-sample/assets/test-configs/test-179-html-transparent-bg.json create mode 100644 flutter-sample/assets/test-configs/test-180-html-scrollable-content.json create mode 100644 flutter-sample/assets/test-configs/test-VERIFY-percentage-offset-fix.json create mode 100644 flutter-sample/assets/test-configs/test-parity-80pct-ar.json create mode 100644 flutter-sample/linux/.gitignore create mode 100644 flutter-sample/linux/CMakeLists.txt create mode 100644 flutter-sample/linux/flutter/CMakeLists.txt create mode 100644 flutter-sample/linux/flutter/generated_plugin_registrant.cc create mode 100644 flutter-sample/linux/flutter/generated_plugin_registrant.h create mode 100644 flutter-sample/linux/flutter/generated_plugins.cmake create mode 100644 flutter-sample/linux/runner/CMakeLists.txt create mode 100644 flutter-sample/linux/runner/main.cc create mode 100644 flutter-sample/linux/runner/my_application.cc create mode 100644 flutter-sample/linux/runner/my_application.h create mode 100644 flutter-sample/macos/.gitignore create mode 100644 flutter-sample/macos/Flutter/Flutter-Debug.xcconfig create mode 100644 flutter-sample/macos/Flutter/Flutter-Release.xcconfig create mode 100644 flutter-sample/macos/Flutter/GeneratedPluginRegistrant.swift create mode 100644 flutter-sample/macos/Podfile create mode 100644 flutter-sample/macos/Runner.xcodeproj/project.pbxproj create mode 100644 flutter-sample/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme create mode 100644 flutter-sample/macos/Runner/AppDelegate.swift create mode 100644 flutter-sample/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 flutter-sample/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png create mode 100644 flutter-sample/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png create mode 100644 flutter-sample/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png create mode 100644 flutter-sample/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png create mode 100644 flutter-sample/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png create mode 100644 flutter-sample/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png create mode 100644 flutter-sample/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png create mode 100644 flutter-sample/macos/Runner/Base.lproj/MainMenu.xib create mode 100644 flutter-sample/macos/Runner/Configs/AppInfo.xcconfig create mode 100644 flutter-sample/macos/Runner/Configs/Debug.xcconfig create mode 100644 flutter-sample/macos/Runner/Configs/Release.xcconfig create mode 100644 flutter-sample/macos/Runner/Configs/Warnings.xcconfig create mode 100644 flutter-sample/macos/Runner/DebugProfile.entitlements create mode 100644 flutter-sample/macos/Runner/Info.plist create mode 100644 flutter-sample/macos/Runner/MainFlutterWindow.swift create mode 100644 flutter-sample/macos/Runner/Release.entitlements create mode 100644 flutter-sample/macos/RunnerTests/RunnerTests.swift create mode 100644 flutter-sample/web/favicon.png create mode 100644 flutter-sample/web/icons/Icon-192.png create mode 100644 flutter-sample/web/icons/Icon-512.png create mode 100644 flutter-sample/web/icons/Icon-maskable-192.png create mode 100644 flutter-sample/web/icons/Icon-maskable-512.png create mode 100644 flutter-sample/web/index.html create mode 100644 flutter-sample/web/manifest.json create mode 100644 flutter-sample/windows/.gitignore create mode 100644 flutter-sample/windows/CMakeLists.txt create mode 100644 flutter-sample/windows/flutter/CMakeLists.txt create mode 100644 flutter-sample/windows/flutter/generated_plugin_registrant.cc create mode 100644 flutter-sample/windows/flutter/generated_plugin_registrant.h create mode 100644 flutter-sample/windows/flutter/generated_plugins.cmake create mode 100644 flutter-sample/windows/runner/CMakeLists.txt create mode 100644 flutter-sample/windows/runner/Runner.rc create mode 100644 flutter-sample/windows/runner/flutter_window.cpp create mode 100644 flutter-sample/windows/runner/flutter_window.h create mode 100644 flutter-sample/windows/runner/main.cpp create mode 100644 flutter-sample/windows/runner/resource.h create mode 100644 flutter-sample/windows/runner/resources/app_icon.ico create mode 100644 flutter-sample/windows/runner/runner.exe.manifest create mode 100644 flutter-sample/windows/runner/utils.cpp create mode 100644 flutter-sample/windows/runner/utils.h create mode 100644 flutter-sample/windows/runner/win32_window.cpp create mode 100644 flutter-sample/windows/runner/win32_window.h diff --git a/flutter-sample/.flutter-plugins b/flutter-sample/.flutter-plugins index c4b8788..0452d8d 100644 --- a/flutter-sample/.flutter-plugins +++ b/flutter-sample/.flutter-plugins @@ -1,5 +1,6 @@ # This is a generated file; do not edit or check into version control. clevertap_native_display=/Users/lalitkumar/StudioProjects/clevertap-native-ui-kit/flutter/ +clevertap_plugin=/Users/lalitkumar/.pub-cache/hosted/pub.dev/clevertap_plugin-4.0.0/ path_provider=/Users/lalitkumar/.pub-cache/hosted/pub.dev/path_provider-2.1.5/ path_provider_android=/Users/lalitkumar/.pub-cache/hosted/pub.dev/path_provider_android-2.2.19/ path_provider_foundation=/Users/lalitkumar/.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.2/ diff --git a/flutter-sample/.flutter-plugins-dependencies b/flutter-sample/.flutter-plugins-dependencies index 700b01e..f4a7a03 100644 --- a/flutter-sample/.flutter-plugins-dependencies +++ b/flutter-sample/.flutter-plugins-dependencies @@ -1 +1 @@ -{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"clevertap_native_display","path":"/Users/lalitkumar/StudioProjects/clevertap-native-ui-kit/flutter/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_foundation","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.2/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"sqflite_darwin","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/sqflite_darwin-2.4.2/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_ios","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/url_launcher_ios-6.3.4/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"video_player_avfoundation","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/video_player_avfoundation-2.8.4/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"webview_flutter_wkwebview","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.23.0/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"clevertap_native_display","path":"/Users/lalitkumar/StudioProjects/clevertap-native-ui-kit/flutter/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_android","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/path_provider_android-2.2.19/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"sqflite_android","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/sqflite_android-2.4.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_android","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/url_launcher_android-6.3.20/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"video_player_android","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/video_player_android-2.8.15/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"webview_flutter_android","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/webview_flutter_android-4.10.1/","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[{"name":"path_provider_foundation","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.2/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"sqflite_darwin","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/sqflite_darwin-2.4.2/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_macos","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/url_launcher_macos-3.2.3/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"video_player_avfoundation","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/video_player_avfoundation-2.8.4/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"webview_flutter_wkwebview","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.23.0/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"linux":[{"name":"path_provider_linux","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_linux","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/url_launcher_linux-3.2.1/","native_build":true,"dependencies":[],"dev_dependency":false}],"windows":[{"name":"path_provider_windows","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/path_provider_windows-2.3.0/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_windows","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/url_launcher_windows-3.1.4/","native_build":true,"dependencies":[],"dev_dependency":false}],"web":[{"name":"url_launcher_web","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/url_launcher_web-2.4.1/","dependencies":[],"dev_dependency":false},{"name":"video_player_web","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/video_player_web-2.4.0/","dependencies":[],"dev_dependency":false}]},"dependencyGraph":[{"name":"clevertap_native_display","dependencies":["video_player","webview_flutter","url_launcher"]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"sqflite","dependencies":["sqflite_android","sqflite_darwin"]},{"name":"sqflite_android","dependencies":[]},{"name":"sqflite_darwin","dependencies":[]},{"name":"url_launcher","dependencies":["url_launcher_android","url_launcher_ios","url_launcher_linux","url_launcher_macos","url_launcher_web","url_launcher_windows"]},{"name":"url_launcher_android","dependencies":[]},{"name":"url_launcher_ios","dependencies":[]},{"name":"url_launcher_linux","dependencies":[]},{"name":"url_launcher_macos","dependencies":[]},{"name":"url_launcher_web","dependencies":[]},{"name":"url_launcher_windows","dependencies":[]},{"name":"video_player","dependencies":["video_player_android","video_player_avfoundation","video_player_web"]},{"name":"video_player_android","dependencies":[]},{"name":"video_player_avfoundation","dependencies":[]},{"name":"video_player_web","dependencies":[]},{"name":"webview_flutter","dependencies":["webview_flutter_android","webview_flutter_wkwebview"]},{"name":"webview_flutter_android","dependencies":[]},{"name":"webview_flutter_wkwebview","dependencies":[]}],"date_created":"2026-05-25 16:57:40.544270","version":"3.29.0","swift_package_manager_enabled":{"ios":false,"macos":false}} \ No newline at end of file +{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"clevertap_native_display","path":"/Users/lalitkumar/StudioProjects/clevertap-native-ui-kit/flutter/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"clevertap_plugin","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/clevertap_plugin-4.0.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_foundation","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.2/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"sqflite_darwin","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/sqflite_darwin-2.4.2/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_ios","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/url_launcher_ios-6.3.4/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"video_player_avfoundation","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/video_player_avfoundation-2.8.4/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"webview_flutter_wkwebview","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.23.0/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"clevertap_native_display","path":"/Users/lalitkumar/StudioProjects/clevertap-native-ui-kit/flutter/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"clevertap_plugin","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/clevertap_plugin-4.0.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_android","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/path_provider_android-2.2.19/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"sqflite_android","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/sqflite_android-2.4.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_android","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/url_launcher_android-6.3.20/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"video_player_android","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/video_player_android-2.8.15/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"webview_flutter_android","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/webview_flutter_android-4.10.1/","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[{"name":"path_provider_foundation","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.2/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"sqflite_darwin","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/sqflite_darwin-2.4.2/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_macos","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/url_launcher_macos-3.2.3/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"video_player_avfoundation","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/video_player_avfoundation-2.8.4/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"webview_flutter_wkwebview","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.23.0/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"linux":[{"name":"path_provider_linux","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_linux","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/url_launcher_linux-3.2.1/","native_build":true,"dependencies":[],"dev_dependency":false}],"windows":[{"name":"path_provider_windows","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/path_provider_windows-2.3.0/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_windows","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/url_launcher_windows-3.1.4/","native_build":true,"dependencies":[],"dev_dependency":false}],"web":[{"name":"clevertap_plugin","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/clevertap_plugin-4.0.0/","dependencies":[],"dev_dependency":false},{"name":"url_launcher_web","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/url_launcher_web-2.4.1/","dependencies":[],"dev_dependency":false},{"name":"video_player_web","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/video_player_web-2.4.0/","dependencies":[],"dev_dependency":false}]},"dependencyGraph":[{"name":"clevertap_native_display","dependencies":["video_player","webview_flutter","url_launcher"]},{"name":"clevertap_plugin","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"sqflite","dependencies":["sqflite_android","sqflite_darwin"]},{"name":"sqflite_android","dependencies":[]},{"name":"sqflite_darwin","dependencies":[]},{"name":"url_launcher","dependencies":["url_launcher_android","url_launcher_ios","url_launcher_linux","url_launcher_macos","url_launcher_web","url_launcher_windows"]},{"name":"url_launcher_android","dependencies":[]},{"name":"url_launcher_ios","dependencies":[]},{"name":"url_launcher_linux","dependencies":[]},{"name":"url_launcher_macos","dependencies":[]},{"name":"url_launcher_web","dependencies":[]},{"name":"url_launcher_windows","dependencies":[]},{"name":"video_player","dependencies":["video_player_android","video_player_avfoundation","video_player_web"]},{"name":"video_player_android","dependencies":[]},{"name":"video_player_avfoundation","dependencies":[]},{"name":"video_player_web","dependencies":[]},{"name":"webview_flutter","dependencies":["webview_flutter_android","webview_flutter_wkwebview"]},{"name":"webview_flutter_android","dependencies":[]},{"name":"webview_flutter_wkwebview","dependencies":[]}],"date_created":"2026-05-27 03:24:35.977293","version":"3.29.0","swift_package_manager_enabled":{"ios":false,"macos":false}} \ No newline at end of file diff --git a/flutter-sample/.metadata b/flutter-sample/.metadata new file mode 100644 index 0000000..9a674c6 --- /dev/null +++ b/flutter-sample/.metadata @@ -0,0 +1,45 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "35c388afb57ef061d06a39b537336c87e0e3d1b1" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 + base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 + - platform: android + create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 + base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 + - platform: ios + create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 + base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 + - platform: linux + create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 + base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 + - platform: macos + create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 + base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 + - platform: web + create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 + base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 + - platform: windows + create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 + base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/flutter-sample/README.md b/flutter-sample/README.md new file mode 100644 index 0000000..eb0fc7b --- /dev/null +++ b/flutter-sample/README.md @@ -0,0 +1,16 @@ +# clevertap_native_display_sample + +A new Flutter project. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/flutter-sample/android/app/build.gradle.kts b/flutter-sample/android/app/build.gradle.kts index 38e707b..9177472 100644 --- a/flutter-sample/android/app/build.gradle.kts +++ b/flutter-sample/android/app/build.gradle.kts @@ -7,7 +7,7 @@ plugins { android { namespace = "com.clevertap.flutter.clevertap_native_display_sample" - compileSdk = flutter.compileSdkVersion + compileSdk = 36 ndkVersion = "27.0.12077973" compileOptions { @@ -44,6 +44,4 @@ flutter { } dependencies { - // CleverTap Core SDK — provides CleverTapAPI, CTDisplayUnitListener, CleverTapDisplayUnit - implementation("com.clevertap.android:clevertap-android-sdk:8.0.0") } diff --git a/flutter-sample/android/app/src/main/kotlin/com/clevertap/flutter/clevertap_native_display_sample/MainActivity.kt b/flutter-sample/android/app/src/main/kotlin/com/clevertap/flutter/clevertap_native_display_sample/MainActivity.kt index e49c62a..1144a92 100644 --- a/flutter-sample/android/app/src/main/kotlin/com/clevertap/flutter/clevertap_native_display_sample/MainActivity.kt +++ b/flutter-sample/android/app/src/main/kotlin/com/clevertap/flutter/clevertap_native_display_sample/MainActivity.kt @@ -1,60 +1,5 @@ package com.clevertap.flutter.clevertap_native_display_sample -import android.os.Handler -import android.os.Looper import io.flutter.embedding.android.FlutterActivity -import io.flutter.embedding.engine.FlutterEngine -import io.flutter.plugin.common.EventChannel -import io.flutter.plugin.common.MethodChannel -class MainActivity : FlutterActivity() { - - private var eventSink: EventChannel.EventSink? = null - private val mainHandler = Handler(Looper.getMainLooper()) - - companion object { - private const val METHOD_CH = "com.clevertap.flutter/native_display" - private const val EVENT_CH = "com.clevertap.flutter/native_display_events" - } - - override fun configureFlutterEngine(flutterEngine: FlutterEngine) { - super.configureFlutterEngine(flutterEngine) - - EventChannel(flutterEngine.dartExecutor.binaryMessenger, EVENT_CH) - .setStreamHandler(object : EventChannel.StreamHandler { - override fun onListen(arguments: Any?, events: EventChannel.EventSink?) { - eventSink = events - // Replay any units that arrived before Flutter subscribed - val cached = SampleApplication.cachedUnitJsons - if (cached.isNotEmpty()) { - pushUnitsToFlutter(cached) - } - } - override fun onCancel(arguments: Any?) { - eventSink = null - } - }) - - MethodChannel(flutterEngine.dartExecutor.binaryMessenger, METHOD_CH) - .setMethodCallHandler { call, result -> - when (call.method) { - "pushEvent" -> { - val name = call.argument("eventName") ?: "" - if (name.isNotEmpty()) { - SampleApplication.cleverTapApi?.pushEvent(name) - } - result.success(null) - } - else -> result.notImplemented() - } - } - - SampleApplication.onUnitsLoaded = { jsonList -> pushUnitsToFlutter(jsonList) } - } - - private fun pushUnitsToFlutter(jsonList: List) { - mainHandler.post { - eventSink?.success(mapOf("type" to "units_updated", "units" to jsonList)) - } - } -} +class MainActivity : FlutterActivity() diff --git a/flutter-sample/android/app/src/main/kotlin/com/clevertap/flutter/clevertap_native_display_sample/SampleApplication.kt b/flutter-sample/android/app/src/main/kotlin/com/clevertap/flutter/clevertap_native_display_sample/SampleApplication.kt index 450ac12..99ee61a 100644 --- a/flutter-sample/android/app/src/main/kotlin/com/clevertap/flutter/clevertap_native_display_sample/SampleApplication.kt +++ b/flutter-sample/android/app/src/main/kotlin/com/clevertap/flutter/clevertap_native_display_sample/SampleApplication.kt @@ -1,88 +1,14 @@ package com.clevertap.flutter.clevertap_native_display_sample import android.app.Application -import android.util.Log import com.clevertap.android.sdk.ActivityLifecycleCallback import com.clevertap.android.sdk.CleverTapAPI import com.clevertap.android.sdk.CleverTapAPI.LogLevel.VERBOSE -import com.clevertap.android.sdk.displayunits.DisplayUnitListener -import com.clevertap.android.sdk.displayunits.model.CleverTapDisplayUnit -import org.json.JSONObject class SampleApplication : Application() { - - companion object { - private const val TAG = "SampleApplication" - - var cleverTapApi: CleverTapAPI? = null - - /** Last received batch — replayed to new EventChannel sinks so units aren't lost. */ - var cachedUnitJsons: List = emptyList() - - /** Set by MainActivity once the EventChannel sink is ready. */ - var onUnitsLoaded: ((List) -> Unit)? = null - } - override fun onCreate() { CleverTapAPI.setDebugLevel(VERBOSE) ActivityLifecycleCallback.register(this) super.onCreate() - - val ct = CleverTapAPI.getDefaultInstance(this) - cleverTapApi = ct - - if (ct != null) { - ct.setDisplayUnitListener(object : DisplayUnitListener { - override fun onDisplayUnitsLoaded(units: ArrayList) { - Log.d(TAG, "Received ${units.size} display unit(s)") - val jsonList = units.mapNotNull { unit -> - extractNdConfigJson(unit.jsonObject) - }.filter { it.isNotEmpty() } - Log.d(TAG, "Extracted ${jsonList.size} NativeDisplayConfig JSON(s)") - cachedUnitJsons = jsonList - onUnitsLoaded?.invoke(jsonList) - } - }) - Log.d(TAG, "CleverTap initialized, display unit listener registered") - } else { - Log.w(TAG, "CleverTapAPI.getDefaultInstance() returned null — check manifest metadata") - } - } - - /** - * Mirrors NativeDisplayConfigParser detection strategy (3 attempts in order): - * - * 1. `native_display_config` top-level key → parse its value as NativeDisplayConfig JSON - * 2. `custom_kv.nd_config` string value → that string IS the NativeDisplayConfig JSON - * 3. `root` top-level key present → entire JSON object is treated as NativeDisplayConfig - * - * Returns null if the unit does not contain a recognizable NativeDisplayConfig payload. - */ - private fun extractNdConfigJson(json: JSONObject?): String? { - if (json == null) return null - - // Strategy 1: native_display_config key - json.optJSONObject("native_display_config")?.let { ndConfig -> - Log.d(TAG, "Found NativeDisplayConfig via 'native_display_config' key") - return ndConfig.toString() - } - - // Strategy 2: custom_kv.nd_config string - json.optJSONObject("custom_kv") - ?.optString("nd_config", null) - ?.takeIf { it.isNotEmpty() } - ?.let { ndConfigStr -> - Log.d(TAG, "Found NativeDisplayConfig via 'custom_kv.nd_config'") - return ndConfigStr - } - - // Strategy 3: root key present — entire JSON is NativeDisplayConfig - if (json.has("root")) { - Log.d(TAG, "Found NativeDisplayConfig via top-level 'root' key") - return json.toString() - } - - Log.w(TAG, "Display unit does not contain a NativeDisplayConfig payload, skipping") - return null } } diff --git a/flutter-sample/assets/configs/test_simple.json b/flutter-sample/assets/configs/test_simple.json new file mode 100644 index 0000000..4640f9b --- /dev/null +++ b/flutter-sample/assets/configs/test_simple.json @@ -0,0 +1,40 @@ +{ + "theme": { + "id": "default", + "defaultStyle": { + "textColor": "#000000", + "fontSize": 14 + }, + "colors": {} + }, + "styleClasses": [], + "variables": {}, + "root": { + "type": "container", + "id": "test_root", + "containerType": "vertical", + "layout": { + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#FFFFFF" + }, + "children": [ + { + "type": "element", + "id": "test_text", + "elementType": "text", + "bindings": { + "text": "Hello World" + }, + "style": { + "textColor": "#000000", + "fontSize": 20, + "fontWeight": "bold" + } + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-001-vertical-simple.json b/flutter-sample/assets/test-configs/test-001-vertical-simple.json new file mode 100644 index 0000000..bd3bf8a --- /dev/null +++ b/flutter-sample/assets/test-configs/test-001-vertical-simple.json @@ -0,0 +1,91 @@ +{ + "theme": { + "id": "default", + "defaultStyle": { + "textColor": "#212121", + "fontSize": 14, + "fontFamily": "system", + "lineHeight": 20 + } + }, + "root": { + "type": "container", + "id": "vertical-container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "spacing": 12, + "strategy": "spaced", + "spacingUnit": "dp" + } + }, + "style": { + "background": { + "type": "solid", + "color": "#E3F2FD" + } + }, + "children": [ + { + "type": "element", + "id": "text-element", + "elementType": "text", + "bindings": { + "text": "VERTICAL Container Test" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 20, + "fontWeight": "bold", + "textColor": "#000000", + "lineHeight": 28 + } + }, + { + "type": "element", + "id": "image-element", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-1.jpg" + }, + "layout": { + "width": { + "value": 200, + "unit": "dp" + }, + "height": { + "value": 200, + "unit": "dp" + } + }, + "style": { + "borderRadius": 8 + } + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-002-horizontal-simple.json b/flutter-sample/assets/test-configs/test-002-horizontal-simple.json new file mode 100644 index 0000000..2dfad70 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-002-horizontal-simple.json @@ -0,0 +1,96 @@ +{ + "theme": { + "id": "default", + "defaultStyle": { + "textColor": "#212121", + "fontSize": 14, + "fontFamily": "system", + "lineHeight": 20 + } + }, + "root": { + "type": "container", + "id": "horizontal-container", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "spacing": 12, + "strategy": "spaced", + "spacingUnit": "dp" + } + }, + "style": { + "background": { + "type": "linear_gradient", + "angle": 135, + "colors": [ + "#667eea", + "#764ba2" + ] + } + }, + "children": [ + { + "type": "element", + "id": "text-element", + "elementType": "text", + "bindings": { + "text": "HORIZONTAL Container Test" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 20, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "lineHeight": 28 + } + }, + { + "type": "element", + "id": "image-element", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-2.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "dp" + }, + "height": { + "value": 100, + "unit": "dp" + } + }, + "style": { + "borderRadius": 8 + } + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-003-box-simple.json b/flutter-sample/assets/test-configs/test-003-box-simple.json new file mode 100644 index 0000000..8008f20 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-003-box-simple.json @@ -0,0 +1,99 @@ +{ + "theme": { + "id": "default", + "defaultStyle": { + "textColor": "#212121", + "fontSize": 14, + "fontFamily": "system", + "lineHeight": 20 + } + }, + "root": { + "type": "container", + "id": "box-container", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 250, + "unit": "dp" + }, + "padding": { + "all": 16 + } + }, + "style": { + "background": { + "type": "radial_gradient", + "center_x": 0.5, + "center_y": 0.5, + "radius": 1.0, + "colors": [ + "#FFF9C4", + "#FFE082", + "#FFD54F" + ] + } + }, + "children": [ + { + "type": "element", + "id": "image-element", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-3.jpg" + }, + "layout": { + "width": { + "value": 200, + "unit": "dp" + }, + "height": { + "value": 200, + "unit": "dp" + } + }, + "style": { + "borderRadius": 8 + } + }, + { + "type": "element", + "id": "text-element", + "elementType": "text", + "bindings": { + "text": "BOX Container Test" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "offset": { + "x": 16, + "y": 210, + "unit": "dp" + } + }, + "style": { + "fontSize": 16, + "fontWeight": "bold", + "textColor": "#000000", + "backgroundColor": "#FFFFFFCC", + "lineHeight": 22 + } + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-004-stack-simple.json b/flutter-sample/assets/test-configs/test-004-stack-simple.json new file mode 100644 index 0000000..86b222d --- /dev/null +++ b/flutter-sample/assets/test-configs/test-004-stack-simple.json @@ -0,0 +1,105 @@ +{ + "theme": { + "id": "default", + "defaultStyle": { + "textColor": "#212121", + "fontSize": 14, + "fontFamily": "system", + "lineHeight": 20 + } + }, + "root": { + "type": "container", + "id": "stack-container", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 250, + "unit": "dp" + }, + "padding": { + "all": 16 + } + }, + "style": { + "background": { + "type": "layered", + "layers": [ + { + "type": "solid", + "color": "#E8F5E9" + }, + { + "type": "pattern", + "pattern_type": "dots", + "primary_color": "#00000000", + "secondary_color": "#4CAF5020", + "size": 4, + "spacing": 20 + } + ] + } + }, + "children": [ + { + "type": "element", + "id": "image-element", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-4.jpg" + }, + "layout": { + "width": { + "value": 200, + "unit": "dp" + }, + "height": { + "value": 200, + "unit": "dp" + } + }, + "style": { + "borderRadius": 8 + } + }, + { + "type": "element", + "id": "text-element", + "elementType": "text", + "bindings": { + "text": "STACK Container Test" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "offset": { + "x": 8, + "y": 8, + "unit": "dp" + } + }, + "style": { + "fontSize": 16, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "backgroundColor": "#000000CC", + "lineHeight": 22 + } + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-005-gallery-simple.json b/flutter-sample/assets/test-configs/test-005-gallery-simple.json new file mode 100644 index 0000000..9d7312f --- /dev/null +++ b/flutter-sample/assets/test-configs/test-005-gallery-simple.json @@ -0,0 +1,224 @@ +{ + "theme": { + "id": "default", + "defaultStyle": { + "textColor": "#212121", + "fontSize": 14, + "fontFamily": "system", + "lineHeight": 20 + } + }, + "root": { + "type": "container", + "id": "gallery-container", + "containerType": "gallery", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 250, + "unit": "dp" + } + }, + "galleryConfig": { + "mode": "snapping", + "orientation": "horizontal", + "snapBehavior": "center", + "peekPercentage": 10, + "spacing": 16, + "showIndicators": true + }, + "style": { + "background": { + "type": "shimmer", + "base_color": "#F0F0F0", + "highlight_color": "#FFFFFF", + "angle": 45, + "duration": 1500, + "loop": true + } + }, + "children": [ + { + "type": "container", + "id": "gallery-item-1", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 8 + }, + "arrangement": { + "spacing": 8, + "strategy": "spaced", + "spacingUnit": "dp" + } + }, + "style": { + "background": { + "type": "animated_gradient", + "gradient_type": "linear", + "angle": 90, + "colors": [ + "#FF6B6B", + "#FFA500", + "#FFD700" + ], + "duration": 3000, + "loop": true, + "animation_style": "shift" + }, + "borderRadius": 12 + }, + "children": [ + { + "type": "element", + "id": "text-1", + "elementType": "text", + "bindings": { + "text": "GALLERY Container Test" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "textColor": "#000000", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "image-1", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-5.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 180, + "unit": "dp" + } + }, + "style": { + "borderRadius": 8 + } + } + ] + }, + { + "type": "container", + "id": "gallery-item-2", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 8 + }, + "arrangement": { + "spacing": 8, + "strategy": "spaced", + "spacingUnit": "dp" + } + }, + "style": { + "background": { + "type": "animated_gradient", + "gradient_type": "linear", + "angle": 90, + "colors": [ + "#4ECDC4", + "#44A08D", + "#4ECDC4" + ], + "duration": 3000, + "loop": true, + "animation_style": "shift" + }, + "borderRadius": 12 + }, + "children": [ + { + "type": "element", + "id": "text-2", + "elementType": "text", + "bindings": { + "text": "Second Item" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "textColor": "#000000", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "image-2", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-6.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 180, + "unit": "dp" + } + }, + "style": { + "borderRadius": 8 + } + } + ] + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-006-vertical-empty.json b/flutter-sample/assets/test-configs/test-006-vertical-empty.json new file mode 100644 index 0000000..3a7936c --- /dev/null +++ b/flutter-sample/assets/test-configs/test-006-vertical-empty.json @@ -0,0 +1,37 @@ +{ + "theme": { + "id": "test-theme", + "defaultStyle": { + "textColor": "#333333", + "fontSize": 16, + "lineHeight": 22 + } + }, + "root": { + "type": "container", + "id": "vertical-empty", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 200, + "unit": "dp" + }, + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#E3F2FD", + "borderRadius": 12, + "borderWidth": 2, + "borderColor": "#1976D2" + }, + "children": [] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-007-vertical-single-child.json b/flutter-sample/assets/test-configs/test-007-vertical-single-child.json new file mode 100644 index 0000000..ab08aae --- /dev/null +++ b/flutter-sample/assets/test-configs/test-007-vertical-single-child.json @@ -0,0 +1,71 @@ +{ + "theme": { + "id": "test-theme", + "defaultStyle": { + "textColor": "#212121", + "fontSize": 16, + "lineHeight": 22 + } + }, + "root": { + "type": "container", + "id": "vertical-single", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 300, + "unit": "dp" + }, + "padding": { + "all": 20 + }, + "arrangement": { + "strategy": "center" + } + }, + "style": { + "background": { + "type": "linear_gradient", + "angle": 135, + "colors": [ + "#667EEA", + "#764BA2" + ] + } + }, + "children": [ + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "Welcome to Native Display" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 28, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "textAlign": "center", + "lineHeight": 39 + } + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-008-vertical-3-children.json b/flutter-sample/assets/test-configs/test-008-vertical-3-children.json new file mode 100644 index 0000000..9b64c5d --- /dev/null +++ b/flutter-sample/assets/test-configs/test-008-vertical-3-children.json @@ -0,0 +1,109 @@ +{ + "theme": { + "id": "test-theme", + "defaultStyle": { + "textColor": "#424242", + "fontSize": 16, + "lineHeight": 22 + } + }, + "root": { + "type": "container", + "id": "vertical-3-children", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 500, + "unit": "dp" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "strategy": "space_between" + } + }, + "style": { + "backgroundColor": "#FFF3E0", + "borderRadius": 16 + }, + "children": [ + { + "type": "element", + "id": "header", + "elementType": "text", + "bindings": { + "text": "Product Showcase" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 24, + "fontWeight": "bold", + "textColor": "#E65100", + "lineHeight": 34 + } + }, + { + "type": "element", + "id": "product-image", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-7.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 300, + "unit": "dp" + } + }, + "style": { + "borderRadius": 12 + } + }, + { + "type": "element", + "id": "description", + "elementType": "text", + "bindings": { + "text": "Discover our premium collection of handcrafted items" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 16, + "textColor": "#5D4037", + "lineHeight": 24 + } + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-009-vertical-5-children.json b/flutter-sample/assets/test-configs/test-009-vertical-5-children.json new file mode 100644 index 0000000..28780e4 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-009-vertical-5-children.json @@ -0,0 +1,172 @@ +{ + "theme": { + "id": "test-theme", + "defaultStyle": { + "textColor": "#263238", + "fontSize": 16, + "lineHeight": 22 + } + }, + "root": { + "type": "container", + "id": "vertical-5-children", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 700, + "unit": "dp" + }, + "padding": { + "top": 20, + "bottom": 20, + "left": 16, + "right": 16 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 12, + "spacingUnit": "dp" + } + }, + "style": { + "background": { + "type": "radial_gradient", + "colors": [ + "#C5E1A5", + "#7CB342" + ], + "centerX": 0.5, + "centerY": 0.5, + "radius": 1.0 + } + }, + "children": [ + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "Feature Highlights" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 28, + "fontWeight": "bold", + "textColor": "#1B5E20", + "lineHeight": 39 + } + }, + { + "type": "element", + "id": "feature-1", + "elementType": "text", + "bindings": { + "text": "Fast Performance - Lightning quick rendering" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 18, + "textColor": "#2E7D32", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "image", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-8.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 200, + "unit": "dp" + } + }, + "style": { + "borderRadius": 8 + } + }, + { + "type": "element", + "id": "feature-2", + "elementType": "text", + "bindings": { + "text": "Cross-Platform - Works on Android and iOS" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 18, + "textColor": "#2E7D32", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "cta", + "elementType": "button", + "bindings": { + "text": "Get Started" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 50, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#33691E", + "textColor": "#FFFFFF", + "fontSize": 18, + "fontWeight": "bold", + "borderRadius": 25, + "lineHeight": 25 + } + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-010-vertical-10-children.json b/flutter-sample/assets/test-configs/test-010-vertical-10-children.json new file mode 100644 index 0000000..b9df54d --- /dev/null +++ b/flutter-sample/assets/test-configs/test-010-vertical-10-children.json @@ -0,0 +1,267 @@ +{ + "theme": { + "id": "test-theme", + "defaultStyle": { + "textColor": "#37474F", + "fontSize": 14, + "lineHeight": 20 + } + }, + "root": { + "type": "container", + "id": "vertical-10-children", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 1200, + "unit": "dp" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 8, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#ECEFF1" + }, + "children": [ + { + "type": "element", + "id": "header", + "elementType": "text", + "bindings": { + "text": "Daily News Feed" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 26, + "fontWeight": "bold", + "textColor": "#263238", + "lineHeight": 36 + } + }, + { + "type": "element", + "id": "news-1", + "elementType": "text", + "bindings": { + "text": "Breaking: New SDK Release Available" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 16, + "textColor": "#455A64", + "lineHeight": 22 + } + }, + { + "type": "element", + "id": "image-1", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-9.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 150, + "unit": "dp" + } + }, + "style": { + "borderRadius": 8 + } + }, + { + "type": "element", + "id": "news-2", + "elementType": "text", + "bindings": { + "text": "Tech: AI Advances in Mobile Development" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 16, + "textColor": "#455A64", + "lineHeight": 22 + } + }, + { + "type": "element", + "id": "image-2", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-10.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 150, + "unit": "dp" + } + }, + "style": { + "borderRadius": 8 + } + }, + { + "type": "element", + "id": "news-3", + "elementType": "text", + "bindings": { + "text": "Business: Market Trends for 2026" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 16, + "textColor": "#455A64", + "lineHeight": 22 + } + }, + { + "type": "element", + "id": "image-3", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-11.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 150, + "unit": "dp" + } + }, + "style": { + "borderRadius": 8 + } + }, + { + "type": "element", + "id": "news-4", + "elementType": "text", + "bindings": { + "text": "Sports: Championship Finals This Weekend" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 16, + "textColor": "#455A64", + "lineHeight": 22 + } + }, + { + "type": "element", + "id": "divider", + "elementType": "divider", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 2, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#B0BEC5" + } + }, + { + "type": "element", + "id": "footer", + "elementType": "text", + "bindings": { + "text": "Load more stories..." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#78909C", + "textAlign": "center", + "lineHeight": 20 + } + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-011-horizontal-empty.json b/flutter-sample/assets/test-configs/test-011-horizontal-empty.json new file mode 100644 index 0000000..570a73e --- /dev/null +++ b/flutter-sample/assets/test-configs/test-011-horizontal-empty.json @@ -0,0 +1,42 @@ +{ + "theme": { + "id": "test-theme", + "defaultStyle": { + "textColor": "#424242", + "fontSize": 16, + "lineHeight": 22 + } + }, + "root": { + "type": "container", + "id": "horizontal-empty", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 120, + "unit": "dp" + }, + "padding": { + "all": 16 + } + }, + "style": { + "background": { + "type": "linear_gradient", + "angle": 90, + "colors": [ + "#FCE4EC", + "#F8BBD0" + ] + }, + "borderRadius": 8 + }, + "children": [] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-012-horizontal-single-child.json b/flutter-sample/assets/test-configs/test-012-horizontal-single-child.json new file mode 100644 index 0000000..5cab89c --- /dev/null +++ b/flutter-sample/assets/test-configs/test-012-horizontal-single-child.json @@ -0,0 +1,64 @@ +{ + "theme": { + "id": "test-theme", + "defaultStyle": { + "textColor": "#1A237E", + "fontSize": 16, + "lineHeight": 22 + } + }, + "root": { + "type": "container", + "id": "horizontal-single", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 150, + "unit": "dp" + }, + "padding": { + "all": 20 + }, + "arrangement": { + "strategy": "center" + } + }, + "style": { + "backgroundColor": "#E8EAF6" + }, + "children": [ + { + "type": "element", + "id": "icon-button", + "elementType": "button", + "bindings": { + "text": "Click Me" + }, + "layout": { + "width": { + "value": 150, + "unit": "dp" + }, + "height": { + "value": 50, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#3F51B5", + "textColor": "#FFFFFF", + "fontSize": 18, + "fontWeight": "bold", + "borderRadius": 25, + "lineHeight": 25 + } + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-013-horizontal-3-children.json b/flutter-sample/assets/test-configs/test-013-horizontal-3-children.json new file mode 100644 index 0000000..4005e77 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-013-horizontal-3-children.json @@ -0,0 +1,102 @@ +{ + "theme": { + "id": "test-theme", + "defaultStyle": { + "textColor": "#4E342E", + "fontSize": 14, + "lineHeight": 20 + } + }, + "root": { + "type": "container", + "id": "horizontal-3-children", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 200, + "unit": "dp" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "strategy": "space_evenly" + } + }, + "style": { + "backgroundColor": "#EFEBE9", + "borderRadius": 12 + }, + "children": [ + { + "type": "element", + "id": "card-1", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-12.jpg" + }, + "layout": { + "width": { + "value": 150, + "unit": "dp" + }, + "height": { + "value": 150, + "unit": "dp" + } + }, + "style": { + "borderRadius": 8 + } + }, + { + "type": "element", + "id": "card-2", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-13.jpg" + }, + "layout": { + "width": { + "value": 150, + "unit": "dp" + }, + "height": { + "value": 150, + "unit": "dp" + } + }, + "style": { + "borderRadius": 8 + } + }, + { + "type": "element", + "id": "card-3", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-14.jpg" + }, + "layout": { + "width": { + "value": 150, + "unit": "dp" + }, + "height": { + "value": 150, + "unit": "dp" + } + }, + "style": { + "borderRadius": 8 + } + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-014-horizontal-5-children.json b/flutter-sample/assets/test-configs/test-014-horizontal-5-children.json new file mode 100644 index 0000000..a7f0f58 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-014-horizontal-5-children.json @@ -0,0 +1,215 @@ +{ + "theme": { + "id": "test-theme", + "defaultStyle": { + "textColor": "#004D40", + "fontSize": 12, + "lineHeight": 17 + } + }, + "root": { + "type": "container", + "id": "horizontal-5-children", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "dp" + }, + "padding": { + "all": 12 + }, + "arrangement": { + "strategy": "space_between" + } + }, + "style": { + "background": { + "type": "linear_gradient", + "angle": 180, + "colors": [ + "#B2DFDB", + "#4DB6AC" + ] + } + }, + "children": [ + { + "type": "element", + "id": "tag-1", + "elementType": "text", + "bindings": { + "text": "New" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "top": 8, + "bottom": 8, + "left": 16, + "right": 16 + } + }, + "style": { + "backgroundColor": "#00897B", + "textColor": "#FFFFFF", + "fontSize": 14, + "fontWeight": "bold", + "borderRadius": 16, + "lineHeight": 20 + } + }, + { + "type": "element", + "id": "tag-2", + "elementType": "text", + "bindings": { + "text": "Featured" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "top": 8, + "bottom": 8, + "left": 16, + "right": 16 + } + }, + "style": { + "backgroundColor": "#00796B", + "textColor": "#FFFFFF", + "fontSize": 14, + "fontWeight": "bold", + "borderRadius": 16, + "lineHeight": 20 + } + }, + { + "type": "element", + "id": "tag-3", + "elementType": "text", + "bindings": { + "text": "Popular" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "top": 8, + "bottom": 8, + "left": 16, + "right": 16 + } + }, + "style": { + "backgroundColor": "#00695C", + "textColor": "#FFFFFF", + "fontSize": 14, + "fontWeight": "bold", + "borderRadius": 16, + "lineHeight": 20 + } + }, + { + "type": "element", + "id": "tag-4", + "elementType": "text", + "bindings": { + "text": "Sale" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "top": 8, + "bottom": 8, + "left": 16, + "right": 16 + } + }, + "style": { + "backgroundColor": "#00695C", + "textColor": "#FFFFFF", + "fontSize": 14, + "fontWeight": "bold", + "borderRadius": 16, + "lineHeight": 20 + } + }, + { + "type": "element", + "id": "tag-5", + "elementType": "text", + "bindings": { + "text": "Trending" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "top": 8, + "bottom": 8, + "left": 16, + "right": 16 + } + }, + "style": { + "backgroundColor": "#004D40", + "textColor": "#FFFFFF", + "fontSize": 14, + "fontWeight": "bold", + "borderRadius": 16, + "lineHeight": 20 + } + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-015-horizontal-10-children.json b/flutter-sample/assets/test-configs/test-015-horizontal-10-children.json new file mode 100644 index 0000000..6617ec2 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-015-horizontal-10-children.json @@ -0,0 +1,250 @@ +{ + "theme": { + "id": "test-theme", + "defaultStyle": { + "textColor": "#1A237E", + "fontSize": 10, + "lineHeight": 14 + } + }, + "root": { + "type": "container", + "id": "horizontal-10-children", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 80, + "unit": "dp" + }, + "padding": { + "all": 8 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 4, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#E3F2FD" + }, + "children": [ + { + "type": "element", + "id": "icon-1", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-15.jpg" + }, + "layout": { + "width": { + "value": 60, + "unit": "dp" + }, + "height": { + "value": 60, + "unit": "dp" + } + }, + "style": { + "borderRadius": 30 + } + }, + { + "type": "element", + "id": "icon-2", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-16.jpg" + }, + "layout": { + "width": { + "value": 60, + "unit": "dp" + }, + "height": { + "value": 60, + "unit": "dp" + } + }, + "style": { + "borderRadius": 30 + } + }, + { + "type": "element", + "id": "icon-3", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-17.jpg" + }, + "layout": { + "width": { + "value": 60, + "unit": "dp" + }, + "height": { + "value": 60, + "unit": "dp" + } + }, + "style": { + "borderRadius": 30 + } + }, + { + "type": "element", + "id": "icon-4", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-18.jpg" + }, + "layout": { + "width": { + "value": 60, + "unit": "dp" + }, + "height": { + "value": 60, + "unit": "dp" + } + }, + "style": { + "borderRadius": 30 + } + }, + { + "type": "element", + "id": "icon-5", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-19.jpg" + }, + "layout": { + "width": { + "value": 60, + "unit": "dp" + }, + "height": { + "value": 60, + "unit": "dp" + } + }, + "style": { + "borderRadius": 30 + } + }, + { + "type": "element", + "id": "icon-6", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-20.jpg" + }, + "layout": { + "width": { + "value": 60, + "unit": "dp" + }, + "height": { + "value": 60, + "unit": "dp" + } + }, + "style": { + "borderRadius": 30 + } + }, + { + "type": "element", + "id": "icon-7", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-21.jpg" + }, + "layout": { + "width": { + "value": 60, + "unit": "dp" + }, + "height": { + "value": 60, + "unit": "dp" + } + }, + "style": { + "borderRadius": 30 + } + }, + { + "type": "element", + "id": "icon-8", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-22.jpg" + }, + "layout": { + "width": { + "value": 60, + "unit": "dp" + }, + "height": { + "value": 60, + "unit": "dp" + } + }, + "style": { + "borderRadius": 30 + } + }, + { + "type": "element", + "id": "icon-9", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-23.jpg" + }, + "layout": { + "width": { + "value": 60, + "unit": "dp" + }, + "height": { + "value": 60, + "unit": "dp" + } + }, + "style": { + "borderRadius": 30 + } + }, + { + "type": "element", + "id": "icon-10", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-24.jpg" + }, + "layout": { + "width": { + "value": 60, + "unit": "dp" + }, + "height": { + "value": 60, + "unit": "dp" + } + }, + "style": { + "borderRadius": 30 + } + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-016-box-empty.json b/flutter-sample/assets/test-configs/test-016-box-empty.json new file mode 100644 index 0000000..ec8cd07 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-016-box-empty.json @@ -0,0 +1,46 @@ +{ + "theme": { + "id": "test-theme", + "defaultStyle": { + "textColor": "#311B92", + "fontSize": 16, + "lineHeight": 22 + } + }, + "root": { + "type": "container", + "id": "box-empty", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 300, + "unit": "dp" + }, + "padding": { + "all": 0 + } + }, + "style": { + "background": { + "type": "radial_gradient", + "colors": [ + "#D1C4E9", + "#9575CD" + ], + "centerX": 0.5, + "centerY": 0.5, + "radius": 0.8 + }, + "borderRadius": 20, + "borderWidth": 3, + "borderColor": "#4A148C" + }, + "children": [] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-017-box-single-child.json b/flutter-sample/assets/test-configs/test-017-box-single-child.json new file mode 100644 index 0000000..57ff034 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-017-box-single-child.json @@ -0,0 +1,66 @@ +{ + "theme": { + "id": "test-theme", + "defaultStyle": { + "textColor": "#FFFFFF", + "fontSize": 16, + "lineHeight": 22 + } + }, + "root": { + "type": "container", + "id": "box-single", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 400, + "unit": "dp" + }, + "padding": { + "all": 0 + } + }, + "style": { + "backgroundColor": "#212121" + }, + "children": [ + { + "type": "element", + "id": "centered-text", + "elementType": "text", + "bindings": { + "text": "Perfectly Centered" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "offset": { + "x": 50, + "y": 50, + "unit": "percent" + } + }, + "style": { + "fontSize": 32, + "fontWeight": "bold", + "textColor": "#FFEB3B", + "lineHeight": 45 + } + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-018-box-3-children.json b/flutter-sample/assets/test-configs/test-018-box-3-children.json new file mode 100644 index 0000000..e2cb99b --- /dev/null +++ b/flutter-sample/assets/test-configs/test-018-box-3-children.json @@ -0,0 +1,143 @@ +{ + "theme": { + "id": "test-theme", + "defaultStyle": { + "textColor": "#1B5E20", + "fontSize": 14, + "lineHeight": 20 + } + }, + "root": { + "type": "container", + "id": "box-3-children", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 500, + "unit": "dp" + }, + "padding": { + "all": 0 + } + }, + "style": { + "background": { + "type": "linear_gradient", + "angle": 135, + "colors": [ + "#A5D6A7", + "#66BB6A", + "#2E7D32" + ] + } + }, + "children": [ + { + "type": "element", + "id": "top-left", + "elementType": "text", + "bindings": { + "text": "Top Left" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 12 + }, + "offset": { + "x": 16, + "y": 16, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "textColor": "#1B5E20", + "fontSize": 18, + "fontWeight": "bold", + "borderRadius": 8, + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "center", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-25.jpg" + }, + "layout": { + "width": { + "value": 200, + "unit": "dp" + }, + "height": { + "value": 200, + "unit": "dp" + }, + "offset": { + "x": 50, + "y": 50, + "unit": "percent" + } + }, + "style": { + "borderRadius": 100, + "borderWidth": 4, + "borderColor": "#FFFFFF" + } + }, + { + "type": "element", + "id": "bottom-right", + "elementType": "text", + "bindings": { + "text": "Bottom Right" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 12 + }, + "offset": { + "x": 16, + "y": 16, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "textColor": "#1B5E20", + "fontSize": 18, + "fontWeight": "bold", + "borderRadius": 8, + "lineHeight": 25 + } + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-019-box-5-children.json b/flutter-sample/assets/test-configs/test-019-box-5-children.json new file mode 100644 index 0000000..a49bc91 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-019-box-5-children.json @@ -0,0 +1,199 @@ +{ + "theme": { + "id": "test-theme", + "defaultStyle": { + "textColor": "#01579B", + "fontSize": 12, + "lineHeight": 17 + } + }, + "root": { + "type": "container", + "id": "box-5-children", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 600, + "unit": "dp" + }, + "padding": { + "all": 0 + } + }, + "style": { + "backgroundColor": "#E1F5FE" + }, + "children": [ + { + "type": "element", + "id": "background-image", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-26.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "offset": { + "x": 0, + "y": 0, + "unit": "dp" + } + } + }, + { + "type": "element", + "id": "top-badge", + "elementType": "text", + "bindings": { + "text": "NEW" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "top": 6, + "bottom": 6, + "left": 12, + "right": 12 + }, + "offset": { + "x": 16, + "y": 16, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF5722", + "textColor": "#FFFFFF", + "fontSize": 14, + "fontWeight": "bold", + "borderRadius": 4, + "lineHeight": 20 + } + }, + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "Overlay Title" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 16 + }, + "offset": { + "x": 50, + "y": 30, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#000000AA", + "textColor": "#FFFFFF", + "fontSize": 28, + "fontWeight": "bold", + "borderRadius": 8, + "lineHeight": 39 + } + }, + { + "type": "element", + "id": "subtitle", + "elementType": "text", + "bindings": { + "text": "Additional Information" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 12 + }, + "offset": { + "x": 50, + "y": 50, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "textColor": "#01579B", + "fontSize": 16, + "borderRadius": 6, + "lineHeight": 22 + } + }, + { + "type": "element", + "id": "bottom-button", + "elementType": "button", + "bindings": { + "text": "Learn More" + }, + "layout": { + "width": { + "value": 200, + "unit": "dp" + }, + "height": { + "value": 50, + "unit": "dp" + }, + "offset": { + "x": 50, + "y": 32, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#0288D1", + "textColor": "#FFFFFF", + "fontSize": 18, + "fontWeight": "bold", + "borderRadius": 25, + "lineHeight": 25 + } + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-020-stack-empty.json b/flutter-sample/assets/test-configs/test-020-stack-empty.json new file mode 100644 index 0000000..1e8e47e --- /dev/null +++ b/flutter-sample/assets/test-configs/test-020-stack-empty.json @@ -0,0 +1,43 @@ +{ + "theme": { + "id": "test-theme", + "defaultStyle": { + "textColor": "#880E4F", + "fontSize": 16, + "lineHeight": 22 + } + }, + "root": { + "type": "container", + "id": "stack-empty", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 250, + "unit": "dp" + }, + "padding": { + "all": 0 + } + }, + "style": { + "background": { + "type": "linear_gradient", + "angle": 45, + "colors": [ + "#F8BBD0", + "#F06292", + "#E91E63" + ] + }, + "borderRadius": 16 + }, + "children": [] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-021-stack-single-child.json b/flutter-sample/assets/test-configs/test-021-stack-single-child.json new file mode 100644 index 0000000..21435e7 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-021-stack-single-child.json @@ -0,0 +1,61 @@ +{ + "theme": { + "id": "test-theme", + "defaultStyle": { + "textColor": "#F57F17", + "fontSize": 16, + "lineHeight": 22 + } + }, + "root": { + "type": "container", + "id": "stack-single", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 350, + "unit": "dp" + }, + "padding": { + "all": 0 + } + }, + "style": { + "backgroundColor": "#FFF9C4" + }, + "children": [ + { + "type": "element", + "id": "base-layer", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-27.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "offset": { + "x": 0, + "y": 0, + "unit": "dp" + } + }, + "style": { + "opacity": 0.8 + } + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-022-stack-3-children.json b/flutter-sample/assets/test-configs/test-022-stack-3-children.json new file mode 100644 index 0000000..080143c --- /dev/null +++ b/flutter-sample/assets/test-configs/test-022-stack-3-children.json @@ -0,0 +1,182 @@ +{ + "theme": { + "id": "test-theme", + "defaultStyle": { + "textColor": "#FFFFFF", + "fontSize": 14, + "lineHeight": 20 + } + }, + "root": { + "type": "container", + "id": "stack-3-children", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 400, + "unit": "dp" + }, + "padding": { + "all": 0 + } + }, + "style": { + "backgroundColor": "#263238" + }, + "children": [ + { + "type": "element", + "id": "layer-1", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-28.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "offset": { + "x": 0, + "y": 0, + "unit": "dp" + } + }, + "style": { + "opacity": 0.5 + } + }, + { + "type": "container", + "id": "layer-2", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 24 + }, + "offset": { + "x": 0, + "y": 0, + "unit": "dp" + }, + "arrangement": { + "strategy": "center" + } + }, + "style": { + "backgroundColor": "#00000066" + }, + "children": [ + { + "type": "element", + "id": "overlay-title", + "elementType": "text", + "bindings": { + "text": "Layered Design" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 32, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "textAlign": "center", + "lineHeight": 45 + } + }, + { + "type": "element", + "id": "overlay-subtitle", + "elementType": "text", + "bindings": { + "text": "Multiple layers stacked together" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 18, + "textColor": "#E0E0E0", + "textAlign": "center", + "lineHeight": 25 + } + } + ] + }, + { + "type": "element", + "id": "layer-3", + "elementType": "text", + "bindings": { + "text": "Top Layer Badge" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "top": 8, + "bottom": 8, + "left": 16, + "right": 16 + }, + "offset": { + "x": 16, + "y": 16, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF6F00", + "textColor": "#FFFFFF", + "fontSize": 14, + "fontWeight": "bold", + "borderRadius": 20, + "lineHeight": 20 + } + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-023-stack-5-children.json b/flutter-sample/assets/test-configs/test-023-stack-5-children.json new file mode 100644 index 0000000..fd53449 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-023-stack-5-children.json @@ -0,0 +1,242 @@ +{ + "theme": { + "id": "test-theme", + "defaultStyle": { + "textColor": "#FFFFFF", + "fontSize": 14, + "lineHeight": 20 + } + }, + "root": { + "type": "container", + "id": "stack-5-children", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 500, + "unit": "dp" + }, + "padding": { + "all": 0 + } + }, + "style": { + "backgroundColor": "#1A237E" + }, + "children": [ + { + "type": "element", + "id": "layer-1-background", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-29.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "offset": { + "x": 0, + "y": 0, + "unit": "dp" + } + }, + "style": { + "opacity": 0.3 + } + }, + { + "type": "container", + "id": "layer-2-gradient", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "offset": { + "x": 0, + "y": 0, + "unit": "dp" + } + }, + "style": { + "background": { + "type": "linear_gradient", + "angle": 180, + "colors": [ + "#00000000", + "#000000AA" + ] + } + }, + "children": [] + }, + { + "type": "element", + "id": "layer-3-top-badge", + "elementType": "text", + "bindings": { + "text": "FEATURED" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "top": 6, + "bottom": 6, + "left": 12, + "right": 12 + }, + "offset": { + "x": 16, + "y": 16, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#D32F2F", + "textColor": "#FFFFFF", + "fontSize": 12, + "fontWeight": "bold", + "borderRadius": 4, + "lineHeight": 17 + } + }, + { + "type": "container", + "id": "layer-4-content", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 24 + }, + "offset": { + "x": 0, + "y": 24, + "unit": "dp" + }, + "arrangement": { + "strategy": "spaced", + "spacing": 12, + "spacingUnit": "dp" + } + }, + "children": [ + { + "type": "element", + "id": "content-title", + "elementType": "text", + "bindings": { + "text": "Complex Stack Layout" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 28, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "lineHeight": 39 + } + }, + { + "type": "element", + "id": "content-description", + "elementType": "text", + "bindings": { + "text": "Demonstrating multiple layers with complex positioning" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 16, + "textColor": "#E0E0E0", + "lineHeight": 22 + } + } + ] + }, + { + "type": "element", + "id": "layer-5-action-button", + "elementType": "button", + "bindings": { + "text": "Explore Now" + }, + "layout": { + "width": { + "value": 180, + "unit": "dp" + }, + "height": { + "value": 50, + "unit": "dp" + }, + "offset": { + "x": 50, + "y": 40, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#1976D2", + "textColor": "#FFFFFF", + "fontSize": 16, + "fontWeight": "bold", + "borderRadius": 25, + "lineHeight": 22 + } + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-024-gallery-empty.json b/flutter-sample/assets/test-configs/test-024-gallery-empty.json new file mode 100644 index 0000000..93f2d14 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-024-gallery-empty.json @@ -0,0 +1,43 @@ +{ + "theme": { + "id": "test-theme", + "defaultStyle": { + "textColor": "#4A148C", + "fontSize": 16, + "lineHeight": 22 + } + }, + "root": { + "type": "container", + "id": "gallery-empty", + "containerType": "gallery", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 300, + "unit": "dp" + }, + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#F3E5F5", + "borderRadius": 12 + }, + "galleryConfig": { + "mode": "snapping", + "orientation": "horizontal", + "peek": { + "before": 0, + "after": 0 + } + }, + "children": [] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-025-gallery-single-child.json b/flutter-sample/assets/test-configs/test-025-gallery-single-child.json new file mode 100644 index 0000000..082ff78 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-025-gallery-single-child.json @@ -0,0 +1,64 @@ +{ + "theme": { + "id": "test-theme", + "defaultStyle": { + "textColor": "#BF360C", + "fontSize": 16, + "lineHeight": 22 + } + }, + "root": { + "type": "container", + "id": "gallery-single", + "containerType": "gallery", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 350, + "unit": "dp" + }, + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#FBE9E7" + }, + "galleryConfig": { + "mode": "snapping", + "orientation": "horizontal", + "peek": { + "before": 20, + "after": 20 + } + }, + "children": [ + { + "type": "element", + "id": "single-item", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-30.jpg" + }, + "layout": { + "width": { + "value": 90, + "unit": "percent" + }, + "height": { + "value": 300, + "unit": "dp" + } + }, + "style": { + "borderRadius": 16 + } + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-026-gallery-3-children-snapping.json b/flutter-sample/assets/test-configs/test-026-gallery-3-children-snapping.json new file mode 100644 index 0000000..e763d7a --- /dev/null +++ b/flutter-sample/assets/test-configs/test-026-gallery-3-children-snapping.json @@ -0,0 +1,278 @@ +{ + "theme": { + "id": "test-theme", + "defaultStyle": { + "textColor": "#1A237E", + "fontSize": 14, + "lineHeight": 20 + } + }, + "root": { + "type": "container", + "id": "gallery-3-snapping", + "containerType": "gallery", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 400, + "unit": "dp" + }, + "padding": { + "all": 0 + } + }, + "style": { + "backgroundColor": "#E8EAF6" + }, + "galleryConfig": { + "mode": "snapping", + "orientation": "horizontal", + "peek": { + "before": 16, + "after": 16 + }, + "spacing": 12 + }, + "children": [ + { + "type": "container", + "id": "card-1", + "containerType": "vertical", + "layout": { + "width": { + "value": 85, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 12, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 16, + "shadowRadius": 8, + "shadowOffsetY": 4, + "shadowColor": "#00000033" + }, + "children": [ + { + "type": "element", + "id": "image-1", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-31.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 250, + "unit": "dp" + } + }, + "style": { + "borderRadius": 12 + } + }, + { + "type": "element", + "id": "title-1", + "elementType": "text", + "bindings": { + "text": "Mountain Adventure" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 20, + "fontWeight": "bold", + "textColor": "#1A237E", + "lineHeight": 28 + } + } + ] + }, + { + "type": "container", + "id": "card-2", + "containerType": "vertical", + "layout": { + "width": { + "value": 85, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 12, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 16, + "shadowRadius": 8, + "shadowOffsetY": 4, + "shadowColor": "#00000033" + }, + "children": [ + { + "type": "element", + "id": "image-2", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-32.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 250, + "unit": "dp" + } + }, + "style": { + "borderRadius": 12 + } + }, + { + "type": "element", + "id": "title-2", + "elementType": "text", + "bindings": { + "text": "Ocean Breeze" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 20, + "fontWeight": "bold", + "textColor": "#1A237E", + "lineHeight": 28 + } + } + ] + }, + { + "type": "container", + "id": "card-3", + "containerType": "vertical", + "layout": { + "width": { + "value": 85, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 12, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 16, + "shadowRadius": 8, + "shadowOffsetY": 4, + "shadowColor": "#00000033" + }, + "children": [ + { + "type": "element", + "id": "image-3", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-33.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 250, + "unit": "dp" + } + }, + "style": { + "borderRadius": 12 + } + }, + { + "type": "element", + "id": "title-3", + "elementType": "text", + "bindings": { + "text": "City Lights" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 20, + "fontWeight": "bold", + "textColor": "#1A237E", + "lineHeight": 28 + } + } + ] + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-027-gallery-5-children-snapping.json b/flutter-sample/assets/test-configs/test-027-gallery-5-children-snapping.json new file mode 100644 index 0000000..8f6c7d9 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-027-gallery-5-children-snapping.json @@ -0,0 +1,159 @@ +{ + "theme": { + "id": "test-theme", + "defaultStyle": { + "textColor": "#004D40", + "fontSize": 14, + "lineHeight": 20 + } + }, + "root": { + "type": "container", + "id": "gallery-5-snapping", + "containerType": "gallery", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 280, + "unit": "dp" + }, + "padding": { + "all": 0 + } + }, + "style": { + "backgroundColor": "#E0F2F1" + }, + "galleryConfig": { + "mode": "snapping", + "orientation": "horizontal", + "peek": { + "before": 40, + "after": 40 + }, + "spacing": 16 + }, + "children": [ + { + "type": "element", + "id": "product-1", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-34.jpg" + }, + "layout": { + "width": { + "value": 220, + "unit": "dp" + }, + "height": { + "value": 220, + "unit": "dp" + } + }, + "style": { + "borderRadius": 16, + "borderWidth": 2, + "borderColor": "#00897B" + } + }, + { + "type": "element", + "id": "product-2", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-35.jpg" + }, + "layout": { + "width": { + "value": 220, + "unit": "dp" + }, + "height": { + "value": 220, + "unit": "dp" + } + }, + "style": { + "borderRadius": 16, + "borderWidth": 2, + "borderColor": "#00796B" + } + }, + { + "type": "element", + "id": "product-3", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-36.jpg" + }, + "layout": { + "width": { + "value": 220, + "unit": "dp" + }, + "height": { + "value": 220, + "unit": "dp" + } + }, + "style": { + "borderRadius": 16, + "borderWidth": 2, + "borderColor": "#00695C" + } + }, + { + "type": "element", + "id": "product-4", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-37.jpg" + }, + "layout": { + "width": { + "value": 220, + "unit": "dp" + }, + "height": { + "value": 220, + "unit": "dp" + } + }, + "style": { + "borderRadius": 16, + "borderWidth": 2, + "borderColor": "#00695C" + } + }, + { + "type": "element", + "id": "product-5", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-38.jpg" + }, + "layout": { + "width": { + "value": 220, + "unit": "dp" + }, + "height": { + "value": 220, + "unit": "dp" + } + }, + "style": { + "borderRadius": 16, + "borderWidth": 2, + "borderColor": "#004D40" + } + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-028-gallery-10-children-snapping.json b/flutter-sample/assets/test-configs/test-028-gallery-10-children-snapping.json new file mode 100644 index 0000000..32b423f --- /dev/null +++ b/flutter-sample/assets/test-configs/test-028-gallery-10-children-snapping.json @@ -0,0 +1,254 @@ +{ + "theme": { + "id": "test-theme", + "defaultStyle": { + "textColor": "#880E4F", + "fontSize": 12, + "lineHeight": 17 + } + }, + "root": { + "type": "container", + "id": "gallery-10-snapping", + "containerType": "gallery", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 200, + "unit": "dp" + }, + "padding": { + "all": 8 + } + }, + "style": { + "backgroundColor": "#FCE4EC" + }, + "galleryConfig": { + "mode": "snapping", + "orientation": "horizontal", + "peek": { + "before": 20, + "after": 20 + }, + "spacing": 8 + }, + "children": [ + { + "type": "element", + "id": "item-1", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-39.jpg" + }, + "layout": { + "width": { + "value": 160, + "unit": "dp" + }, + "height": { + "value": 160, + "unit": "dp" + } + }, + "style": { + "borderRadius": 12 + } + }, + { + "type": "element", + "id": "item-2", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-40.jpg" + }, + "layout": { + "width": { + "value": 160, + "unit": "dp" + }, + "height": { + "value": 160, + "unit": "dp" + } + }, + "style": { + "borderRadius": 12 + } + }, + { + "type": "element", + "id": "item-3", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-41.jpg" + }, + "layout": { + "width": { + "value": 160, + "unit": "dp" + }, + "height": { + "value": 160, + "unit": "dp" + } + }, + "style": { + "borderRadius": 12 + } + }, + { + "type": "element", + "id": "item-4", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-42.jpg" + }, + "layout": { + "width": { + "value": 160, + "unit": "dp" + }, + "height": { + "value": 160, + "unit": "dp" + } + }, + "style": { + "borderRadius": 12 + } + }, + { + "type": "element", + "id": "item-5", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-43.jpg" + }, + "layout": { + "width": { + "value": 160, + "unit": "dp" + }, + "height": { + "value": 160, + "unit": "dp" + } + }, + "style": { + "borderRadius": 12 + } + }, + { + "type": "element", + "id": "item-6", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-44.jpg" + }, + "layout": { + "width": { + "value": 160, + "unit": "dp" + }, + "height": { + "value": 160, + "unit": "dp" + } + }, + "style": { + "borderRadius": 12 + } + }, + { + "type": "element", + "id": "item-7", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-45.jpg" + }, + "layout": { + "width": { + "value": 160, + "unit": "dp" + }, + "height": { + "value": 160, + "unit": "dp" + } + }, + "style": { + "borderRadius": 12 + } + }, + { + "type": "element", + "id": "item-8", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-46.jpg" + }, + "layout": { + "width": { + "value": 160, + "unit": "dp" + }, + "height": { + "value": 160, + "unit": "dp" + } + }, + "style": { + "borderRadius": 12 + } + }, + { + "type": "element", + "id": "item-9", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-47.jpg" + }, + "layout": { + "width": { + "value": 160, + "unit": "dp" + }, + "height": { + "value": 160, + "unit": "dp" + } + }, + "style": { + "borderRadius": 12 + } + }, + { + "type": "element", + "id": "item-10", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-48.jpg" + }, + "layout": { + "width": { + "value": 160, + "unit": "dp" + }, + "height": { + "value": 160, + "unit": "dp" + } + }, + "style": { + "borderRadius": 12 + } + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-029-gallery-3-children-free-flow.json b/flutter-sample/assets/test-configs/test-029-gallery-3-children-free-flow.json new file mode 100644 index 0000000..2c7fcf5 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-029-gallery-3-children-free-flow.json @@ -0,0 +1,271 @@ +{ + "theme": { + "id": "test-theme", + "defaultStyle": { + "textColor": "#E65100", + "fontSize": 14, + "lineHeight": 20 + } + }, + "root": { + "type": "container", + "id": "gallery-3-free-flow", + "containerType": "gallery", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 350, + "unit": "dp" + }, + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#FFF3E0" + }, + "galleryConfig": { + "mode": "free_flow", + "orientation": "horizontal", + "spacing": 12 + }, + "children": [ + { + "type": "container", + "id": "item-1", + "containerType": "vertical", + "layout": { + "width": { + "value": 250, + "unit": "dp" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 8, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 12, + "shadowRadius": 6, + "shadowOffsetY": 3, + "shadowColor": "#00000022" + }, + "children": [ + { + "type": "element", + "id": "img-1", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-49.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 180, + "unit": "dp" + } + }, + "style": { + "borderRadius": 8 + } + }, + { + "type": "element", + "id": "text-1", + "elementType": "text", + "bindings": { + "text": "Free-flowing gallery allows natural scrolling" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#E65100", + "lineHeight": 20 + } + } + ] + }, + { + "type": "container", + "id": "item-2", + "containerType": "vertical", + "layout": { + "width": { + "value": 250, + "unit": "dp" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 8, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 12, + "shadowRadius": 6, + "shadowOffsetY": 3, + "shadowColor": "#00000022" + }, + "children": [ + { + "type": "element", + "id": "img-2", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-50.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 180, + "unit": "dp" + } + }, + "style": { + "borderRadius": 8 + } + }, + { + "type": "element", + "id": "text-2", + "elementType": "text", + "bindings": { + "text": "Smooth scrolling without snapping behavior" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#E65100", + "lineHeight": 20 + } + } + ] + }, + { + "type": "container", + "id": "item-3", + "containerType": "vertical", + "layout": { + "width": { + "value": 250, + "unit": "dp" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 8, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 12, + "shadowRadius": 6, + "shadowOffsetY": 3, + "shadowColor": "#00000022" + }, + "children": [ + { + "type": "element", + "id": "img-3", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-51.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 180, + "unit": "dp" + } + }, + "style": { + "borderRadius": 8 + } + }, + { + "type": "element", + "id": "text-3", + "elementType": "text", + "bindings": { + "text": "User controls the scroll position precisely" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#E65100", + "lineHeight": 20 + } + } + ] + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-030-gallery-3-children-free-flow-grid.json b/flutter-sample/assets/test-configs/test-030-gallery-3-children-free-flow-grid.json new file mode 100644 index 0000000..3efbf46 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-030-gallery-3-children-free-flow-grid.json @@ -0,0 +1,278 @@ +{ + "theme": { + "id": "test-theme", + "defaultStyle": { + "textColor": "#33691E", + "fontSize": 12, + "lineHeight": 17 + } + }, + "root": { + "type": "container", + "id": "gallery-3-free-flow-grid", + "containerType": "gallery", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 500, + "unit": "dp" + }, + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#F1F8E9" + }, + "galleryConfig": { + "mode": "free_flow_grid", + "orientation": "vertical", + "spacing": 12, + "columns": 2 + }, + "children": [ + { + "type": "container", + "id": "grid-item-1", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 12 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 8, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 12, + "shadowRadius": 4, + "shadowOffsetY": 2, + "shadowColor": "#00000018" + }, + "children": [ + { + "type": "element", + "id": "grid-img-1", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-52.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 150, + "unit": "dp" + } + }, + "style": { + "borderRadius": 8 + } + }, + { + "type": "element", + "id": "grid-text-1", + "elementType": "text", + "bindings": { + "text": "Grid Item 1" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#33691E", + "lineHeight": 20 + } + } + ] + }, + { + "type": "container", + "id": "grid-item-2", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 12 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 8, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 12, + "shadowRadius": 4, + "shadowOffsetY": 2, + "shadowColor": "#00000018" + }, + "children": [ + { + "type": "element", + "id": "grid-img-2", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-53.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 180, + "unit": "dp" + } + }, + "style": { + "borderRadius": 8 + } + }, + { + "type": "element", + "id": "grid-text-2", + "elementType": "text", + "bindings": { + "text": "Grid Item 2" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#33691E", + "lineHeight": 20 + } + } + ] + }, + { + "type": "container", + "id": "grid-item-3", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 12 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 8, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 12, + "shadowRadius": 4, + "shadowOffsetY": 2, + "shadowColor": "#00000018" + }, + "children": [ + { + "type": "element", + "id": "grid-img-3", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-54.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 160, + "unit": "dp" + } + }, + "style": { + "borderRadius": 8 + } + }, + { + "type": "element", + "id": "grid-text-3", + "elementType": "text", + "bindings": { + "text": "Grid Item 3" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#33691E", + "lineHeight": 20 + } + } + ] + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-031-vertical-spaced.json b/flutter-sample/assets/test-configs/test-031-vertical-spaced.json new file mode 100644 index 0000000..90a97a2 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-031-vertical-spaced.json @@ -0,0 +1,180 @@ +{ + "theme": { + "id": "test-theme", + "defaultStyle": { + "textColor": "#1A237E", + "fontSize": 16, + "lineHeight": 22 + } + }, + "root": { + "type": "container", + "id": "vertical-spaced", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 600, + "unit": "dp" + }, + "padding": { + "all": 20 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 12, + "spacingUnit": "dp" + } + }, + "style": { + "background": { + "type": "linear_gradient", + "angle": 135, + "colors": [ + "#E8EAF6", + "#C5CAE9" + ] + } + }, + "children": [ + { + "type": "element", + "id": "header", + "elementType": "text", + "bindings": { + "text": "SPACED Arrangement Strategy" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 24, + "fontWeight": "bold", + "textColor": "#1A237E", + "lineHeight": 34 + } + }, + { + "type": "element", + "id": "description", + "elementType": "text", + "bindings": { + "text": "Fixed spacing of 12dp between each child element" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 16, + "textColor": "#3F51B5", + "lineHeight": 22 + } + }, + { + "type": "element", + "id": "card-1", + "elementType": "text", + "bindings": { + "text": "Card 1" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 80, + "unit": "dp" + }, + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 8, + "fontSize": 18, + "textColor": "#1A237E", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "card-2", + "elementType": "text", + "bindings": { + "text": "Card 2" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 80, + "unit": "dp" + }, + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 8, + "fontSize": 18, + "textColor": "#1A237E", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "card-3", + "elementType": "text", + "bindings": { + "text": "Card 3" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 80, + "unit": "dp" + }, + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 8, + "fontSize": 18, + "textColor": "#1A237E", + "lineHeight": 25 + } + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-032-vertical-space-between.json b/flutter-sample/assets/test-configs/test-032-vertical-space-between.json new file mode 100644 index 0000000..88d1763 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-032-vertical-space-between.json @@ -0,0 +1,157 @@ +{ + "theme": { + "id": "test-theme", + "defaultStyle": { + "textColor": "#2E7D32", + "fontSize": 16, + "lineHeight": 22 + } + }, + "root": { + "type": "container", + "id": "vertical-space-between", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 600, + "unit": "dp" + }, + "padding": { + "all": 20 + }, + "arrangement": { + "strategy": "space_between" + } + }, + "style": { + "background": { + "type": "linear_gradient", + "angle": 180, + "colors": [ + "#C8E6C9", + "#A5D6A7" + ] + } + }, + "children": [ + { + "type": "element", + "id": "header", + "elementType": "text", + "bindings": { + "text": "SPACE_BETWEEN Arrangement" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 24, + "fontWeight": "bold", + "textColor": "#1B5E20", + "lineHeight": 34 + } + }, + { + "type": "element", + "id": "description", + "elementType": "text", + "bindings": { + "text": "Equal space between items, no space at edges" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 16, + "textColor": "#2E7D32", + "lineHeight": 22 + } + }, + { + "type": "element", + "id": "item-1", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-55.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 120, + "unit": "dp" + } + }, + "style": { + "borderRadius": 12 + } + }, + { + "type": "element", + "id": "item-2", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-56.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 120, + "unit": "dp" + } + }, + "style": { + "borderRadius": 12 + } + }, + { + "type": "element", + "id": "item-3", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-57.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 120, + "unit": "dp" + } + }, + "style": { + "borderRadius": 12 + } + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-033-vertical-space-evenly.json b/flutter-sample/assets/test-configs/test-033-vertical-space-evenly.json new file mode 100644 index 0000000..d7b5e5c --- /dev/null +++ b/flutter-sample/assets/test-configs/test-033-vertical-space-evenly.json @@ -0,0 +1,184 @@ +{ + "theme": { + "id": "test-theme", + "defaultStyle": { + "textColor": "#D84315", + "fontSize": 16, + "lineHeight": 22 + } + }, + "root": { + "type": "container", + "id": "vertical-space-evenly", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 600, + "unit": "dp" + }, + "padding": { + "all": 20 + }, + "arrangement": { + "strategy": "space_evenly" + } + }, + "style": { + "background": { + "type": "linear_gradient", + "angle": 90, + "colors": [ + "#FFCCBC", + "#FF8A65" + ] + } + }, + "children": [ + { + "type": "element", + "id": "header", + "elementType": "text", + "bindings": { + "text": "SPACE_EVENLY Arrangement" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 24, + "fontWeight": "bold", + "textColor": "#BF360C", + "lineHeight": 34 + } + }, + { + "type": "element", + "id": "description", + "elementType": "text", + "bindings": { + "text": "Equal space between items AND at edges" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 16, + "textColor": "#D84315", + "lineHeight": 22 + } + }, + { + "type": "element", + "id": "box-1", + "elementType": "text", + "bindings": { + "text": "Box 1" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "dp" + }, + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 16, + "fontSize": 20, + "fontWeight": "bold", + "textColor": "#BF360C", + "textAlign": "center", + "lineHeight": 28 + } + }, + { + "type": "element", + "id": "box-2", + "elementType": "text", + "bindings": { + "text": "Box 2" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "dp" + }, + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 16, + "fontSize": 20, + "fontWeight": "bold", + "textColor": "#BF360C", + "textAlign": "center", + "lineHeight": 28 + } + }, + { + "type": "element", + "id": "box-3", + "elementType": "text", + "bindings": { + "text": "Box 3" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "dp" + }, + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 16, + "fontSize": 20, + "fontWeight": "bold", + "textColor": "#BF360C", + "textAlign": "center", + "lineHeight": 28 + } + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-034-vertical-space-around.json b/flutter-sample/assets/test-configs/test-034-vertical-space-around.json new file mode 100644 index 0000000..1c9bc2c --- /dev/null +++ b/flutter-sample/assets/test-configs/test-034-vertical-space-around.json @@ -0,0 +1,165 @@ +{ + "theme": { + "id": "test-theme", + "defaultStyle": { + "textColor": "#6A1B9A", + "fontSize": 16, + "lineHeight": 22 + } + }, + "root": { + "type": "container", + "id": "vertical-space-around", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 600, + "unit": "dp" + }, + "padding": { + "all": 20 + }, + "arrangement": { + "strategy": "space_around" + } + }, + "style": { + "background": { + "type": "radial_gradient", + "colors": [ + "#E1BEE7", + "#BA68C8" + ], + "centerX": 0.5, + "centerY": 0.5, + "radius": 1.0 + } + }, + "children": [ + { + "type": "element", + "id": "header", + "elementType": "text", + "bindings": { + "text": "SPACE_AROUND Arrangement" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 24, + "fontWeight": "bold", + "textColor": "#4A148C", + "lineHeight": 34 + } + }, + { + "type": "element", + "id": "description", + "elementType": "text", + "bindings": { + "text": "Half space at edges, full space between items" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 16, + "textColor": "#6A1B9A", + "lineHeight": 22 + } + }, + { + "type": "element", + "id": "circle-1", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-58.jpg" + }, + "layout": { + "width": { + "value": 120, + "unit": "dp" + }, + "height": { + "value": 120, + "unit": "dp" + } + }, + "style": { + "borderRadius": 60, + "borderWidth": 4, + "borderColor": "#FFFFFF" + } + }, + { + "type": "element", + "id": "circle-2", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-59.jpg" + }, + "layout": { + "width": { + "value": 120, + "unit": "dp" + }, + "height": { + "value": 120, + "unit": "dp" + } + }, + "style": { + "borderRadius": 60, + "borderWidth": 4, + "borderColor": "#FFFFFF" + } + }, + { + "type": "element", + "id": "circle-3", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-60.jpg" + }, + "layout": { + "width": { + "value": 120, + "unit": "dp" + }, + "height": { + "value": 120, + "unit": "dp" + } + }, + "style": { + "borderRadius": 60, + "borderWidth": 4, + "borderColor": "#FFFFFF" + } + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-035-horizontal-start.json b/flutter-sample/assets/test-configs/test-035-horizontal-start.json new file mode 100644 index 0000000..5dfbc7a --- /dev/null +++ b/flutter-sample/assets/test-configs/test-035-horizontal-start.json @@ -0,0 +1,275 @@ +{ + "theme": { + "id": "test-theme", + "defaultStyle": { + "textColor": "#01579B", + "fontSize": 14, + "lineHeight": 20 + } + }, + "root": { + "type": "container", + "id": "horizontal-start", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 300, + "unit": "dp" + }, + "padding": { + "all": 20 + }, + "arrangement": { + "strategy": "start" + } + }, + "style": { + "backgroundColor": "#E1F5FE" + }, + "children": [ + { + "type": "container", + "id": "item-1", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "dp" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 12 + }, + "arrangement": { + "strategy": "center" + } + }, + "style": { + "backgroundColor": "#0288D1", + "borderRadius": 12 + }, + "children": [ + { + "type": "element", + "id": "icon-1", + "elementType": "text", + "bindings": { + "text": "START" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "label-1", + "elementType": "text", + "bindings": { + "text": "Items align to start (left)" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 12, + "textColor": "#E1F5FE", + "textAlign": "center", + "lineHeight": 17 + } + } + ] + }, + { + "type": "container", + "id": "item-2", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "dp" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 12 + }, + "arrangement": { + "strategy": "center" + } + }, + "style": { + "backgroundColor": "#0277BD", + "borderRadius": 12 + }, + "children": [ + { + "type": "element", + "id": "icon-2", + "elementType": "text", + "bindings": { + "text": "Item 2" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "label-2", + "elementType": "text", + "bindings": { + "text": "No space between" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 12, + "textColor": "#E1F5FE", + "textAlign": "center", + "lineHeight": 17 + } + } + ] + }, + { + "type": "container", + "id": "item-3", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "dp" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 12 + }, + "arrangement": { + "strategy": "center" + } + }, + "style": { + "backgroundColor": "#01579B", + "borderRadius": 12 + }, + "children": [ + { + "type": "element", + "id": "icon-3", + "elementType": "text", + "bindings": { + "text": "Item 3" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "label-3", + "elementType": "text", + "bindings": { + "text": "Grouped left" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 12, + "textColor": "#E1F5FE", + "textAlign": "center", + "lineHeight": 17 + } + } + ] + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-036-horizontal-center.json b/flutter-sample/assets/test-configs/test-036-horizontal-center.json new file mode 100644 index 0000000..5867845 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-036-horizontal-center.json @@ -0,0 +1,275 @@ +{ + "theme": { + "id": "test-theme", + "defaultStyle": { + "textColor": "#00695C", + "fontSize": 14, + "lineHeight": 20 + } + }, + "root": { + "type": "container", + "id": "horizontal-center", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 300, + "unit": "dp" + }, + "padding": { + "all": 20 + }, + "arrangement": { + "strategy": "center" + } + }, + "style": { + "backgroundColor": "#E0F2F1" + }, + "children": [ + { + "type": "container", + "id": "item-1", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "dp" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 12 + }, + "arrangement": { + "strategy": "center" + } + }, + "style": { + "backgroundColor": "#00897B", + "borderRadius": 12 + }, + "children": [ + { + "type": "element", + "id": "icon-1", + "elementType": "text", + "bindings": { + "text": "CENTER" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "label-1", + "elementType": "text", + "bindings": { + "text": "Items centered horizontally" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 12, + "textColor": "#E0F2F1", + "textAlign": "center", + "lineHeight": 17 + } + } + ] + }, + { + "type": "container", + "id": "item-2", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "dp" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 12 + }, + "arrangement": { + "strategy": "center" + } + }, + "style": { + "backgroundColor": "#00796B", + "borderRadius": 12 + }, + "children": [ + { + "type": "element", + "id": "icon-2", + "elementType": "text", + "bindings": { + "text": "Item 2" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "label-2", + "elementType": "text", + "bindings": { + "text": "Grouped center" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 12, + "textColor": "#E0F2F1", + "textAlign": "center", + "lineHeight": 17 + } + } + ] + }, + { + "type": "container", + "id": "item-3", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "dp" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 12 + }, + "arrangement": { + "strategy": "center" + } + }, + "style": { + "backgroundColor": "#00695C", + "borderRadius": 12 + }, + "children": [ + { + "type": "element", + "id": "icon-3", + "elementType": "text", + "bindings": { + "text": "Item 3" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "label-3", + "elementType": "text", + "bindings": { + "text": "No spacing" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 12, + "textColor": "#E0F2F1", + "textAlign": "center", + "lineHeight": 17 + } + } + ] + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-037-horizontal-end.json b/flutter-sample/assets/test-configs/test-037-horizontal-end.json new file mode 100644 index 0000000..da4e5c1 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-037-horizontal-end.json @@ -0,0 +1,275 @@ +{ + "theme": { + "id": "test-theme", + "defaultStyle": { + "textColor": "#C62828", + "fontSize": 14, + "lineHeight": 20 + } + }, + "root": { + "type": "container", + "id": "horizontal-end", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 300, + "unit": "dp" + }, + "padding": { + "all": 20 + }, + "arrangement": { + "strategy": "end" + } + }, + "style": { + "backgroundColor": "#FFEBEE" + }, + "children": [ + { + "type": "container", + "id": "item-1", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "dp" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 12 + }, + "arrangement": { + "strategy": "center" + } + }, + "style": { + "backgroundColor": "#E53935", + "borderRadius": 12 + }, + "children": [ + { + "type": "element", + "id": "icon-1", + "elementType": "text", + "bindings": { + "text": "END" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "label-1", + "elementType": "text", + "bindings": { + "text": "Items align to end (right)" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 12, + "textColor": "#FFEBEE", + "textAlign": "center", + "lineHeight": 17 + } + } + ] + }, + { + "type": "container", + "id": "item-2", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "dp" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 12 + }, + "arrangement": { + "strategy": "center" + } + }, + "style": { + "backgroundColor": "#D32F2F", + "borderRadius": 12 + }, + "children": [ + { + "type": "element", + "id": "icon-2", + "elementType": "text", + "bindings": { + "text": "Item 2" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "label-2", + "elementType": "text", + "bindings": { + "text": "Grouped right" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 12, + "textColor": "#FFEBEE", + "textAlign": "center", + "lineHeight": 17 + } + } + ] + }, + { + "type": "container", + "id": "item-3", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "dp" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 12 + }, + "arrangement": { + "strategy": "center" + } + }, + "style": { + "backgroundColor": "#C62828", + "borderRadius": 12 + }, + "children": [ + { + "type": "element", + "id": "icon-3", + "elementType": "text", + "bindings": { + "text": "Item 3" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "label-3", + "elementType": "text", + "bindings": { + "text": "No spacing" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 12, + "textColor": "#FFEBEE", + "textAlign": "center", + "lineHeight": 17 + } + } + ] + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-038-vertical-spacing-0.json b/flutter-sample/assets/test-configs/test-038-vertical-spacing-0.json new file mode 100644 index 0000000..3256ec8 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-038-vertical-spacing-0.json @@ -0,0 +1,197 @@ +{ + "theme": { + "id": "test-theme", + "defaultStyle": { + "textColor": "#37474F", + "fontSize": 16, + "lineHeight": 22 + } + }, + "root": { + "type": "container", + "id": "vertical-spacing-0", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 600, + "unit": "dp" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 0, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#ECEFF1" + }, + "children": [ + { + "type": "element", + "id": "header", + "elementType": "text", + "bindings": { + "text": "Zero Spacing (0dp)" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 24, + "fontWeight": "bold", + "textColor": "#263238", + "lineHeight": 34 + } + }, + { + "type": "element", + "id": "description", + "elementType": "text", + "bindings": { + "text": "Elements touch each other with no gap" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 16, + "textColor": "#546E7A", + "lineHeight": 22 + } + }, + { + "type": "element", + "id": "item-1", + "elementType": "text", + "bindings": { + "text": "Item 1" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 80, + "unit": "dp" + }, + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#CFD8DC", + "fontSize": 18, + "textColor": "#263238", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "item-2", + "elementType": "text", + "bindings": { + "text": "Item 2" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 80, + "unit": "dp" + }, + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#B0BEC5", + "fontSize": 18, + "textColor": "#263238", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "item-3", + "elementType": "text", + "bindings": { + "text": "Item 3" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 80, + "unit": "dp" + }, + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#90A4AE", + "fontSize": 18, + "textColor": "#FFFFFF", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "item-4", + "elementType": "text", + "bindings": { + "text": "Item 4" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 80, + "unit": "dp" + }, + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#78909C", + "fontSize": 18, + "textColor": "#FFFFFF", + "lineHeight": 25 + } + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-039-vertical-spacing-8.json b/flutter-sample/assets/test-configs/test-039-vertical-spacing-8.json new file mode 100644 index 0000000..73602b5 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-039-vertical-spacing-8.json @@ -0,0 +1,152 @@ +{ + "theme": { + "id": "test-theme", + "defaultStyle": { + "textColor": "#F57C00", + "fontSize": 16, + "lineHeight": 22 + } + }, + "root": { + "type": "container", + "id": "vertical-spacing-8", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 600, + "unit": "dp" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 8, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FFF3E0" + }, + "children": [ + { + "type": "element", + "id": "header", + "elementType": "text", + "bindings": { + "text": "Small Spacing (8dp)" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 24, + "fontWeight": "bold", + "textColor": "#E65100", + "lineHeight": 34 + } + }, + { + "type": "element", + "id": "description", + "elementType": "text", + "bindings": { + "text": "Compact layout with minimal gaps" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 16, + "textColor": "#F57C00", + "lineHeight": 22 + } + }, + { + "type": "element", + "id": "card-1", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-61.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "dp" + } + }, + "style": { + "borderRadius": 8 + } + }, + { + "type": "element", + "id": "card-2", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-62.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "dp" + } + }, + "style": { + "borderRadius": 8 + } + }, + { + "type": "element", + "id": "card-3", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-63.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "dp" + } + }, + "style": { + "borderRadius": 8 + } + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-040-vertical-spacing-16.json b/flutter-sample/assets/test-configs/test-040-vertical-spacing-16.json new file mode 100644 index 0000000..394ae04 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-040-vertical-spacing-16.json @@ -0,0 +1,182 @@ +{ + "theme": { + "id": "test-theme", + "defaultStyle": { + "textColor": "#388E3C", + "fontSize": 16, + "lineHeight": 22 + } + }, + "root": { + "type": "container", + "id": "vertical-spacing-16", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 650, + "unit": "dp" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 16, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#E8F5E9" + }, + "children": [ + { + "type": "element", + "id": "header", + "elementType": "text", + "bindings": { + "text": "Medium Spacing (16dp)" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 24, + "fontWeight": "bold", + "textColor": "#1B5E20", + "lineHeight": 34 + } + }, + { + "type": "element", + "id": "description", + "elementType": "text", + "bindings": { + "text": "Standard comfortable spacing - most common" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 16, + "textColor": "#388E3C", + "lineHeight": 22 + } + }, + { + "type": "element", + "id": "section-1", + "elementType": "text", + "bindings": { + "text": "Section 1 - Well-balanced visual breathing room" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "dp" + }, + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 12, + "fontSize": 16, + "textColor": "#2E7D32", + "shadowRadius": 4, + "shadowOffsetY": 2, + "shadowColor": "#00000022", + "lineHeight": 22 + } + }, + { + "type": "element", + "id": "section-2", + "elementType": "text", + "bindings": { + "text": "Section 2 - Clear separation between elements" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "dp" + }, + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 12, + "fontSize": 16, + "textColor": "#2E7D32", + "shadowRadius": 4, + "shadowOffsetY": 2, + "shadowColor": "#00000022", + "lineHeight": 22 + } + }, + { + "type": "element", + "id": "section-3", + "elementType": "text", + "bindings": { + "text": "Section 3 - Easy to scan and read" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "dp" + }, + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 12, + "fontSize": 16, + "textColor": "#2E7D32", + "shadowRadius": 4, + "shadowOffsetY": 2, + "shadowColor": "#00000022", + "lineHeight": 22 + } + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-041-vertical-spacing-32.json b/flutter-sample/assets/test-configs/test-041-vertical-spacing-32.json new file mode 100644 index 0000000..7a5f8a8 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-041-vertical-spacing-32.json @@ -0,0 +1,326 @@ +{ + "theme": { + "id": "test-theme", + "defaultStyle": { + "textColor": "#5E35B1", + "fontSize": 16, + "lineHeight": 22 + } + }, + "root": { + "type": "container", + "id": "vertical-spacing-32", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 750, + "unit": "dp" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 32, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#EDE7F6" + }, + "children": [ + { + "type": "element", + "id": "header", + "elementType": "text", + "bindings": { + "text": "Large Spacing (32dp)" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 24, + "fontWeight": "bold", + "textColor": "#4A148C", + "lineHeight": 34 + } + }, + { + "type": "element", + "id": "description", + "elementType": "text", + "bindings": { + "text": "Generous spacing for premium feel and clear separation" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 16, + "textColor": "#5E35B1", + "lineHeight": 22 + } + }, + { + "type": "container", + "id": "feature-1", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 120, + "unit": "dp" + }, + "padding": { + "all": 20 + }, + "arrangement": { + "strategy": "center" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 16, + "shadowRadius": 8, + "shadowOffsetY": 4, + "shadowColor": "#00000033" + }, + "children": [ + { + "type": "element", + "id": "feature-1-title", + "elementType": "text", + "bindings": { + "text": "Premium Feature" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 20, + "fontWeight": "bold", + "textColor": "#4A148C", + "lineHeight": 28 + } + }, + { + "type": "element", + "id": "feature-1-desc", + "elementType": "text", + "bindings": { + "text": "Extra space creates luxurious feel" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#7E57C2", + "lineHeight": 20 + } + } + ] + }, + { + "type": "container", + "id": "feature-2", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 120, + "unit": "dp" + }, + "padding": { + "all": 20 + }, + "arrangement": { + "strategy": "center" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 16, + "shadowRadius": 8, + "shadowOffsetY": 4, + "shadowColor": "#00000033" + }, + "children": [ + { + "type": "element", + "id": "feature-2-title", + "elementType": "text", + "bindings": { + "text": "Enhanced Readability" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 20, + "fontWeight": "bold", + "textColor": "#4A148C", + "lineHeight": 28 + } + }, + { + "type": "element", + "id": "feature-2-desc", + "elementType": "text", + "bindings": { + "text": "Each section clearly distinct" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#7E57C2", + "lineHeight": 20 + } + } + ] + }, + { + "type": "container", + "id": "feature-3", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 120, + "unit": "dp" + }, + "padding": { + "all": 20 + }, + "arrangement": { + "strategy": "center" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 16, + "shadowRadius": 8, + "shadowOffsetY": 4, + "shadowColor": "#00000033" + }, + "children": [ + { + "type": "element", + "id": "feature-3-title", + "elementType": "text", + "bindings": { + "text": "Visual Hierarchy" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 20, + "fontWeight": "bold", + "textColor": "#4A148C", + "lineHeight": 28 + } + }, + { + "type": "element", + "id": "feature-3-desc", + "elementType": "text", + "bindings": { + "text": "Excellent for content prioritization" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#7E57C2", + "lineHeight": 20 + } + } + ] + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-042-vertical-padding-uniform.json b/flutter-sample/assets/test-configs/test-042-vertical-padding-uniform.json new file mode 100644 index 0000000..6b9dd01 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-042-vertical-padding-uniform.json @@ -0,0 +1,175 @@ +{ + "theme": { + "id": "test-theme", + "defaultStyle": { + "textColor": "#0277BD", + "fontSize": 16, + "lineHeight": 22 + } + }, + "root": { + "type": "container", + "id": "vertical-padding-uniform", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 600, + "unit": "dp" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 12, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#B3E5FC", + "borderWidth": 4, + "borderColor": "#0277BD" + }, + "children": [ + { + "type": "element", + "id": "header", + "elementType": "text", + "bindings": { + "text": "Uniform Padding (16dp all sides)" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 22, + "fontWeight": "bold", + "textColor": "#01579B", + "lineHeight": 31 + } + }, + { + "type": "element", + "id": "description", + "elementType": "text", + "bindings": { + "text": "Container has equal padding on all four sides creating balanced frame" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 16, + "textColor": "#0277BD", + "lineHeight": 24 + } + }, + { + "type": "element", + "id": "content-1", + "elementType": "text", + "bindings": { + "text": "Content is inset equally from edges" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "dp" + }, + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 12, + "fontSize": 16, + "textColor": "#0277BD", + "lineHeight": 22 + } + }, + { + "type": "element", + "id": "content-2", + "elementType": "text", + "bindings": { + "text": "Creates symmetrical, balanced layout" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "dp" + }, + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 12, + "fontSize": 16, + "textColor": "#0277BD", + "lineHeight": 22 + } + }, + { + "type": "element", + "id": "content-3", + "elementType": "text", + "bindings": { + "text": "Most common padding pattern" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "dp" + }, + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 12, + "fontSize": 16, + "textColor": "#0277BD", + "lineHeight": 22 + } + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-043-vertical-padding-individual.json b/flutter-sample/assets/test-configs/test-043-vertical-padding-individual.json new file mode 100644 index 0000000..5426f74 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-043-vertical-padding-individual.json @@ -0,0 +1,157 @@ +{ + "theme": { + "id": "test-theme", + "defaultStyle": { + "textColor": "#C62828", + "fontSize": 16, + "lineHeight": 22 + } + }, + "root": { + "type": "container", + "id": "vertical-padding-individual", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 600, + "unit": "dp" + }, + "padding": { + "top": 8, + "right": 16, + "bottom": 8, + "left": 16 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 12, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FFCDD2", + "borderWidth": 4, + "borderColor": "#C62828" + }, + "children": [ + { + "type": "element", + "id": "header", + "elementType": "text", + "bindings": { + "text": "Individual Padding (Top/Bottom: 8dp, Left/Right: 16dp)" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 20, + "fontWeight": "bold", + "textColor": "#B71C1C", + "lineHeight": 28 + } + }, + { + "type": "element", + "id": "description", + "elementType": "text", + "bindings": { + "text": "Different padding per side - less vertical space, more horizontal breathing room" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 16, + "textColor": "#C62828", + "lineHeight": 24 + } + }, + { + "type": "element", + "id": "item-1", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-64.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "dp" + } + }, + "style": { + "borderRadius": 8 + } + }, + { + "type": "element", + "id": "item-2", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-65.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "dp" + } + }, + "style": { + "borderRadius": 8 + } + }, + { + "type": "element", + "id": "item-3", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-66.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "dp" + } + }, + "style": { + "borderRadius": 8 + } + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-044-horizontal-padding-asymmetric.json b/flutter-sample/assets/test-configs/test-044-horizontal-padding-asymmetric.json new file mode 100644 index 0000000..37152c4 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-044-horizontal-padding-asymmetric.json @@ -0,0 +1,225 @@ +{ + "theme": { + "id": "test-theme", + "defaultStyle": { + "textColor": "#558B2F", + "fontSize": 14, + "lineHeight": 20 + } + }, + "root": { + "type": "container", + "id": "horizontal-padding-asymmetric", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 400, + "unit": "dp" + }, + "padding": { + "top": 24, + "right": 8, + "bottom": 8, + "left": 24 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 12, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#DCEDC8", + "borderWidth": 4, + "borderColor": "#558B2F" + }, + "children": [ + { + "type": "container", + "id": "item-1", + "containerType": "vertical", + "layout": { + "width": { + "value": 150, + "unit": "dp" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "strategy": "space_between" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 12 + }, + "children": [ + { + "type": "element", + "id": "title-1", + "elementType": "text", + "bindings": { + "text": "Asymmetric" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "textColor": "#33691E", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "desc-1", + "elementType": "text", + "bindings": { + "text": "Top: 24dp\nRight: 8dp\nBottom: 8dp\nLeft: 24dp" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#558B2F", + "lineHeight": 20 + } + } + ] + }, + { + "type": "container", + "id": "item-2", + "containerType": "vertical", + "layout": { + "width": { + "value": 150, + "unit": "dp" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "strategy": "center" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 12 + }, + "children": [ + { + "type": "element", + "id": "label-2", + "elementType": "text", + "bindings": { + "text": "Creates unique visual effects" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#558B2F", + "textAlign": "center", + "lineHeight": 20 + } + } + ] + }, + { + "type": "container", + "id": "item-3", + "containerType": "vertical", + "layout": { + "width": { + "value": 150, + "unit": "dp" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "strategy": "center" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 12 + }, + "children": [ + { + "type": "element", + "id": "label-3", + "elementType": "text", + "bindings": { + "text": "Useful for special layouts" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#558B2F", + "textAlign": "center", + "lineHeight": 20 + } + } + ] + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-045-box-padding-large.json b/flutter-sample/assets/test-configs/test-045-box-padding-large.json new file mode 100644 index 0000000..043093d --- /dev/null +++ b/flutter-sample/assets/test-configs/test-045-box-padding-large.json @@ -0,0 +1,159 @@ +{ + "theme": { + "id": "test-theme", + "defaultStyle": { + "textColor": "#FFFFFF", + "fontSize": 16, + "lineHeight": 22 + } + }, + "root": { + "type": "container", + "id": "box-padding-large", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 500, + "unit": "dp" + }, + "padding": { + "all": 32 + } + }, + "style": { + "background": { + "type": "linear_gradient", + "angle": 135, + "colors": [ + "#1A237E", + "#3F51B5", + "#5C6BC0" + ] + }, + "borderWidth": 6, + "borderColor": "#FFFFFF" + }, + "children": [ + { + "type": "container", + "id": "content-box", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 24 + }, + "offset": { + "x": 50, + "y": 50, + "unit": "percent" + }, + "arrangement": { + "strategy": "spaced", + "spacing": 16, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 20, + "shadowRadius": 16, + "shadowOffsetY": 8, + "shadowColor": "#00000044" + }, + "children": [ + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "Large Padding (32dp)" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 28, + "fontWeight": "bold", + "textColor": "#1A237E", + "textAlign": "center", + "lineHeight": 39 + } + }, + { + "type": "element", + "id": "description", + "elementType": "text", + "bindings": { + "text": "Large padding creates dramatic framing effect" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 16, + "textColor": "#3F51B5", + "textAlign": "center", + "lineHeight": 24 + } + }, + { + "type": "element", + "id": "note", + "elementType": "text", + "bindings": { + "text": "Content feels protected and emphasized" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#5C6BC0", + "textAlign": "center", + "lineHeight": 20 + } + } + ] + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-046-vertical-wrap-content.json b/flutter-sample/assets/test-configs/test-046-vertical-wrap-content.json new file mode 100644 index 0000000..1dc9bd2 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-046-vertical-wrap-content.json @@ -0,0 +1,178 @@ +{ + "theme": { + "id": "test-theme", + "defaultStyle": { + "textColor": "#D84315", + "fontSize": 16, + "lineHeight": 22 + } + }, + "root": { + "type": "container", + "id": "vertical-wrap-content", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 20 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 16, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FBE9E7", + "borderRadius": 16 + }, + "children": [ + { + "type": "element", + "id": "header", + "elementType": "text", + "bindings": { + "text": "WRAP_CONTENT Height" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 26, + "fontWeight": "bold", + "textColor": "#BF360C", + "lineHeight": 36 + } + }, + { + "type": "element", + "id": "description", + "elementType": "text", + "bindings": { + "text": "Container height automatically adjusts to fit all children plus padding and spacing" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 16, + "textColor": "#D84315", + "lineHeight": 24 + } + }, + { + "type": "element", + "id": "card-1", + "elementType": "text", + "bindings": { + "text": "Card with variable text" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 12, + "fontSize": 18, + "textColor": "#BF360C", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "card-2", + "elementType": "text", + "bindings": { + "text": "This card has much longer text that spans multiple lines and demonstrates how wrap_content adapts to content size dynamically." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 12, + "fontSize": 16, + "textColor": "#D84315", + "lineHeight": 24 + } + }, + { + "type": "element", + "id": "card-3", + "elementType": "text", + "bindings": { + "text": "Short text" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 12, + "fontSize": 18, + "textColor": "#BF360C", + "lineHeight": 25 + } + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-047-horizontal-percent-width.json b/flutter-sample/assets/test-configs/test-047-horizontal-percent-width.json new file mode 100644 index 0000000..15b9516 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-047-horizontal-percent-width.json @@ -0,0 +1,275 @@ +{ + "theme": { + "id": "test-theme", + "defaultStyle": { + "textColor": "#00796B", + "fontSize": 14, + "lineHeight": 20 + } + }, + "root": { + "type": "container", + "id": "horizontal-percent-width", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 400, + "unit": "dp" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 8, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#E0F2F1" + }, + "children": [ + { + "type": "container", + "id": "col-1", + "containerType": "vertical", + "layout": { + "width": { + "value": 30, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 12 + }, + "arrangement": { + "strategy": "center" + } + }, + "style": { + "backgroundColor": "#00897B", + "borderRadius": 12 + }, + "children": [ + { + "type": "element", + "id": "label-1", + "elementType": "text", + "bindings": { + "text": "30%" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 24, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "lineHeight": 34 + } + }, + { + "type": "element", + "id": "desc-1", + "elementType": "text", + "bindings": { + "text": "Width" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#E0F2F1", + "lineHeight": 20 + } + } + ] + }, + { + "type": "container", + "id": "col-2", + "containerType": "vertical", + "layout": { + "width": { + "value": 50, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 12 + }, + "arrangement": { + "strategy": "center" + } + }, + "style": { + "backgroundColor": "#00796B", + "borderRadius": 12 + }, + "children": [ + { + "type": "element", + "id": "label-2", + "elementType": "text", + "bindings": { + "text": "50%" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 28, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "lineHeight": 39 + } + }, + { + "type": "element", + "id": "desc-2", + "elementType": "text", + "bindings": { + "text": "Percentage widths create responsive layouts" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#E0F2F1", + "textAlign": "center", + "lineHeight": 20 + } + } + ] + }, + { + "type": "container", + "id": "col-3", + "containerType": "vertical", + "layout": { + "width": { + "value": 20, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 12 + }, + "arrangement": { + "strategy": "center" + } + }, + "style": { + "backgroundColor": "#00695C", + "borderRadius": 12 + }, + "children": [ + { + "type": "element", + "id": "label-3", + "elementType": "text", + "bindings": { + "text": "20%" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 20, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "lineHeight": 28 + } + }, + { + "type": "element", + "id": "desc-3", + "elementType": "text", + "bindings": { + "text": "Width" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 12, + "textColor": "#E0F2F1", + "lineHeight": 17 + } + } + ] + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-048-vertical-mixed-units.json b/flutter-sample/assets/test-configs/test-048-vertical-mixed-units.json new file mode 100644 index 0000000..fa04cec --- /dev/null +++ b/flutter-sample/assets/test-configs/test-048-vertical-mixed-units.json @@ -0,0 +1,261 @@ +{ + "theme": { + "id": "test-theme", + "defaultStyle": { + "textColor": "#6A1B9A", + "fontSize": 16, + "lineHeight": 22 + } + }, + "root": { + "type": "container", + "id": "vertical-mixed-units", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 700, + "unit": "dp" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 12, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#F3E5F5" + }, + "children": [ + { + "type": "element", + "id": "header", + "elementType": "text", + "bindings": { + "text": "Mixed Dimension Units" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 26, + "fontWeight": "bold", + "textColor": "#4A148C", + "lineHeight": 36 + } + }, + { + "type": "element", + "id": "description", + "elementType": "text", + "bindings": { + "text": "Combining different unit types in one layout" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 16, + "textColor": "#7B1FA2", + "lineHeight": 22 + } + }, + { + "type": "element", + "id": "fixed-height", + "elementType": "text", + "bindings": { + "text": "Fixed Height: 120dp" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 120, + "unit": "dp" + }, + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#CE93D8", + "borderRadius": 12, + "fontSize": 18, + "textColor": "#4A148C", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "wrap-content-height", + "elementType": "text", + "bindings": { + "text": "WRAP_CONTENT Height - Adjusts to text length automatically" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#BA68C8", + "borderRadius": 12, + "fontSize": 16, + "textColor": "#FFFFFF", + "lineHeight": 24 + } + }, + { + "type": "container", + "id": "percent-width-container", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "dp" + }, + "padding": { + "all": 0 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 8, + "spacingUnit": "dp" + } + }, + "children": [ + { + "type": "element", + "id": "box-1", + "elementType": "text", + "bindings": { + "text": "60%" + }, + "layout": { + "width": { + "value": 60, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#AB47BC", + "borderRadius": 12, + "fontSize": 20, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "textAlign": "center", + "lineHeight": 28 + } + }, + { + "type": "element", + "id": "box-2", + "elementType": "text", + "bindings": { + "text": "40%" + }, + "layout": { + "width": { + "value": 40, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#9C27B0", + "borderRadius": 12, + "fontSize": 18, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "textAlign": "center", + "lineHeight": 25 + } + } + ] + }, + { + "type": "element", + "id": "footer", + "elementType": "text", + "bindings": { + "text": "Mix units for flexible, responsive designs" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 12 + } + }, + "style": { + "backgroundColor": "#8E24AA", + "borderRadius": 8, + "fontSize": 14, + "textColor": "#FFFFFF", + "textAlign": "center", + "lineHeight": 20 + } + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-049-nested-mixed-arrangements.json b/flutter-sample/assets/test-configs/test-049-nested-mixed-arrangements.json new file mode 100644 index 0000000..9e91d57 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-049-nested-mixed-arrangements.json @@ -0,0 +1,449 @@ +{ + "theme": { + "id": "test-theme", + "defaultStyle": { + "textColor": "#F57F17", + "fontSize": 14, + "lineHeight": 20 + } + }, + "root": { + "type": "container", + "id": "nested-mixed-arrangements", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 800, + "unit": "dp" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "strategy": "space_between" + } + }, + "style": { + "backgroundColor": "#FFF9C4" + }, + "children": [ + { + "type": "container", + "id": "header-section", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 8, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FFF59D", + "borderRadius": 12 + }, + "children": [ + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "Nested Mixed Arrangements" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 24, + "fontWeight": "bold", + "textColor": "#F57F17", + "lineHeight": 34 + } + }, + { + "type": "element", + "id": "subtitle", + "elementType": "text", + "bindings": { + "text": "Parent: SPACE_BETWEEN | Child: SPACED" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#F9A825", + "lineHeight": 20 + } + } + ] + }, + { + "type": "container", + "id": "content-section", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 300, + "unit": "dp" + }, + "padding": { + "all": 12 + }, + "arrangement": { + "strategy": "space_evenly" + } + }, + "style": { + "backgroundColor": "#FFF59D", + "borderRadius": 12 + }, + "children": [ + { + "type": "container", + "id": "col-1", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "dp" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 8 + }, + "arrangement": { + "strategy": "center" + } + }, + "style": { + "backgroundColor": "#FFEB3B", + "borderRadius": 8 + }, + "children": [ + { + "type": "element", + "id": "col-1-item", + "elementType": "text", + "bindings": { + "text": "CENTER" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 16, + "fontWeight": "bold", + "textColor": "#F57F17", + "lineHeight": 22 + } + } + ] + }, + { + "type": "container", + "id": "col-2", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "dp" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 8 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 8, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FDD835", + "borderRadius": 8 + }, + "children": [ + { + "type": "element", + "id": "col-2-item-1", + "elementType": "text", + "bindings": { + "text": "SPACED" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#F57F17", + "lineHeight": 20 + } + }, + { + "type": "element", + "id": "col-2-item-2", + "elementType": "text", + "bindings": { + "text": "8dp gap" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 12, + "textColor": "#F9A825", + "lineHeight": 17 + } + } + ] + }, + { + "type": "container", + "id": "col-3", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "dp" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 8 + }, + "arrangement": { + "strategy": "start" + } + }, + "style": { + "backgroundColor": "#FBC02D", + "borderRadius": 8 + }, + "children": [ + { + "type": "element", + "id": "col-3-item", + "elementType": "text", + "bindings": { + "text": "START" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 16, + "fontWeight": "bold", + "textColor": "#F57F17", + "lineHeight": 22 + } + } + ] + } + ] + }, + { + "type": "container", + "id": "footer-section", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "strategy": "space_around" + } + }, + "style": { + "backgroundColor": "#FFF59D", + "borderRadius": 12 + }, + "children": [ + { + "type": "element", + "id": "footer-item-1", + "elementType": "text", + "bindings": { + "text": "Mixed" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 12 + } + }, + "style": { + "backgroundColor": "#F9A825", + "borderRadius": 20, + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "lineHeight": 20 + } + }, + { + "type": "element", + "id": "footer-item-2", + "elementType": "text", + "bindings": { + "text": "Strategies" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 12 + } + }, + "style": { + "backgroundColor": "#F9A825", + "borderRadius": 20, + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "lineHeight": 20 + } + }, + { + "type": "element", + "id": "footer-item-3", + "elementType": "text", + "bindings": { + "text": "Work" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 12 + } + }, + "style": { + "backgroundColor": "#F9A825", + "borderRadius": 20, + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "lineHeight": 20 + } + } + ] + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-050-gallery-spacing-variations.json b/flutter-sample/assets/test-configs/test-050-gallery-spacing-variations.json new file mode 100644 index 0000000..a9b3e4e --- /dev/null +++ b/flutter-sample/assets/test-configs/test-050-gallery-spacing-variations.json @@ -0,0 +1,500 @@ +{ + "theme": { + "id": "test-theme", + "defaultStyle": { + "textColor": "#37474F", + "fontSize": 14, + "lineHeight": 20 + } + }, + "root": { + "type": "container", + "id": "gallery-spacing-variations", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 900, + "unit": "dp" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 20, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#ECEFF1" + }, + "children": [ + { + "type": "element", + "id": "header", + "elementType": "text", + "bindings": { + "text": "Gallery Spacing Variations" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 26, + "fontWeight": "bold", + "textColor": "#263238", + "lineHeight": 36 + } + }, + { + "type": "container", + "id": "gallery-tight", + "containerType": "gallery", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 180, + "unit": "dp" + }, + "padding": { + "all": 0 + } + }, + "style": { + "backgroundColor": "#CFD8DC" + }, + "galleryConfig": { + "mode": "snapping", + "orientation": "horizontal", + "peek": { + "before": 8, + "after": 8 + }, + "spacing": 4 + }, + "children": [ + { + "type": "element", + "id": "tight-1", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-67.jpg" + }, + "layout": { + "width": { + "value": 200, + "unit": "dp" + }, + "height": { + "value": 160, + "unit": "dp" + } + }, + "style": { + "borderRadius": 8 + } + }, + { + "type": "element", + "id": "tight-2", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-68.jpg" + }, + "layout": { + "width": { + "value": 200, + "unit": "dp" + }, + "height": { + "value": 160, + "unit": "dp" + } + }, + "style": { + "borderRadius": 8 + } + }, + { + "type": "element", + "id": "tight-3", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-69.jpg" + }, + "layout": { + "width": { + "value": 200, + "unit": "dp" + }, + "height": { + "value": 160, + "unit": "dp" + } + }, + "style": { + "borderRadius": 8 + } + }, + { + "type": "element", + "id": "tight-4", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-70.jpg" + }, + "layout": { + "width": { + "value": 200, + "unit": "dp" + }, + "height": { + "value": 160, + "unit": "dp" + } + }, + "style": { + "borderRadius": 8 + } + } + ] + }, + { + "type": "element", + "id": "tight-label", + "elementType": "text", + "bindings": { + "text": "Tight Spacing: 4dp spacing, 8dp peek" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#546E7A", + "textAlign": "center", + "lineHeight": 20 + } + }, + { + "type": "container", + "id": "gallery-comfortable", + "containerType": "gallery", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 180, + "unit": "dp" + }, + "padding": { + "all": 0 + } + }, + "style": { + "backgroundColor": "#B0BEC5" + }, + "galleryConfig": { + "mode": "snapping", + "orientation": "horizontal", + "peek": { + "before": 24, + "after": 24 + }, + "spacing": 16 + }, + "children": [ + { + "type": "element", + "id": "comfort-1", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-71.jpg" + }, + "layout": { + "width": { + "value": 240, + "unit": "dp" + }, + "height": { + "value": 160, + "unit": "dp" + } + }, + "style": { + "borderRadius": 12 + } + }, + { + "type": "element", + "id": "comfort-2", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-72.jpg" + }, + "layout": { + "width": { + "value": 240, + "unit": "dp" + }, + "height": { + "value": 160, + "unit": "dp" + } + }, + "style": { + "borderRadius": 12 + } + }, + { + "type": "element", + "id": "comfort-3", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-73.jpg" + }, + "layout": { + "width": { + "value": 240, + "unit": "dp" + }, + "height": { + "value": 160, + "unit": "dp" + } + }, + "style": { + "borderRadius": 12 + } + }, + { + "type": "element", + "id": "comfort-4", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-74.jpg" + }, + "layout": { + "width": { + "value": 240, + "unit": "dp" + }, + "height": { + "value": 160, + "unit": "dp" + } + }, + "style": { + "borderRadius": 12 + } + } + ] + }, + { + "type": "element", + "id": "comfort-label", + "elementType": "text", + "bindings": { + "text": "Comfortable: 16dp spacing, 24dp peek" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#546E7A", + "textAlign": "center", + "lineHeight": 20 + } + }, + { + "type": "container", + "id": "gallery-spacious", + "containerType": "gallery", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 180, + "unit": "dp" + }, + "padding": { + "all": 0 + } + }, + "style": { + "backgroundColor": "#90A4AE" + }, + "galleryConfig": { + "mode": "snapping", + "orientation": "horizontal", + "peek": { + "before": 40, + "after": 40 + }, + "spacing": 24 + }, + "children": [ + { + "type": "element", + "id": "spacious-1", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-75.jpg" + }, + "layout": { + "width": { + "value": 280, + "unit": "dp" + }, + "height": { + "value": 160, + "unit": "dp" + } + }, + "style": { + "borderRadius": 16, + "shadowRadius": 8, + "shadowOffsetY": 4, + "shadowColor": "#00000033" + } + }, + { + "type": "element", + "id": "spacious-2", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-76.jpg" + }, + "layout": { + "width": { + "value": 280, + "unit": "dp" + }, + "height": { + "value": 160, + "unit": "dp" + } + }, + "style": { + "borderRadius": 16, + "shadowRadius": 8, + "shadowOffsetY": 4, + "shadowColor": "#00000033" + } + }, + { + "type": "element", + "id": "spacious-3", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-77.jpg" + }, + "layout": { + "width": { + "value": 280, + "unit": "dp" + }, + "height": { + "value": 160, + "unit": "dp" + } + }, + "style": { + "borderRadius": 16, + "shadowRadius": 8, + "shadowOffsetY": 4, + "shadowColor": "#00000033" + } + }, + { + "type": "element", + "id": "spacious-4", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-78.jpg" + }, + "layout": { + "width": { + "value": 280, + "unit": "dp" + }, + "height": { + "value": 160, + "unit": "dp" + } + }, + "style": { + "borderRadius": 16, + "shadowRadius": 8, + "shadowOffsetY": 4, + "shadowColor": "#00000033" + } + } + ] + }, + { + "type": "element", + "id": "spacious-label", + "elementType": "text", + "bindings": { + "text": "Spacious: 24dp spacing, 40dp peek - Premium feel" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#546E7A", + "textAlign": "center", + "lineHeight": 20 + } + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-051-all-text-elements.json b/flutter-sample/assets/test-configs/test-051-all-text-elements.json new file mode 100644 index 0000000..f15f5f0 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-051-all-text-elements.json @@ -0,0 +1,170 @@ +{ + "theme": { + "id": "text-showcase", + "defaultStyle": { + "textColor": "#1A1A1A", + "fontSize": 14, + "fontWeight": "normal", + "lineHeight": 20 + } + }, + "root": { + "type": "container", + "id": "all-text-container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 24 + }, + "arrangement": { + "type": "spaced" + } + }, + "style": { + "background": { + "type": "linear_gradient", + "colors": [ + "#F8F9FA", + "#FFFFFF" + ], + "angle": 180 + } + }, + "children": [ + { + "type": "element", + "id": "heading", + "elementType": "text", + "bindings": { + "text": "Typography Showcase" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": -2, + "unit": "dp" + } + }, + "style": { + "fontSize": 32, + "fontWeight": "bold", + "textColor": "#000000", + "textAlign": "center", + "lineHeight": 45 + } + }, + { + "type": "element", + "id": "subheading", + "elementType": "text", + "bindings": { + "text": "Different text styles and weights" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": -2, + "unit": "dp" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "medium", + "textColor": "#666666", + "textAlign": "center", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "body-text", + "elementType": "text", + "bindings": { + "text": "This is a regular body text paragraph that demonstrates normal font weight and standard sizing. It provides readable content for longer text blocks." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": -2, + "unit": "dp" + } + }, + "style": { + "fontSize": 16, + "fontWeight": "normal", + "textColor": "#333333", + "lineHeight": 24 + } + }, + { + "type": "element", + "id": "caption-text", + "elementType": "text", + "bindings": { + "text": "Caption text - smaller size for secondary information" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": -2, + "unit": "dp" + } + }, + "style": { + "fontSize": 12, + "fontWeight": "normal", + "textColor": "#999999", + "lineHeight": 17 + } + }, + { + "type": "element", + "id": "emphasized-text", + "elementType": "text", + "bindings": { + "text": "EMPHASIZED TEXT IN BOLD" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": -2, + "unit": "dp" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#2563EB", + "textAlign": "center", + "textDecoration": "underline", + "lineHeight": 20 + } + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-052-all-image-elements.json b/flutter-sample/assets/test-configs/test-052-all-image-elements.json new file mode 100644 index 0000000..2d714b1 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-052-all-image-elements.json @@ -0,0 +1,162 @@ +{ + "theme": { + "id": "image-grid", + "defaultStyle": { + "textColor": "#000000", + "fontSize": 14, + "lineHeight": 20 + } + }, + "root": { + "type": "container", + "id": "image-grid-container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "type": "spaced" + } + }, + "style": { + "backgroundColor": "#000000" + }, + "children": [ + { + "type": "container", + "id": "row-1", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 180, + "unit": "dp" + }, + "arrangement": { + "type": "spaced" + } + }, + "children": [ + { + "type": "element", + "id": "image-1", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-79.jpg" + }, + "layout": { + "width": { + "value": 50, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "borderRadius": 8 + } + }, + { + "type": "element", + "id": "image-2", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-80.jpg" + }, + "layout": { + "width": { + "value": 50, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "borderRadius": 8 + } + } + ] + }, + { + "type": "container", + "id": "row-2", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 180, + "unit": "dp" + }, + "arrangement": { + "type": "spaced" + } + }, + "children": [ + { + "type": "element", + "id": "image-3", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-81.jpg" + }, + "layout": { + "width": { + "value": 50, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "borderRadius": 8 + } + }, + { + "type": "element", + "id": "image-4", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-82.jpg" + }, + "layout": { + "width": { + "value": 50, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "borderRadius": 8 + } + } + ] + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-053-all-button-elements.json b/flutter-sample/assets/test-configs/test-053-all-button-elements.json new file mode 100644 index 0000000..0170b76 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-053-all-button-elements.json @@ -0,0 +1,155 @@ +{ + "theme": { + "id": "button-actions", + "defaultStyle": { + "textColor": "#FFFFFF", + "fontSize": 16, + "fontWeight": "medium", + "lineHeight": 22 + } + }, + "root": { + "type": "container", + "id": "button-action-bar", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 24 + }, + "arrangement": { + "type": "spaced" + } + }, + "style": { + "background": { + "type": "radial_gradient", + "colors": [ + "#667EEA", + "#764BA2" + ], + "centerX": 0.5, + "centerY": 0.5, + "radius": 1.0 + } + }, + "children": [ + { + "type": "element", + "id": "primary-button", + "elementType": "button", + "bindings": { + "text": "Primary Action" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 56, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#2563EB", + "borderRadius": 12, + "fontSize": 18, + "fontWeight": "bold", + "shadowRadius": 8, + "shadowColor": "#000000", + "shadowOpacity": 0.3, + "shadowOffsetX": 0, + "shadowOffsetY": 4, + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "secondary-button", + "elementType": "button", + "bindings": { + "text": "Secondary Action" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 56, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#10B981", + "borderRadius": 12, + "fontSize": 16, + "fontWeight": "medium", + "lineHeight": 22 + } + }, + { + "type": "element", + "id": "tertiary-button", + "elementType": "button", + "bindings": { + "text": "Tertiary Action" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 56, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#00000000", + "borderRadius": 12, + "borderWidth": 2, + "borderColor": "#FFFFFF", + "fontSize": 16, + "fontWeight": "medium", + "lineHeight": 22 + } + }, + { + "type": "element", + "id": "danger-button", + "elementType": "button", + "bindings": { + "text": "Delete" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 56, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#EF4444", + "borderRadius": 12, + "fontSize": 16, + "fontWeight": "bold", + "lineHeight": 22 + } + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-054-all-video-elements.json b/flutter-sample/assets/test-configs/test-054-all-video-elements.json new file mode 100644 index 0000000..7170310 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-054-all-video-elements.json @@ -0,0 +1,83 @@ +{ + "theme": { + "id": "video-showcase", + "defaultStyle": { + "textColor": "#FFFFFF", + "fontSize": 14, + "lineHeight": 20 + } + }, + "root": { + "type": "container", + "id": "video-container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "type": "spaced" + } + }, + "style": { + "backgroundColor": "#1A1A1A" + }, + "children": [ + { + "type": "element", + "id": "main-video", + "elementType": "video", + "bindings": { + "url": "https://demo.unified-streaming.com/k8s/features/stable/video/tears-of-steel/tears-of-steel.ism/.m3u8" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 240, + "unit": "dp" + } + }, + "style": { + "borderRadius": 12, + "backgroundColor": "#000000" + } + }, + { + "type": "element", + "id": "preview-video", + "elementType": "video", + "bindings": { + "url": "https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 180, + "unit": "dp" + } + }, + "style": { + "borderRadius": 12, + "backgroundColor": "#000000", + "opacity": 0.8 + } + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-055-all-spacer-elements.json b/flutter-sample/assets/test-configs/test-055-all-spacer-elements.json new file mode 100644 index 0000000..daf4a72 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-055-all-spacer-elements.json @@ -0,0 +1,132 @@ +{ + "theme": { + "id": "spacer-layout", + "defaultStyle": { + "textColor": "#000000", + "fontSize": 14, + "lineHeight": 20 + } + }, + "root": { + "type": "container", + "id": "spacer-demo", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 0 + } + }, + "style": { + "background": { + "type": "linear_gradient", + "colors": [ + "#FEF3C7", + "#FDE68A" + ], + "angle": 135 + } + }, + "children": [ + { + "type": "element", + "id": "spacer-small", + "elementType": "spacer", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 24, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#F59E0B40" + } + }, + { + "type": "element", + "id": "spacer-medium", + "elementType": "spacer", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 48, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#F59E0B60" + } + }, + { + "type": "element", + "id": "spacer-large", + "elementType": "spacer", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 72, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#F59E0B80" + } + }, + { + "type": "element", + "id": "spacer-flexible", + "elementType": "spacer", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": -1, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#F59E0BA0" + } + }, + { + "type": "element", + "id": "spacer-extra-large", + "elementType": "spacer", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 96, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#F59E0BC0" + } + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-056-all-divider-elements.json b/flutter-sample/assets/test-configs/test-056-all-divider-elements.json new file mode 100644 index 0000000..5879a22 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-056-all-divider-elements.json @@ -0,0 +1,133 @@ +{ + "theme": { + "id": "divider-sections", + "defaultStyle": { + "textColor": "#374151", + "fontSize": 14, + "lineHeight": 20 + } + }, + "root": { + "type": "container", + "id": "divider-container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 0 + } + }, + "style": { + "backgroundColor": "#F9FAFB" + }, + "children": [ + { + "type": "element", + "id": "divider-thin", + "elementType": "divider", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 1, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#E5E7EB" + } + }, + { + "type": "element", + "id": "divider-medium", + "elementType": "divider", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 2, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#D1D5DB" + } + }, + { + "type": "element", + "id": "divider-thick", + "elementType": "divider", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 4, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#9CA3AF" + } + }, + { + "type": "element", + "id": "divider-colored", + "elementType": "divider", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 3, + "unit": "dp" + } + }, + "style": { + "background": { + "type": "linear_gradient", + "colors": [ + "#3B82F6", + "#8B5CF6", + "#EC4899" + ], + "angle": 90 + } + } + }, + { + "type": "element", + "id": "divider-partial", + "elementType": "divider", + "layout": { + "width": { + "value": 80, + "unit": "percent" + }, + "height": { + "value": 2, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#6B7280" + } + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-057-product-card.json b/flutter-sample/assets/test-configs/test-057-product-card.json new file mode 100644 index 0000000..11cefb1 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-057-product-card.json @@ -0,0 +1,162 @@ +{ + "theme": { + "id": "product-card", + "defaultStyle": { + "textColor": "#1F2937", + "fontSize": 14, + "lineHeight": 20 + } + }, + "root": { + "type": "container", + "id": "product-card-container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 20 + } + }, + "style": { + "backgroundColor": "#F3F4F6" + }, + "children": [ + { + "type": "container", + "id": "product-card", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": -2, + "unit": "dp" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "type": "spaced" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 16, + "shadowRadius": 12, + "shadowColor": "#000000", + "shadowOpacity": 0.1, + "shadowOffsetX": 0, + "shadowOffsetY": 4 + }, + "children": [ + { + "type": "element", + "id": "product-image", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-83.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 240, + "unit": "dp" + } + }, + "style": { + "borderRadius": 12 + } + }, + { + "type": "element", + "id": "product-name", + "elementType": "text", + "bindings": { + "text": "Premium Wireless Headphones" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": -2, + "unit": "dp" + } + }, + "style": { + "fontSize": 20, + "fontWeight": "bold", + "textColor": "#111827", + "lineHeight": 28 + } + }, + { + "type": "element", + "id": "product-price", + "elementType": "text", + "bindings": { + "text": "$299.99" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": -2, + "unit": "dp" + } + }, + "style": { + "fontSize": 24, + "fontWeight": "bold", + "textColor": "#10B981", + "lineHeight": 34 + } + }, + { + "type": "element", + "id": "buy-button", + "elementType": "button", + "bindings": { + "text": "Add to Cart" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 52, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#3B82F6", + "borderRadius": 12, + "textColor": "#FFFFFF", + "fontSize": 16, + "fontWeight": "bold", + "lineHeight": 22 + } + } + ] + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-058-login-form.json b/flutter-sample/assets/test-configs/test-058-login-form.json new file mode 100644 index 0000000..121a9b6 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-058-login-form.json @@ -0,0 +1,172 @@ +{ + "theme": { + "id": "login-form", + "defaultStyle": { + "textColor": "#374151", + "fontSize": 16, + "lineHeight": 22 + } + }, + "root": { + "type": "container", + "id": "login-container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 32 + }, + "arrangement": { + "type": "center" + } + }, + "style": { + "background": { + "type": "linear_gradient", + "colors": [ + "#667EEA", + "#764BA2" + ], + "angle": 135 + } + }, + "children": [ + { + "type": "container", + "id": "login-card", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": -2, + "unit": "dp" + }, + "padding": { + "all": 24 + }, + "arrangement": { + "type": "spaced" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 20, + "shadowRadius": 20, + "shadowColor": "#000000", + "shadowOpacity": 0.3, + "shadowOffsetX": 0, + "shadowOffsetY": 8 + }, + "children": [ + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "Welcome Back" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": -2, + "unit": "dp" + } + }, + "style": { + "fontSize": 28, + "fontWeight": "bold", + "textColor": "#111827", + "textAlign": "center", + "lineHeight": 39 + } + }, + { + "type": "element", + "id": "divider", + "elementType": "divider", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 1, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#E5E7EB" + } + }, + { + "type": "element", + "id": "sign-in-button", + "elementType": "button", + "bindings": { + "text": "Sign In" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 56, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#667EEA", + "borderRadius": 12, + "textColor": "#FFFFFF", + "fontSize": 18, + "fontWeight": "bold", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "forgot-button", + "elementType": "button", + "bindings": { + "text": "Forgot Password?" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 48, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#00000000", + "textColor": "#667EEA", + "fontSize": 14, + "fontWeight": "medium", + "textDecoration": "underline", + "lineHeight": 20 + } + } + ] + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-059-profile-header.json b/flutter-sample/assets/test-configs/test-059-profile-header.json new file mode 100644 index 0000000..939674d --- /dev/null +++ b/flutter-sample/assets/test-configs/test-059-profile-header.json @@ -0,0 +1,182 @@ +{ + "theme": { + "id": "profile-header", + "defaultStyle": { + "textColor": "#1F2937", + "fontSize": 14, + "lineHeight": 20 + } + }, + "root": { + "type": "container", + "id": "profile-container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 0 + } + }, + "style": { + "backgroundColor": "#F9FAFB" + }, + "children": [ + { + "type": "container", + "id": "profile-header-content", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": -2, + "unit": "dp" + }, + "padding": { + "all": 24 + }, + "arrangement": { + "type": "spaced" + } + }, + "style": { + "background": { + "type": "linear_gradient", + "colors": [ + "#6366F1", + "#8B5CF6" + ], + "angle": 135 + } + }, + "children": [ + { + "type": "element", + "id": "avatar", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-84.jpg" + }, + "layout": { + "width": { + "value": 120, + "unit": "dp" + }, + "height": { + "value": 120, + "unit": "dp" + } + }, + "style": { + "borderRadius": 60, + "borderWidth": 4, + "borderColor": "#FFFFFF" + } + }, + { + "type": "element", + "id": "name", + "elementType": "text", + "bindings": { + "text": "Sarah Anderson" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": -2, + "unit": "dp" + } + }, + "style": { + "fontSize": 28, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "textAlign": "center", + "lineHeight": 39 + } + }, + { + "type": "element", + "id": "bio", + "elementType": "text", + "bindings": { + "text": "Product Designer • Tech Enthusiast • Coffee Lover" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": -2, + "unit": "dp" + } + }, + "style": { + "fontSize": 16, + "fontWeight": "normal", + "textColor": "#E0E7FF", + "textAlign": "center", + "lineHeight": 22 + } + }, + { + "type": "element", + "id": "spacer", + "elementType": "spacer", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 8, + "unit": "dp" + } + } + }, + { + "type": "element", + "id": "edit-button", + "elementType": "button", + "bindings": { + "text": "Edit Profile" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 48, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 24, + "textColor": "#6366F1", + "fontSize": 16, + "fontWeight": "bold", + "lineHeight": 22 + } + } + ] + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-060-media-player.json b/flutter-sample/assets/test-configs/test-060-media-player.json new file mode 100644 index 0000000..77b79b5 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-060-media-player.json @@ -0,0 +1,268 @@ +{ + "theme": { + "id": "media-player", + "defaultStyle": { + "textColor": "#FFFFFFFF", + "fontSize": 14, + "fontFamily": "System", + "lineHeight": 20 + } + }, + "variables": { + "videoTitle": "Wildlife Documentary: Nature's Wonders", + "videoUrl": "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4", + "duration": "12:34", + "currentTime": "5:42" + }, + "root": { + "id": "media-player-container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 0 + } + }, + "style": { + "backgroundColor": "#FF000000" + }, + "children": [ + { + "id": "video-player", + "elementType": "video", + "bindings": { + "url": "{{videoUrl}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 280, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF000000" + }, + "type": "element" + }, + { + "id": "controls-container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 24 + }, + "arrangement": { + "spacing": 16, + "strategy": "spaced" + } + }, + "style": { + "backgroundColor": "#FF1A1A1A" + }, + "children": [ + { + "id": "video-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "arrangement": { + "spacing": 8, + "strategy": "spaced" + } + }, + "children": [ + { + "id": "video-title", + "elementType": "text", + "bindings": { + "text": "{{videoTitle}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 20, + "fontWeight": "bold", + "textColor": "#FFFFFFFF", + "lineHeight": 28 + }, + "type": "element" + }, + { + "id": "time-display", + "elementType": "text", + "bindings": { + "text": "{{currentTime}} / {{duration}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "normal", + "textColor": "#FFB3B3B3", + "lineHeight": 20 + }, + "type": "element" + } + ], + "type": "container" + }, + { + "id": "progress-bar", + "elementType": "divider", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 4, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FFEF4444", + "borderRadius": 2 + }, + "type": "element" + }, + { + "id": "button-row", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 64, + "unit": "dp" + }, + "arrangement": { + "strategy": "space_evenly" + } + }, + "children": [ + { + "id": "rewind-button", + "elementType": "button", + "bindings": { + "text": "⏪" + }, + "layout": { + "width": { + "value": 64, + "unit": "dp" + }, + "height": { + "value": 64, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF333333", + "borderRadius": 32, + "fontSize": 28, + "textColor": "#FFFFFFFF", + "shadowRadius": 8, + "shadowColor": "#FF000000", + "shadowOpacity": 0.3, + "shadowOffsetY": 4, + "lineHeight": 39 + }, + "type": "element" + }, + { + "id": "play-button", + "elementType": "button", + "bindings": { + "text": "▶️" + }, + "layout": { + "width": { + "value": 80, + "unit": "dp" + }, + "height": { + "value": 80, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FFEF4444", + "borderRadius": 40, + "fontSize": 32, + "textColor": "#FFFFFFFF", + "shadowRadius": 12, + "shadowColor": "#FFEF4444", + "shadowOpacity": 0.5, + "shadowOffsetY": 6, + "lineHeight": 45 + }, + "type": "element" + }, + { + "id": "forward-button", + "elementType": "button", + "bindings": { + "text": "⏩" + }, + "layout": { + "width": { + "value": 64, + "unit": "dp" + }, + "height": { + "value": 64, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF333333", + "borderRadius": 32, + "fontSize": 28, + "textColor": "#FFFFFFFF", + "shadowRadius": 8, + "shadowColor": "#FF000000", + "shadowOpacity": 0.3, + "shadowOffsetY": 4, + "lineHeight": 39 + }, + "type": "element" + } + ], + "type": "container" + } + ], + "type": "container" + } + ], + "type": "container" + }, + "styleClasses": [] +} diff --git a/flutter-sample/assets/test-configs/test-061-article-layout.json b/flutter-sample/assets/test-configs/test-061-article-layout.json new file mode 100644 index 0000000..be6093a --- /dev/null +++ b/flutter-sample/assets/test-configs/test-061-article-layout.json @@ -0,0 +1,243 @@ +{ + "theme": { + "id": "article-layout", + "defaultStyle": { + "textColor": "#FF374151", + "fontSize": 16, + "lineHeight": 24, + "fontFamily": "System" + } + }, + "variables": { + "articleTitle": "The Future of Mobile Design", + "articleAuthor": "Sarah Johnson", + "publishDate": "February 17, 2026", + "heroImage": "https://yavuzceliker.github.io/sample-images/image-1001.jpg", + "intro": "In the rapidly evolving world of mobile technology, design principles are constantly being redefined. This article explores the latest trends and innovations shaping the future of mobile user interfaces.", + "body": "Mobile design has come a long way from simple static screens. Today's interfaces are dynamic, responsive, and increasingly intelligent. They adapt to user behavior, context, and preferences in ways that were unimaginable just a few years ago.\n\nAs we look to the future, we see exciting possibilities emerging in areas such as augmented reality, voice interfaces, and personalized experiences. The challenge for designers is to harness these technologies while maintaining simplicity and usability." + }, + "root": { + "id": "article-container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 24 + }, + "arrangement": { + "spacing": 20, + "strategy": "spaced" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF" + }, + "children": [ + { + "id": "article-header", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "arrangement": { + "spacing": 12, + "strategy": "spaced" + } + }, + "children": [ + { + "id": "article-title", + "elementType": "text", + "bindings": { + "text": "{{articleTitle}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 32, + "fontWeight": "bold", + "textColor": "#FF111827", + "lineHeight": 40 + }, + "type": "element" + }, + { + "id": "article-meta", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "arrangement": { + "spacing": 8, + "strategy": "spaced" + } + }, + "children": [ + { + "id": "author", + "elementType": "text", + "bindings": { + "text": "{{articleAuthor}}" + }, + "layout": { + "width": { + "value": -2, + "unit": "dp" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "medium", + "textColor": "#FF6B7280", + "lineHeight": 20 + }, + "type": "element" + }, + { + "id": "separator", + "elementType": "text", + "bindings": { + "text": "•" + }, + "layout": { + "width": { + "value": -2, + "unit": "dp" + } + }, + "style": { + "fontSize": 14, + "textColor": "#FF9CA3AF", + "lineHeight": 20 + }, + "type": "element" + }, + { + "id": "date", + "elementType": "text", + "bindings": { + "text": "{{publishDate}}" + }, + "layout": { + "width": { + "value": -2, + "unit": "dp" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "normal", + "textColor": "#FF6B7280", + "lineHeight": 20 + }, + "type": "element" + } + ], + "type": "container" + } + ], + "type": "container" + }, + { + "id": "hero-image", + "elementType": "image", + "bindings": { + "url": "{{heroImage}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 220, + "unit": "dp" + } + }, + "style": { + "borderRadius": 16, + "shadowRadius": 12, + "shadowColor": "#FF000000", + "shadowOpacity": 0.1, + "shadowOffsetY": 4 + }, + "type": "element" + }, + { + "id": "article-intro", + "elementType": "text", + "bindings": { + "text": "{{intro}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "medium", + "textColor": "#FF4B5563", + "lineHeight": 28 + }, + "type": "element" + }, + { + "id": "divider", + "elementType": "divider", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 2, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FFE5E7EB" + }, + "type": "element" + }, + { + "id": "article-body", + "elementType": "text", + "bindings": { + "text": "{{body}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 16, + "fontWeight": "normal", + "textColor": "#FF374151", + "lineHeight": 26 + }, + "type": "element" + } + ], + "type": "container" + }, + "styleClasses": [] +} diff --git a/flutter-sample/assets/test-configs/test-062-action-sheet.json b/flutter-sample/assets/test-configs/test-062-action-sheet.json new file mode 100644 index 0000000..781b5b4 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-062-action-sheet.json @@ -0,0 +1,303 @@ +{ + "theme": { + "id": "action-sheet", + "defaultStyle": { + "textColor": "#FF1F2937", + "fontSize": 16, + "fontFamily": "System", + "lineHeight": 22 + } + }, + "variables": { + "sheetTitle": "Choose an Action", + "action1": "Share", + "action2": "Edit", + "action3": "Duplicate", + "action4": "Delete", + "cancelText": "Cancel" + }, + "root": { + "id": "action-sheet-backdrop", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 0 + }, + "arrangement": { + "strategy": "end" + } + }, + "style": { + "backgroundColor": "#80000000" + }, + "children": [ + { + "id": "action-sheet", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 0 + } + }, + "style": { + "backgroundColor": "#FFF9FAFB", + "borderRadius": 24 + }, + "children": [ + { + "id": "sheet-header", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 20 + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 24 + }, + "children": [ + { + "id": "header-text", + "elementType": "text", + "bindings": { + "text": "{{sheetTitle}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "textColor": "#FF111827", + "textAlign": "center", + "lineHeight": 25 + }, + "type": "element" + }, + { + "id": "handle-spacer", + "elementType": "spacer", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 8, + "unit": "dp" + } + }, + "type": "element" + } + ], + "type": "container" + }, + { + "id": "divider-1", + "elementType": "divider", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 1, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FFE5E7EB" + }, + "type": "element" + }, + { + "id": "actions-container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 0 + } + }, + "style": { + "backgroundColor": "#FFFFFFFF" + }, + "children": [ + { + "id": "action-1", + "elementType": "button", + "bindings": { + "text": "{{action1}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 56, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#00000000", + "textColor": "#FF3B82F6", + "fontSize": 17, + "fontWeight": "medium", + "lineHeight": 24 + }, + "type": "element" + }, + { + "id": "action-2", + "elementType": "button", + "bindings": { + "text": "{{action2}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 56, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#00000000", + "textColor": "#FF3B82F6", + "fontSize": 17, + "fontWeight": "medium", + "lineHeight": 24 + }, + "type": "element" + }, + { + "id": "action-3", + "elementType": "button", + "bindings": { + "text": "{{action3}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 56, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#00000000", + "textColor": "#FF3B82F6", + "fontSize": 17, + "fontWeight": "medium", + "lineHeight": 24 + }, + "type": "element" + }, + { + "id": "action-4", + "elementType": "button", + "bindings": { + "text": "{{action4}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 56, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#00000000", + "textColor": "#FFEF4444", + "fontSize": 17, + "fontWeight": "medium", + "lineHeight": 24 + }, + "type": "element" + } + ], + "type": "container" + }, + { + "id": "divider-2", + "elementType": "spacer", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 8, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FFF3F4F6" + }, + "type": "element" + }, + { + "id": "cancel-button", + "elementType": "button", + "bindings": { + "text": "{{cancelText}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 56, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "textColor": "#FF6B7280", + "fontSize": 17, + "fontWeight": "bold", + "borderRadius": 16, + "lineHeight": 24 + }, + "type": "element" + } + ], + "type": "container" + } + ], + "type": "container" + }, + "styleClasses": [] +} diff --git a/flutter-sample/assets/test-configs/test-063-stats-card.json b/flutter-sample/assets/test-configs/test-063-stats-card.json new file mode 100644 index 0000000..edd7d84 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-063-stats-card.json @@ -0,0 +1,460 @@ +{ + "theme": { + "id": "stats-card", + "defaultStyle": { + "textColor": "#FF1F2937", + "fontSize": 14, + "fontFamily": "System", + "lineHeight": 20 + } + }, + "variables": { + "revenueLabel": "Revenue", + "revenueValue": "$45.2K", + "revenueChange": "+12.5%", + "ordersLabel": "Orders", + "ordersValue": "1,248", + "ordersChange": "+8.2%", + "usersLabel": "Users", + "usersValue": "8,429", + "usersChange": "+5.7%", + "conversionLabel": "Conversion", + "conversionValue": "3.42%", + "conversionChange": "+0.8%" + }, + "root": { + "id": "stats-container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 20 + } + }, + "style": { + "backgroundColor": "#FFF9FAFB" + }, + "children": [ + { + "id": "stats-grid", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "arrangement": { + "spacing": 16, + "strategy": "spaced" + } + }, + "children": [ + { + "id": "row-1", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": -2, + "unit": "dp" + }, + "arrangement": { + "spacing": 16, + "strategy": "spaced" + } + }, + "children": [ + { + "id": "stat-card-1", + "containerType": "vertical", + "layout": { + "width": { + "value": -1, + "unit": "dp" + }, + "padding": { + "all": 20 + }, + "arrangement": { + "spacing": 12, + "strategy": "spaced" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 16, + "shadowRadius": 12, + "shadowColor": "#FF000000", + "shadowOpacity": 0.08, + "shadowOffsetY": 4 + }, + "children": [ + { + "id": "revenue-label", + "elementType": "text", + "bindings": { + "text": "{{revenueLabel}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 12, + "fontWeight": "medium", + "textColor": "#FF6B7280", + "lineHeight": 17 + }, + "type": "element" + }, + { + "id": "revenue-value", + "elementType": "text", + "bindings": { + "text": "{{revenueValue}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 28, + "fontWeight": "bold", + "textColor": "#FF111827", + "lineHeight": 39 + }, + "type": "element" + }, + { + "id": "revenue-change", + "elementType": "text", + "bindings": { + "text": "{{revenueChange}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 12, + "fontWeight": "medium", + "textColor": "#FF10B981", + "lineHeight": 17 + }, + "type": "element" + } + ], + "type": "container" + }, + { + "id": "stat-card-2", + "containerType": "vertical", + "layout": { + "width": { + "value": -1, + "unit": "dp" + }, + "padding": { + "all": 20 + }, + "arrangement": { + "spacing": 12, + "strategy": "spaced" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 16, + "shadowRadius": 12, + "shadowColor": "#FF000000", + "shadowOpacity": 0.08, + "shadowOffsetY": 4 + }, + "children": [ + { + "id": "orders-label", + "elementType": "text", + "bindings": { + "text": "{{ordersLabel}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 12, + "fontWeight": "medium", + "textColor": "#FF6B7280", + "lineHeight": 17 + }, + "type": "element" + }, + { + "id": "orders-value", + "elementType": "text", + "bindings": { + "text": "{{ordersValue}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 28, + "fontWeight": "bold", + "textColor": "#FF3B82F6", + "lineHeight": 39 + }, + "type": "element" + }, + { + "id": "orders-change", + "elementType": "text", + "bindings": { + "text": "{{ordersChange}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 12, + "fontWeight": "medium", + "textColor": "#FF10B981", + "lineHeight": 17 + }, + "type": "element" + } + ], + "type": "container" + } + ], + "type": "container" + }, + { + "id": "row-2", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": -2, + "unit": "dp" + }, + "arrangement": { + "spacing": 16, + "strategy": "spaced" + } + }, + "children": [ + { + "id": "stat-card-3", + "containerType": "vertical", + "layout": { + "width": { + "value": -1, + "unit": "dp" + }, + "padding": { + "all": 20 + }, + "arrangement": { + "spacing": 12, + "strategy": "spaced" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 16, + "shadowRadius": 12, + "shadowColor": "#FF000000", + "shadowOpacity": 0.08, + "shadowOffsetY": 4 + }, + "children": [ + { + "id": "users-label", + "elementType": "text", + "bindings": { + "text": "{{usersLabel}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 12, + "fontWeight": "medium", + "textColor": "#FF6B7280", + "lineHeight": 17 + }, + "type": "element" + }, + { + "id": "users-value", + "elementType": "text", + "bindings": { + "text": "{{usersValue}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 28, + "fontWeight": "bold", + "textColor": "#FF8B5CF6", + "lineHeight": 39 + }, + "type": "element" + }, + { + "id": "users-change", + "elementType": "text", + "bindings": { + "text": "{{usersChange}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 12, + "fontWeight": "medium", + "textColor": "#FF10B981", + "lineHeight": 17 + }, + "type": "element" + } + ], + "type": "container" + }, + { + "id": "stat-card-4", + "containerType": "vertical", + "layout": { + "width": { + "value": -1, + "unit": "dp" + }, + "padding": { + "all": 20 + }, + "arrangement": { + "spacing": 12, + "strategy": "spaced" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 16, + "shadowRadius": 12, + "shadowColor": "#FF000000", + "shadowOpacity": 0.08, + "shadowOffsetY": 4 + }, + "children": [ + { + "id": "conversion-label", + "elementType": "text", + "bindings": { + "text": "{{conversionLabel}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 12, + "fontWeight": "medium", + "textColor": "#FF6B7280", + "lineHeight": 17 + }, + "type": "element" + }, + { + "id": "conversion-value", + "elementType": "text", + "bindings": { + "text": "{{conversionValue}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 28, + "fontWeight": "bold", + "textColor": "#FFF59E0B", + "lineHeight": 39 + }, + "type": "element" + }, + { + "id": "conversion-change", + "elementType": "text", + "bindings": { + "text": "{{conversionChange}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 12, + "fontWeight": "medium", + "textColor": "#FF10B981", + "lineHeight": 17 + }, + "type": "element" + } + ], + "type": "container" + } + ], + "type": "container" + } + ], + "type": "container" + } + ], + "type": "container" + }, + "styleClasses": [] +} diff --git a/flutter-sample/assets/test-configs/test-064-gallery-item.json b/flutter-sample/assets/test-configs/test-064-gallery-item.json new file mode 100644 index 0000000..573c60e --- /dev/null +++ b/flutter-sample/assets/test-configs/test-064-gallery-item.json @@ -0,0 +1,334 @@ +{ + "theme": { + "id": "gallery-item", + "defaultStyle": { + "textColor": "#FF1F2937", + "fontSize": 14, + "fontFamily": "System", + "lineHeight": 20 + } + }, + "variables": { + "imageUrl": "https://yavuzceliker.github.io/sample-images/image-450.jpg", + "title": "Mountain Landscape Photography", + "description": "Breathtaking views captured at sunrise in the Swiss Alps", + "photographer": "Alex Morrison", + "likes": "2,847", + "views": "15.2K", + "buttonText": "View Gallery" + }, + "root": { + "id": "gallery-item-container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#FFF3F4F6" + }, + "children": [ + { + "id": "gallery-card", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 0 + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 20, + "shadowRadius": 16, + "shadowColor": "#FF000000", + "shadowOpacity": 0.12, + "shadowOffsetY": 6 + }, + "children": [ + { + "id": "image-container", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 240, + "unit": "dp" + } + }, + "children": [ + { + "id": "gallery-image", + "elementType": "image", + "bindings": { + "url": "{{imageUrl}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 240, + "unit": "dp" + } + }, + "style": { + "borderRadius": 20 + }, + "type": "element" + }, + { + "id": "overlay", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "strategy": "space_between" + } + }, + "style": { + "background": { + "type": "linear_gradient", + "colors": [ + "#00000000", + "#80000000" + ], + "angle": 180 + } + }, + "children": [ + { + "id": "top-spacer", + "elementType": "spacer", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 1, + "unit": "dp" + } + }, + "type": "element" + }, + { + "id": "stats-row", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "arrangement": { + "spacing": 16, + "strategy": "spaced" + } + }, + "children": [ + { + "id": "likes", + "elementType": "text", + "bindings": { + "text": "❤️ {{likes}}" + }, + "layout": { + "width": { + "value": -2, + "unit": "dp" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#FFFFFFFF", + "shadowRadius": 4, + "shadowColor": "#FF000000", + "shadowOpacity": 0.3, + "lineHeight": 20 + }, + "type": "element" + }, + { + "id": "views", + "elementType": "text", + "bindings": { + "text": "👁️ {{views}}" + }, + "layout": { + "width": { + "value": -2, + "unit": "dp" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#FFFFFFFF", + "shadowRadius": 4, + "shadowColor": "#FF000000", + "shadowOpacity": 0.3, + "lineHeight": 20 + }, + "type": "element" + } + ], + "type": "container" + } + ], + "type": "container" + } + ], + "type": "container" + }, + { + "id": "content-container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 20 + }, + "arrangement": { + "spacing": 12, + "strategy": "spaced" + } + }, + "children": [ + { + "id": "title", + "elementType": "text", + "bindings": { + "text": "{{title}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 20, + "fontWeight": "bold", + "textColor": "#FF111827", + "lineHeight": 28 + }, + "type": "element" + }, + { + "id": "description", + "elementType": "text", + "bindings": { + "text": "{{description}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "normal", + "textColor": "#FF6B7280", + "lineHeight": 20 + }, + "type": "element" + }, + { + "id": "photographer", + "elementType": "text", + "bindings": { + "text": "By {{photographer}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 12, + "fontWeight": "medium", + "textColor": "#FF9CA3AF", + "lineHeight": 17 + }, + "type": "element" + }, + { + "id": "view-button", + "elementType": "button", + "bindings": { + "text": "{{buttonText}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 48, + "unit": "dp" + } + }, + "style": { + "background": { + "type": "linear_gradient", + "colors": [ + "#FF3B82F6", + "#FF8B5CF6" + ], + "angle": 90 + }, + "borderRadius": 12, + "textColor": "#FFFFFFFF", + "fontSize": 16, + "fontWeight": "bold", + "shadowRadius": 12, + "shadowColor": "#FF3B82F6", + "shadowOpacity": 0.4, + "shadowOffsetY": 4, + "lineHeight": 22 + }, + "type": "element" + } + ], + "type": "container" + } + ], + "type": "container" + } + ], + "type": "container" + }, + "styleClasses": [] +} diff --git a/flutter-sample/assets/test-configs/test-065-notification.json b/flutter-sample/assets/test-configs/test-065-notification.json new file mode 100644 index 0000000..8215845 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-065-notification.json @@ -0,0 +1,256 @@ +{ + "theme": { + "id": "notification", + "defaultStyle": { + "textColor": "#FF1F2937", + "fontSize": 14, + "fontFamily": "System", + "lineHeight": 20 + } + }, + "variables": { + "senderName": "Sarah Johnson", + "messageTitle": "New Message", + "messageBody": "Hey! Just wanted to check in about our meeting tomorrow at 3 PM. Looking forward to discussing the project updates.", + "timestamp": "2 minutes ago", + "avatarUrl": "https://yavuzceliker.github.io/sample-images/image-750.jpg", + "replyText": "Reply" + }, + "root": { + "id": "notification-container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#FFF9FAFB" + }, + "children": [ + { + "id": "notification-card", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "spacing": 16, + "strategy": "spaced" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 16, + "shadowRadius": 12, + "shadowColor": "#FF000000", + "shadowOpacity": 0.1, + "shadowOffsetY": 4, + "borderWidth": 1, + "borderColor": "#FFE5E7EB" + }, + "children": [ + { + "id": "avatar-container", + "containerType": "vertical", + "layout": { + "width": { + "value": 56, + "unit": "dp" + }, + "height": { + "value": 56, + "unit": "dp" + } + }, + "children": [ + { + "id": "avatar", + "elementType": "image", + "bindings": { + "url": "{{avatarUrl}}" + }, + "layout": { + "width": { + "value": 56, + "unit": "dp" + }, + "height": { + "value": 56, + "unit": "dp" + } + }, + "style": { + "borderRadius": 28, + "borderWidth": 2, + "borderColor": "#FF3B82F6" + }, + "type": "element" + } + ], + "type": "container" + }, + { + "id": "content-container", + "containerType": "vertical", + "layout": { + "width": { + "value": -1, + "unit": "dp" + }, + "arrangement": { + "spacing": 8, + "strategy": "spaced" + } + }, + "children": [ + { + "id": "header-row", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "arrangement": { + "strategy": "space_between" + } + }, + "children": [ + { + "id": "sender-name", + "elementType": "text", + "bindings": { + "text": "{{senderName}}" + }, + "layout": { + "width": { + "value": -2, + "unit": "dp" + } + }, + "style": { + "fontSize": 16, + "fontWeight": "bold", + "textColor": "#FF111827", + "lineHeight": 22 + }, + "type": "element" + }, + { + "id": "timestamp", + "elementType": "text", + "bindings": { + "text": "{{timestamp}}" + }, + "layout": { + "width": { + "value": -2, + "unit": "dp" + } + }, + "style": { + "fontSize": 12, + "fontWeight": "normal", + "textColor": "#FF9CA3AF", + "lineHeight": 17 + }, + "type": "element" + } + ], + "type": "container" + }, + { + "id": "message-title", + "elementType": "text", + "bindings": { + "text": "{{messageTitle}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 15, + "fontWeight": "medium", + "textColor": "#FF374151", + "lineHeight": 21 + }, + "type": "element" + }, + { + "id": "message-body", + "elementType": "text", + "bindings": { + "text": "{{messageBody}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "normal", + "textColor": "#FF6B7280", + "lineHeight": 20 + }, + "type": "element" + }, + { + "id": "reply-button", + "elementType": "button", + "bindings": { + "text": "{{replyText}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 40, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF3B82F6", + "borderRadius": 10, + "textColor": "#FFFFFFFF", + "fontSize": 14, + "fontWeight": "medium", + "shadowRadius": 8, + "shadowColor": "#FF3B82F6", + "shadowOpacity": 0.3, + "shadowOffsetY": 2, + "lineHeight": 20 + }, + "type": "element" + } + ], + "type": "container" + } + ], + "type": "container" + } + ], + "type": "container" + }, + "styleClasses": [] +} diff --git a/flutter-sample/assets/test-configs/test-066-pricing-card.json b/flutter-sample/assets/test-configs/test-066-pricing-card.json new file mode 100644 index 0000000..248248e --- /dev/null +++ b/flutter-sample/assets/test-configs/test-066-pricing-card.json @@ -0,0 +1,424 @@ +{ + "theme": { + "id": "pricing-card", + "defaultStyle": { + "textColor": "#FF1F2937", + "fontSize": 14, + "fontFamily": "System", + "lineHeight": 20 + } + }, + "variables": { + "planName": "Premium Plan", + "planPrice": "$49", + "planPeriod": "per month", + "feature1": "✓ Unlimited Projects", + "feature2": "✓ Priority Support 24/7", + "feature3": "✓ Advanced Analytics", + "feature4": "✓ Custom Integrations", + "feature5": "✓ Team Collaboration", + "feature6": "✓ Export & Backup", + "buttonText": "Subscribe Now", + "badge": "POPULAR" + }, + "root": { + "id": "pricing-container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 20 + } + }, + "style": { + "background": { + "type": "linear_gradient", + "colors": [ + "#FFEFF6FF", + "#FFDBEAFE" + ], + "angle": 135 + } + }, + "children": [ + { + "id": "pricing-card", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 28 + }, + "arrangement": { + "spacing": 20, + "strategy": "spaced" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 24, + "borderWidth": 3, + "borderColor": "#FF3B82F6", + "shadowRadius": 24, + "shadowColor": "#FF3B82F6", + "shadowOpacity": 0.25, + "shadowOffsetY": 12 + }, + "children": [ + { + "id": "badge-container", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "arrangement": { + "strategy": "center" + } + }, + "children": [ + { + "id": "badge", + "elementType": "text", + "bindings": { + "text": "{{badge}}" + }, + "layout": { + "width": { + "value": -2, + "unit": "dp" + }, + "padding": { + "left": 16, + "right": 16, + "top": 6, + "bottom": 6 + } + }, + "style": { + "fontSize": 12, + "fontWeight": "bold", + "textColor": "#FFFFFFFF", + "backgroundColor": "#FF3B82F6", + "borderRadius": 12, + "lineHeight": 17 + }, + "type": "element" + } + ], + "type": "container" + }, + { + "id": "plan-header", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "arrangement": { + "spacing": 8, + "strategy": "spaced" + } + }, + "children": [ + { + "id": "plan-name", + "elementType": "text", + "bindings": { + "text": "{{planName}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 28, + "fontWeight": "bold", + "textColor": "#FF111827", + "textAlign": "center", + "lineHeight": 39 + }, + "type": "element" + }, + { + "id": "price-container", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "arrangement": { + "spacing": 4, + "strategy": "center" + } + }, + "children": [ + { + "id": "price", + "elementType": "text", + "bindings": { + "text": "{{planPrice}}" + }, + "layout": { + "width": { + "value": -2, + "unit": "dp" + } + }, + "style": { + "fontSize": 56, + "fontWeight": "bold", + "textColor": "#FF3B82F6", + "lineHeight": 78 + }, + "type": "element" + } + ], + "type": "container" + }, + { + "id": "period", + "elementType": "text", + "bindings": { + "text": "{{planPeriod}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 16, + "fontWeight": "normal", + "textColor": "#FF6B7280", + "textAlign": "center", + "lineHeight": 22 + }, + "type": "element" + } + ], + "type": "container" + }, + { + "id": "divider", + "elementType": "divider", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 2, + "unit": "dp" + } + }, + "style": { + "background": { + "type": "linear_gradient", + "colors": [ + "#FF3B82F6", + "#FF8B5CF6" + ], + "angle": 90 + } + }, + "type": "element" + }, + { + "id": "features-list", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "arrangement": { + "spacing": 12, + "strategy": "spaced" + } + }, + "children": [ + { + "id": "feature-1", + "elementType": "text", + "bindings": { + "text": "{{feature1}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 15, + "fontWeight": "normal", + "textColor": "#FF374151", + "lineHeight": 22 + }, + "type": "element" + }, + { + "id": "feature-2", + "elementType": "text", + "bindings": { + "text": "{{feature2}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 15, + "fontWeight": "normal", + "textColor": "#FF374151", + "lineHeight": 22 + }, + "type": "element" + }, + { + "id": "feature-3", + "elementType": "text", + "bindings": { + "text": "{{feature3}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 15, + "fontWeight": "normal", + "textColor": "#FF374151", + "lineHeight": 22 + }, + "type": "element" + }, + { + "id": "feature-4", + "elementType": "text", + "bindings": { + "text": "{{feature4}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 15, + "fontWeight": "normal", + "textColor": "#FF374151", + "lineHeight": 22 + }, + "type": "element" + }, + { + "id": "feature-5", + "elementType": "text", + "bindings": { + "text": "{{feature5}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 15, + "fontWeight": "normal", + "textColor": "#FF374151", + "lineHeight": 22 + }, + "type": "element" + }, + { + "id": "feature-6", + "elementType": "text", + "bindings": { + "text": "{{feature6}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 15, + "fontWeight": "normal", + "textColor": "#FF374151", + "lineHeight": 22 + }, + "type": "element" + } + ], + "type": "container" + }, + { + "id": "subscribe-button", + "elementType": "button", + "bindings": { + "text": "{{buttonText}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 56, + "unit": "dp" + } + }, + "style": { + "background": { + "type": "linear_gradient", + "colors": [ + "#FF3B82F6", + "#FF8B5CF6" + ], + "angle": 90 + }, + "borderRadius": 16, + "textColor": "#FFFFFFFF", + "fontSize": 18, + "fontWeight": "bold", + "shadowRadius": 16, + "shadowColor": "#FF3B82F6", + "shadowOpacity": 0.4, + "shadowOffsetY": 6, + "lineHeight": 25 + }, + "type": "element" + } + ], + "type": "container" + } + ], + "type": "container" + }, + "styleClasses": [] +} diff --git a/flutter-sample/assets/test-configs/test-067-hero-banner.json b/flutter-sample/assets/test-configs/test-067-hero-banner.json new file mode 100644 index 0000000..35ca3d8 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-067-hero-banner.json @@ -0,0 +1,235 @@ +{ + "theme": { + "id": "hero-banner", + "defaultStyle": { + "textColor": "#FFFFFFFF", + "fontSize": 16, + "fontFamily": "System", + "lineHeight": 22 + } + }, + "variables": { + "heroImage": "https://yavuzceliker.github.io/sample-images/image-1200.jpg", + "headline": "Experience Innovation", + "subheadline": "Discover the future of mobile technology with our cutting-edge solutions designed for modern businesses", + "ctaButton": "Get Started", + "tagline": "Trusted by 10,000+ companies worldwide" + }, + "root": { + "id": "hero-container", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 0 + } + }, + "children": [ + { + "id": "background-image", + "elementType": "image", + "bindings": { + "url": "{{heroImage}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "opacity": 0.8 + }, + "type": "element" + }, + { + "id": "overlay-container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 32 + }, + "arrangement": { + "strategy": "center" + } + }, + "style": { + "background": { + "type": "linear_gradient", + "colors": [ + "#99000000", + "#CC000000" + ], + "angle": 180 + } + }, + "children": [ + { + "id": "content-wrapper", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "arrangement": { + "spacing": 24, + "strategy": "spaced" + } + }, + "children": [ + { + "id": "headline", + "elementType": "text", + "bindings": { + "text": "{{headline}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 48, + "fontWeight": "bold", + "textColor": "#FFFFFFFF", + "textAlign": "center", + "lineHeight": 56, + "textShadow": { + "color": "#FF000000", + "radius": 8, + "offsetX": 0, + "offsetY": 4 + } + }, + "type": "element" + }, + { + "id": "subheadline", + "elementType": "text", + "bindings": { + "text": "{{subheadline}}" + }, + "layout": { + "width": { + "value": 90, + "unit": "percent" + }, + "padding": { + "left": 16, + "right": 16 + } + }, + "style": { + "fontSize": 18, + "fontWeight": "normal", + "textColor": "#FFE5E7EB", + "textAlign": "center", + "lineHeight": 28, + "textShadow": { + "color": "#FF000000", + "radius": 4, + "offsetX": 0, + "offsetY": 2 + } + }, + "type": "element" + }, + { + "id": "cta-button", + "elementType": "button", + "bindings": { + "text": "{{ctaButton}}" + }, + "layout": { + "width": { + "value": 80, + "unit": "percent" + }, + "height": { + "value": 64, + "unit": "dp" + } + }, + "style": { + "background": { + "type": "linear_gradient", + "colors": [ + "#FFF59E0B", + "#FFEF4444" + ], + "angle": 135 + }, + "borderRadius": 32, + "textColor": "#FFFFFFFF", + "fontSize": 20, + "fontWeight": "bold", + "shadowRadius": 20, + "shadowColor": "#FFF59E0B", + "shadowOpacity": 0.6, + "shadowOffsetY": 8, + "lineHeight": 28 + }, + "type": "element" + }, + { + "id": "tagline", + "elementType": "text", + "bindings": { + "text": "{{tagline}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "padding": { + "top": 16 + } + }, + "style": { + "fontSize": 14, + "fontWeight": "medium", + "textColor": "#FFD1D5DB", + "textAlign": "center", + "textShadow": { + "color": "#FF000000", + "radius": 4, + "offsetX": 0, + "offsetY": 2 + }, + "lineHeight": 20 + }, + "type": "element" + } + ], + "type": "container" + } + ], + "type": "container" + } + ], + "type": "container" + }, + "styleClasses": [] +} diff --git a/flutter-sample/assets/test-configs/test-068-social-post.json b/flutter-sample/assets/test-configs/test-068-social-post.json new file mode 100644 index 0000000..a70459f --- /dev/null +++ b/flutter-sample/assets/test-configs/test-068-social-post.json @@ -0,0 +1,394 @@ +{ + "theme": { + "id": "social-post", + "defaultStyle": { + "textColor": "#FF1F2937", + "fontSize": 14, + "fontFamily": "System", + "lineHeight": 20 + } + }, + "variables": { + "userName": "Alex Thompson", + "userHandle": "@alexthompson", + "avatarUrl": "https://yavuzceliker.github.io/sample-images/image-890.jpg", + "postTime": "2 hours ago", + "postText": "Just finished an amazing hike! The views from the summit were absolutely breathtaking. Nature never fails to inspire and rejuvenate the soul. 🏔️✨", + "postImage": "https://yavuzceliker.github.io/sample-images/image-1350.jpg", + "likes": "342", + "comments": "28", + "shares": "15", + "commentButtonText": "Add Comment" + }, + "root": { + "id": "social-post-container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#FFF9FAFB" + }, + "children": [ + { + "id": "post-card", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "spacing": 16, + "strategy": "spaced" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 16, + "shadowRadius": 12, + "shadowColor": "#FF000000", + "shadowOpacity": 0.08, + "shadowOffsetY": 4 + }, + "children": [ + { + "id": "post-header", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "arrangement": { + "spacing": 12, + "strategy": "spaced" + } + }, + "children": [ + { + "id": "avatar", + "elementType": "image", + "bindings": { + "url": "{{avatarUrl}}" + }, + "layout": { + "width": { + "value": 48, + "unit": "dp" + }, + "height": { + "value": 48, + "unit": "dp" + } + }, + "style": { + "borderRadius": 24, + "borderWidth": 2, + "borderColor": "#FF3B82F6" + }, + "type": "element" + }, + { + "id": "user-info", + "containerType": "vertical", + "layout": { + "width": { + "value": -1, + "unit": "dp" + }, + "arrangement": { + "spacing": 4, + "strategy": "spaced" + } + }, + "children": [ + { + "id": "user-name", + "elementType": "text", + "bindings": { + "text": "{{userName}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 16, + "fontWeight": "bold", + "textColor": "#FF111827", + "lineHeight": 22 + }, + "type": "element" + }, + { + "id": "user-handle-row", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "arrangement": { + "spacing": 8, + "strategy": "spaced" + } + }, + "children": [ + { + "id": "user-handle", + "elementType": "text", + "bindings": { + "text": "{{userHandle}}" + }, + "layout": { + "width": { + "value": -2, + "unit": "dp" + } + }, + "style": { + "fontSize": 13, + "fontWeight": "normal", + "textColor": "#FF6B7280", + "lineHeight": 18 + }, + "type": "element" + }, + { + "id": "separator", + "elementType": "text", + "bindings": { + "text": "•" + }, + "layout": { + "width": { + "value": -2, + "unit": "dp" + } + }, + "style": { + "fontSize": 13, + "textColor": "#FF9CA3AF", + "lineHeight": 18 + }, + "type": "element" + }, + { + "id": "post-time", + "elementType": "text", + "bindings": { + "text": "{{postTime}}" + }, + "layout": { + "width": { + "value": -2, + "unit": "dp" + } + }, + "style": { + "fontSize": 13, + "fontWeight": "normal", + "textColor": "#FF9CA3AF", + "lineHeight": 18 + }, + "type": "element" + } + ], + "type": "container" + } + ], + "type": "container" + } + ], + "type": "container" + }, + { + "id": "post-text", + "elementType": "text", + "bindings": { + "text": "{{postText}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 15, + "fontWeight": "normal", + "textColor": "#FF374151", + "lineHeight": 22 + }, + "type": "element" + }, + { + "id": "post-image", + "elementType": "image", + "bindings": { + "url": "{{postImage}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 240, + "unit": "dp" + } + }, + "style": { + "borderRadius": 12 + }, + "type": "element" + }, + { + "id": "engagement-stats", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "arrangement": { + "spacing": 20, + "strategy": "spaced" + } + }, + "children": [ + { + "id": "likes-stat", + "elementType": "text", + "bindings": { + "text": "❤️ {{likes}}" + }, + "layout": { + "width": { + "value": -2, + "unit": "dp" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "medium", + "textColor": "#FF6B7280", + "lineHeight": 20 + }, + "type": "element" + }, + { + "id": "comments-stat", + "elementType": "text", + "bindings": { + "text": "💬 {{comments}}" + }, + "layout": { + "width": { + "value": -2, + "unit": "dp" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "medium", + "textColor": "#FF6B7280", + "lineHeight": 20 + }, + "type": "element" + }, + { + "id": "shares-stat", + "elementType": "text", + "bindings": { + "text": "🔄 {{shares}}" + }, + "layout": { + "width": { + "value": -2, + "unit": "dp" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "medium", + "textColor": "#FF6B7280", + "lineHeight": 20 + }, + "type": "element" + } + ], + "type": "container" + }, + { + "id": "divider", + "elementType": "divider", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 1, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FFE5E7EB" + }, + "type": "element" + }, + { + "id": "comment-button", + "elementType": "button", + "bindings": { + "text": "{{commentButtonText}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 44, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF3B82F6", + "borderRadius": 10, + "textColor": "#FFFFFFFF", + "fontSize": 15, + "fontWeight": "medium", + "shadowRadius": 8, + "shadowColor": "#FF3B82F6", + "shadowOpacity": 0.3, + "shadowOffsetY": 2, + "lineHeight": 21 + }, + "type": "element" + } + ], + "type": "container" + } + ], + "type": "container" + }, + "styleClasses": [] +} diff --git a/flutter-sample/assets/test-configs/test-069-settings-row.json b/flutter-sample/assets/test-configs/test-069-settings-row.json new file mode 100644 index 0000000..bce37c2 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-069-settings-row.json @@ -0,0 +1,580 @@ +{ + "theme": { + "id": "settings-row", + "defaultStyle": { + "textColor": "#FF1F2937", + "fontSize": 16, + "fontFamily": "System", + "lineHeight": 22 + } + }, + "variables": { + "section1Title": "ACCOUNT", + "setting1Label": "Profile Settings", + "setting1Icon": "👤", + "setting2Label": "Security & Privacy", + "setting2Icon": "🔒", + "section2Title": "PREFERENCES", + "setting3Label": "Notifications", + "setting3Icon": "🔔", + "setting4Label": "Language & Region", + "setting4Icon": "🌍", + "section3Title": "SUPPORT", + "setting5Label": "Help Center", + "setting5Icon": "❓", + "setting6Label": "Contact Support", + "setting6Icon": "💬" + }, + "root": { + "id": "settings-container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 0 + } + }, + "style": { + "backgroundColor": "#FFF9FAFB" + }, + "children": [ + { + "id": "section-1-header", + "elementType": "text", + "bindings": { + "text": "{{section1Title}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "padding": { + "left": 20, + "right": 20, + "top": 24, + "bottom": 12 + } + }, + "style": { + "fontSize": 13, + "fontWeight": "bold", + "textColor": "#FF6B7280", + "lineHeight": 18 + }, + "type": "element" + }, + { + "id": "section-1-container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 0 + } + }, + "style": { + "backgroundColor": "#FFFFFFFF" + }, + "children": [ + { + "id": "settings-row-1", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 60, + "unit": "dp" + }, + "padding": { + "left": 20, + "right": 20 + }, + "arrangement": { + "strategy": "space_between" + } + }, + "children": [ + { + "id": "row-1-left", + "containerType": "horizontal", + "layout": { + "width": { + "value": -2, + "unit": "dp" + }, + "arrangement": { + "spacing": 12, + "strategy": "spaced" + } + }, + "children": [ + { + "id": "icon-1", + "elementType": "text", + "bindings": { + "text": "{{setting1Icon}}" + }, + "layout": { + "width": { + "value": -2, + "unit": "dp" + } + }, + "style": { + "fontSize": 20, + "lineHeight": 28 + }, + "type": "element" + }, + { + "id": "label-1", + "elementType": "text", + "bindings": { + "text": "{{setting1Label}}" + }, + "layout": { + "width": { + "value": -2, + "unit": "dp" + } + }, + "style": { + "fontSize": 17, + "fontWeight": "normal", + "textColor": "#FF111827", + "lineHeight": 24 + }, + "type": "element" + } + ], + "type": "container" + }, + { + "id": "arrow-1", + "elementType": "text", + "bindings": { + "text": "›" + }, + "layout": { + "width": { + "value": -2, + "unit": "dp" + } + }, + "style": { + "fontSize": 24, + "fontWeight": "normal", + "textColor": "#FF9CA3AF", + "lineHeight": 34 + }, + "type": "element" + } + ], + "type": "container" + }, + { + "id": "divider-1", + "elementType": "divider", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 1, + "unit": "dp" + }, + "padding": { + "left": 52 + } + }, + "style": { + "backgroundColor": "#FFE5E7EB" + }, + "type": "element" + }, + { + "id": "settings-row-2", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 60, + "unit": "dp" + }, + "padding": { + "left": 20, + "right": 20 + }, + "arrangement": { + "strategy": "space_between" + } + }, + "children": [ + { + "id": "row-2-left", + "containerType": "horizontal", + "layout": { + "width": { + "value": -2, + "unit": "dp" + }, + "arrangement": { + "spacing": 12, + "strategy": "spaced" + } + }, + "children": [ + { + "id": "icon-2", + "elementType": "text", + "bindings": { + "text": "{{setting2Icon}}" + }, + "layout": { + "width": { + "value": -2, + "unit": "dp" + } + }, + "style": { + "fontSize": 20, + "lineHeight": 28 + }, + "type": "element" + }, + { + "id": "label-2", + "elementType": "text", + "bindings": { + "text": "{{setting2Label}}" + }, + "layout": { + "width": { + "value": -2, + "unit": "dp" + } + }, + "style": { + "fontSize": 17, + "fontWeight": "normal", + "textColor": "#FF111827", + "lineHeight": 24 + }, + "type": "element" + } + ], + "type": "container" + }, + { + "id": "arrow-2", + "elementType": "text", + "bindings": { + "text": "›" + }, + "layout": { + "width": { + "value": -2, + "unit": "dp" + } + }, + "style": { + "fontSize": 24, + "fontWeight": "normal", + "textColor": "#FF9CA3AF", + "lineHeight": 34 + }, + "type": "element" + } + ], + "type": "container" + } + ], + "type": "container" + }, + { + "id": "section-2-header", + "elementType": "text", + "bindings": { + "text": "{{section2Title}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "padding": { + "left": 20, + "right": 20, + "top": 24, + "bottom": 12 + } + }, + "style": { + "fontSize": 13, + "fontWeight": "bold", + "textColor": "#FF6B7280", + "lineHeight": 18 + }, + "type": "element" + }, + { + "id": "section-2-container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 0 + } + }, + "style": { + "backgroundColor": "#FFFFFFFF" + }, + "children": [ + { + "id": "settings-row-3", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 60, + "unit": "dp" + }, + "padding": { + "left": 20, + "right": 20 + }, + "arrangement": { + "strategy": "space_between" + } + }, + "children": [ + { + "id": "row-3-left", + "containerType": "horizontal", + "layout": { + "width": { + "value": -2, + "unit": "dp" + }, + "arrangement": { + "spacing": 12, + "strategy": "spaced" + } + }, + "children": [ + { + "id": "icon-3", + "elementType": "text", + "bindings": { + "text": "{{setting3Icon}}" + }, + "layout": { + "width": { + "value": -2, + "unit": "dp" + } + }, + "style": { + "fontSize": 20, + "lineHeight": 28 + }, + "type": "element" + }, + { + "id": "label-3", + "elementType": "text", + "bindings": { + "text": "{{setting3Label}}" + }, + "layout": { + "width": { + "value": -2, + "unit": "dp" + } + }, + "style": { + "fontSize": 17, + "fontWeight": "normal", + "textColor": "#FF111827", + "lineHeight": 24 + }, + "type": "element" + } + ], + "type": "container" + }, + { + "id": "arrow-3", + "elementType": "text", + "bindings": { + "text": "›" + }, + "layout": { + "width": { + "value": -2, + "unit": "dp" + } + }, + "style": { + "fontSize": 24, + "fontWeight": "normal", + "textColor": "#FF9CA3AF", + "lineHeight": 34 + }, + "type": "element" + } + ], + "type": "container" + }, + { + "id": "divider-2", + "elementType": "divider", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 1, + "unit": "dp" + }, + "padding": { + "left": 52 + } + }, + "style": { + "backgroundColor": "#FFE5E7EB" + }, + "type": "element" + }, + { + "id": "settings-row-4", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 60, + "unit": "dp" + }, + "padding": { + "left": 20, + "right": 20 + }, + "arrangement": { + "strategy": "space_between" + } + }, + "children": [ + { + "id": "row-4-left", + "containerType": "horizontal", + "layout": { + "width": { + "value": -2, + "unit": "dp" + }, + "arrangement": { + "spacing": 12, + "strategy": "spaced" + } + }, + "children": [ + { + "id": "icon-4", + "elementType": "text", + "bindings": { + "text": "{{setting4Icon}}" + }, + "layout": { + "width": { + "value": -2, + "unit": "dp" + } + }, + "style": { + "fontSize": 20, + "lineHeight": 28 + }, + "type": "element" + }, + { + "id": "label-4", + "elementType": "text", + "bindings": { + "text": "{{setting4Label}}" + }, + "layout": { + "width": { + "value": -2, + "unit": "dp" + } + }, + "style": { + "fontSize": 17, + "fontWeight": "normal", + "textColor": "#FF111827", + "lineHeight": 24 + }, + "type": "element" + } + ], + "type": "container" + }, + { + "id": "arrow-4", + "elementType": "text", + "bindings": { + "text": "›" + }, + "layout": { + "width": { + "value": -2, + "unit": "dp" + } + }, + "style": { + "fontSize": 24, + "fontWeight": "normal", + "textColor": "#FF9CA3AF", + "lineHeight": 34 + }, + "type": "element" + } + ], + "type": "container" + } + ], + "type": "container" + } + ], + "type": "container" + }, + "styleClasses": [] +} diff --git a/flutter-sample/assets/test-configs/test-070-feature-showcase.json b/flutter-sample/assets/test-configs/test-070-feature-showcase.json new file mode 100644 index 0000000..0e39e95 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-070-feature-showcase.json @@ -0,0 +1,365 @@ +{ + "theme": { + "id": "feature-showcase", + "defaultStyle": { + "textColor": "#FF1F2937", + "fontSize": 16, + "fontFamily": "System", + "lineHeight": 22 + } + }, + "variables": { + "iconUrl": "https://yavuzceliker.github.io/sample-images/image-950.jpg", + "featureTitle": "Advanced Analytics", + "featureSubtitle": "Real-time Insights", + "featureDescription": "Get deep insights into your data with our powerful analytics engine. Track metrics, visualize trends, and make informed decisions with real-time reporting and customizable dashboards.", + "benefit1": "📊 Real-time Data Processing", + "benefit2": "📈 Custom Dashboards", + "benefit3": "🎯 Predictive Analytics", + "benefit4": "🔔 Smart Alerts", + "ctaButton": "Explore Features" + }, + "root": { + "id": "feature-container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 24 + } + }, + "style": { + "background": { + "type": "linear_gradient", + "colors": [ + "#FFFAFAFA", + "#FFF3F4F6" + ], + "angle": 180 + } + }, + "children": [ + { + "id": "feature-card", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 24 + }, + "arrangement": { + "spacing": 20, + "strategy": "spaced" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 20, + "shadowRadius": 16, + "shadowColor": "#FF000000", + "shadowOpacity": 0.12, + "shadowOffsetY": 6 + }, + "children": [ + { + "id": "icon-container", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "arrangement": { + "strategy": "center" + } + }, + "children": [ + { + "id": "feature-icon", + "elementType": "image", + "bindings": { + "url": "{{iconUrl}}" + }, + "layout": { + "width": { + "value": 96, + "unit": "dp" + }, + "height": { + "value": 96, + "unit": "dp" + } + }, + "style": { + "borderRadius": 20, + "shadowRadius": 12, + "shadowColor": "#FF3B82F6", + "shadowOpacity": 0.3, + "shadowOffsetY": 4 + }, + "type": "element" + } + ], + "type": "container" + }, + { + "id": "title-section", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "arrangement": { + "spacing": 8, + "strategy": "spaced" + } + }, + "children": [ + { + "id": "feature-title", + "elementType": "text", + "bindings": { + "text": "{{featureTitle}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 32, + "fontWeight": "bold", + "textColor": "#FF111827", + "textAlign": "center", + "lineHeight": 45 + }, + "type": "element" + }, + { + "id": "feature-subtitle", + "elementType": "text", + "bindings": { + "text": "{{featureSubtitle}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 16, + "fontWeight": "medium", + "textColor": "#FF3B82F6", + "textAlign": "center", + "lineHeight": 22 + }, + "type": "element" + } + ], + "type": "container" + }, + { + "id": "divider", + "elementType": "divider", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 2, + "unit": "dp" + } + }, + "style": { + "background": { + "type": "linear_gradient", + "colors": [ + "#FF3B82F6", + "#FF8B5CF6" + ], + "angle": 90 + } + }, + "type": "element" + }, + { + "id": "feature-description", + "elementType": "text", + "bindings": { + "text": "{{featureDescription}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 16, + "fontWeight": "normal", + "textColor": "#FF6B7280", + "lineHeight": 26, + "textAlign": "center" + }, + "type": "element" + }, + { + "id": "benefits-list", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "padding": { + "left": 12, + "right": 12 + }, + "arrangement": { + "spacing": 12, + "strategy": "spaced" + } + }, + "children": [ + { + "id": "benefit-1", + "elementType": "text", + "bindings": { + "text": "{{benefit1}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 15, + "fontWeight": "medium", + "textColor": "#FF374151", + "lineHeight": 22 + }, + "type": "element" + }, + { + "id": "benefit-2", + "elementType": "text", + "bindings": { + "text": "{{benefit2}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 15, + "fontWeight": "medium", + "textColor": "#FF374151", + "lineHeight": 22 + }, + "type": "element" + }, + { + "id": "benefit-3", + "elementType": "text", + "bindings": { + "text": "{{benefit3}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 15, + "fontWeight": "medium", + "textColor": "#FF374151", + "lineHeight": 22 + }, + "type": "element" + }, + { + "id": "benefit-4", + "elementType": "text", + "bindings": { + "text": "{{benefit4}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 15, + "fontWeight": "medium", + "textColor": "#FF374151", + "lineHeight": 22 + }, + "type": "element" + } + ], + "type": "container" + }, + { + "id": "cta-button", + "elementType": "button", + "bindings": { + "text": "{{ctaButton}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 56, + "unit": "dp" + } + }, + "style": { + "background": { + "type": "linear_gradient", + "colors": [ + "#FF3B82F6", + "#FF8B5CF6" + ], + "angle": 90 + }, + "borderRadius": 16, + "textColor": "#FFFFFFFF", + "fontSize": 18, + "fontWeight": "bold", + "shadowRadius": 16, + "shadowColor": "#FF3B82F6", + "shadowOpacity": 0.4, + "shadowOffsetY": 6, + "lineHeight": 25 + }, + "type": "element" + } + ], + "type": "container" + } + ], + "type": "container" + }, + "styleClasses": [] +} diff --git a/flutter-sample/assets/test-configs/test-071-text-colors.json b/flutter-sample/assets/test-configs/test-071-text-colors.json new file mode 100644 index 0000000..a9db7b1 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-071-text-colors.json @@ -0,0 +1,258 @@ +{ + "theme": { + "id": "text-colors-theme", + "defaultStyle": { + "fontSize": 16, + "fontWeight": "normal", + "lineHeight": 22 + } + }, + "root": { + "type": "container", + "id": "text-colors-root", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 12, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#F5F5F5" + }, + "children": [ + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "Text Color Variations" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 24, + "fontWeight": "bold", + "textColor": "#000000", + "textAlign": "center", + "lineHeight": 34 + } + }, + { + "type": "element", + "id": "black-text", + "elementType": "text", + "bindings": { + "text": "Black text (#000000)" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "textColor": "#000000", + "fontSize": 18, + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "red-text", + "elementType": "text", + "bindings": { + "text": "Red text (#FF0000)" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "textColor": "#FF0000", + "fontSize": 18, + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "green-text", + "elementType": "text", + "bindings": { + "text": "Green text (#00AA00)" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "textColor": "#00AA00", + "fontSize": 18, + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "blue-text", + "elementType": "text", + "bindings": { + "text": "Blue text (#0066FF)" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "textColor": "#0066FF", + "fontSize": 18, + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "purple-text", + "elementType": "text", + "bindings": { + "text": "Purple text (#9933FF)" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "textColor": "#9933FF", + "fontSize": 18, + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "alpha-80-text", + "elementType": "text", + "bindings": { + "text": "Red with 80% opacity (#FF0000CC)" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "textColor": "#FF0000CC", + "fontSize": 18, + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "alpha-50-text", + "elementType": "text", + "bindings": { + "text": "Blue with 50% opacity (#0066FF80)" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "textColor": "#0066FF80", + "fontSize": 18, + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "alpha-20-text", + "elementType": "text", + "bindings": { + "text": "Black with 20% opacity (#00000033)" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "textColor": "#00000033", + "fontSize": 18, + "lineHeight": 25 + } + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-072-font-sizes.json b/flutter-sample/assets/test-configs/test-072-font-sizes.json new file mode 100644 index 0000000..31f76b4 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-072-font-sizes.json @@ -0,0 +1,200 @@ +{ + "theme": { + "id": "font-sizes-theme", + "defaultStyle": { + "textColor": "#333333", + "fontWeight": "normal" + } + }, + "root": { + "type": "container", + "id": "font-sizes-root", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 20 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 16, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF" + }, + "children": [ + { + "type": "element", + "id": "size-12", + "elementType": "text", + "bindings": { + "text": "Font size 12sp - Small caption text" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 12, + "lineHeight": 17 + } + }, + { + "type": "element", + "id": "size-14", + "elementType": "text", + "bindings": { + "text": "Font size 14sp - Body text small" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "lineHeight": 20 + } + }, + { + "type": "element", + "id": "size-16", + "elementType": "text", + "bindings": { + "text": "Font size 16sp - Body text default" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 16, + "lineHeight": 22 + } + }, + { + "type": "element", + "id": "size-20", + "elementType": "text", + "bindings": { + "text": "Font size 20sp - Subheading" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 20, + "lineHeight": 28 + } + }, + { + "type": "element", + "id": "size-24", + "elementType": "text", + "bindings": { + "text": "Font size 24sp - Heading" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 24, + "lineHeight": 34 + } + }, + { + "type": "element", + "id": "size-32", + "elementType": "text", + "bindings": { + "text": "Font size 32sp - Large Title" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 32, + "lineHeight": 45 + } + }, + { + "type": "element", + "id": "size-48", + "elementType": "text", + "bindings": { + "text": "Font size 48sp - Display" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 48, + "lineHeight": 67 + } + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-073-font-weights.json b/flutter-sample/assets/test-configs/test-073-font-weights.json new file mode 100644 index 0000000..c47cf47 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-073-font-weights.json @@ -0,0 +1,275 @@ +{ + "theme": { + "id": "font-weights-theme", + "defaultStyle": { + "fontSize": 18, + "textColor": "#222222", + "lineHeight": 25 + } + }, + "root": { + "type": "container", + "id": "font-weights-root", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 20 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 20, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#F8F8F8" + }, + "children": [ + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "Font Weight Variations" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 28, + "fontWeight": "bold", + "textAlign": "center", + "textColor": "#000000", + "lineHeight": 39 + } + }, + { + "type": "element", + "id": "weight-normal", + "elementType": "text", + "bindings": { + "text": "Normal weight - Regular readable text for body content" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontWeight": "normal" + } + }, + { + "type": "element", + "id": "weight-medium", + "elementType": "text", + "bindings": { + "text": "Medium weight - Slightly emphasized text for labels" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontWeight": "medium" + } + }, + { + "type": "element", + "id": "weight-semibold", + "elementType": "text", + "bindings": { + "text": "Bold weight (semibold) - Strong emphasis for subheadings" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontWeight": "bold" + } + }, + { + "type": "element", + "id": "weight-bold", + "elementType": "text", + "bindings": { + "text": "Bold weight - Maximum emphasis for headings" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontWeight": "bold" + } + }, + { + "type": "container", + "id": "comparison-container", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "arrangement": { + "strategy": "space_between" + } + }, + "children": [ + { + "type": "element", + "id": "comp-normal", + "elementType": "text", + "bindings": { + "text": "Normal" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontWeight": "normal", + "fontSize": 16, + "lineHeight": 22 + } + }, + { + "type": "element", + "id": "comp-medium", + "elementType": "text", + "bindings": { + "text": "Medium" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontWeight": "medium", + "fontSize": 16, + "lineHeight": 22 + } + }, + { + "type": "element", + "id": "comp-semibold", + "elementType": "text", + "bindings": { + "text": "Semibold" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontWeight": "bold", + "fontSize": 16, + "lineHeight": 22 + } + }, + { + "type": "element", + "id": "comp-bold", + "elementType": "text", + "bindings": { + "text": "Bold" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontWeight": "bold", + "fontSize": 16, + "lineHeight": 22 + } + } + ] + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-074-text-alignment.json b/flutter-sample/assets/test-configs/test-074-text-alignment.json new file mode 100644 index 0000000..a33b705 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-074-text-alignment.json @@ -0,0 +1,332 @@ +{ + "theme": { + "id": "text-alignment-theme", + "defaultStyle": { + "fontSize": 16, + "textColor": "#000000", + "fontWeight": "normal", + "lineHeight": 22 + } + }, + "root": { + "type": "container", + "id": "text-alignment-root", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 24, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#E8F4F8" + }, + "children": [ + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "Text Alignment Examples" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 24, + "fontWeight": "bold", + "textAlign": "center", + "backgroundColor": "#2196F3", + "textColor": "#FFFFFF", + "borderRadius": 8, + "lineHeight": 34 + } + }, + { + "type": "container", + "id": "align-start-container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 12 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 4, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 8 + }, + "children": [ + { + "type": "element", + "id": "align-start-label", + "elementType": "text", + "bindings": { + "text": "Start Alignment:" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#666666", + "lineHeight": 20 + } + }, + { + "type": "element", + "id": "align-start-text", + "elementType": "text", + "bindings": { + "text": "This text is aligned to the start (left in LTR)" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "textAlign": "start", + "fontSize": 16, + "lineHeight": 22 + } + } + ] + }, + { + "type": "container", + "id": "align-center-container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 12 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 4, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 8 + }, + "children": [ + { + "type": "element", + "id": "align-center-label", + "elementType": "text", + "bindings": { + "text": "Center Alignment:" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#666666", + "lineHeight": 20 + } + }, + { + "type": "element", + "id": "align-center-text", + "elementType": "text", + "bindings": { + "text": "This text is centered" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "textAlign": "center", + "fontSize": 16, + "lineHeight": 22 + } + } + ] + }, + { + "type": "container", + "id": "align-end-container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 12 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 4, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 8 + }, + "children": [ + { + "type": "element", + "id": "align-end-label", + "elementType": "text", + "bindings": { + "text": "End Alignment:" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#666666", + "lineHeight": 20 + } + }, + { + "type": "element", + "id": "align-end-text", + "elementType": "text", + "bindings": { + "text": "This text is aligned to the end (right in LTR)" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "textAlign": "end", + "fontSize": 16, + "lineHeight": 22 + } + } + ] + }, + { + "type": "element", + "id": "multiline-demo", + "elementType": "text", + "bindings": { + "text": "Center aligned multiline text. This demonstrates how text alignment works with longer content that wraps across multiple lines." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "textAlign": "center", + "fontSize": 14, + "lineHeight": 21, + "backgroundColor": "#FFF3E0", + "borderRadius": 8 + } + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-075-text-decoration.json b/flutter-sample/assets/test-configs/test-075-text-decoration.json new file mode 100644 index 0000000..e0c77e2 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-075-text-decoration.json @@ -0,0 +1,412 @@ +{ + "theme": { + "id": "text-decoration-theme", + "defaultStyle": { + "fontSize": 18, + "textColor": "#333333", + "fontWeight": "normal", + "lineHeight": 25 + } + }, + "root": { + "type": "container", + "id": "text-decoration-root", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 20 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 20, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FAFAFA" + }, + "children": [ + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "Text Decoration Examples" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 26, + "fontWeight": "bold", + "textAlign": "center", + "textColor": "#1976D2", + "lineHeight": 36 + } + }, + { + "type": "container", + "id": "none-container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 8, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 8, + "borderWidth": 1, + "borderColor": "#E0E0E0" + }, + "children": [ + { + "type": "element", + "id": "none-label", + "elementType": "text", + "bindings": { + "text": "None (default):" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "medium", + "textColor": "#757575", + "lineHeight": 20 + } + }, + { + "type": "element", + "id": "none-text", + "elementType": "text", + "bindings": { + "text": "This is regular text without any decoration" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "textDecoration": "none" + } + } + ] + }, + { + "type": "container", + "id": "underline-container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 8, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 8, + "borderWidth": 1, + "borderColor": "#E0E0E0" + }, + "children": [ + { + "type": "element", + "id": "underline-label", + "elementType": "text", + "bindings": { + "text": "Underline:" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "medium", + "textColor": "#757575", + "lineHeight": 20 + } + }, + { + "type": "element", + "id": "underline-text", + "elementType": "text", + "bindings": { + "text": "This text has an underline decoration" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "textDecoration": "underline", + "textColor": "#2196F3" + } + } + ] + }, + { + "type": "container", + "id": "line-through-container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 8, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 8, + "borderWidth": 1, + "borderColor": "#E0E0E0" + }, + "children": [ + { + "type": "element", + "id": "line-through-label", + "elementType": "text", + "bindings": { + "text": "Line-through (strikethrough):" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "medium", + "textColor": "#757575", + "lineHeight": 20 + } + }, + { + "type": "element", + "id": "line-through-text", + "elementType": "text", + "bindings": { + "text": "This text has line-through decoration" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "textDecoration": "strikethrough", + "textColor": "#F44336" + } + } + ] + }, + { + "type": "container", + "id": "practical-examples", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "strategy": "space_between" + } + }, + "style": { + "backgroundColor": "#FFF9C4", + "borderRadius": 8 + }, + "children": [ + { + "type": "element", + "id": "price-old", + "elementType": "text", + "bindings": { + "text": "$99.99" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "textDecoration": "strikethrough", + "textColor": "#999999", + "fontSize": 16, + "lineHeight": 22 + } + }, + { + "type": "element", + "id": "price-new", + "elementType": "text", + "bindings": { + "text": "$49.99" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "textColor": "#D32F2F", + "fontSize": 24, + "fontWeight": "bold", + "lineHeight": 34 + } + }, + { + "type": "element", + "id": "link-text", + "elementType": "text", + "bindings": { + "text": "View Details" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "textDecoration": "underline", + "textColor": "#1976D2", + "fontSize": 14, + "lineHeight": 20 + } + } + ] + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-076-line-height.json b/flutter-sample/assets/test-configs/test-076-line-height.json new file mode 100644 index 0000000..d99dd1d --- /dev/null +++ b/flutter-sample/assets/test-configs/test-076-line-height.json @@ -0,0 +1,379 @@ +{ + "theme": { + "id": "line-height-theme", + "defaultStyle": { + "fontSize": 16, + "textColor": "#222222", + "fontWeight": "normal", + "lineHeight": 22 + } + }, + "root": { + "type": "container", + "id": "line-height-root", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 16, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#F5F5F5" + }, + "children": [ + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "Line Height Variations" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 24, + "fontWeight": "bold", + "textAlign": "center", + "textColor": "#000000", + "lineHeight": 34 + } + }, + { + "type": "container", + "id": "lh-1-0-container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 12 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 4, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 8 + }, + "children": [ + { + "type": "element", + "id": "lh-1-0-label", + "elementType": "text", + "bindings": { + "text": "Line Height 1.0 (tight):" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#666666", + "lineHeight": 20 + } + }, + { + "type": "element", + "id": "lh-1-0-text", + "elementType": "text", + "bindings": { + "text": "This paragraph demonstrates tight line spacing with lineHeight 1.0. Lines are very close together with minimal vertical space between them. This can make longer text harder to read but is useful for compact designs." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "lineHeight": 14, + "fontSize": 14 + } + } + ] + }, + { + "type": "container", + "id": "lh-1-2-container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 12 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 4, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 8 + }, + "children": [ + { + "type": "element", + "id": "lh-1-2-label", + "elementType": "text", + "bindings": { + "text": "Line Height 1.2 (compact):" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#666666", + "lineHeight": 20 + } + }, + { + "type": "element", + "id": "lh-1-2-text", + "elementType": "text", + "bindings": { + "text": "This paragraph uses lineHeight 1.2 for a compact but readable spacing. It provides slightly more breathing room than 1.0 while still maintaining a dense layout. Good for captions and secondary text." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "lineHeight": 17, + "fontSize": 14 + } + } + ] + }, + { + "type": "container", + "id": "lh-1-5-container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 12 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 4, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 8 + }, + "children": [ + { + "type": "element", + "id": "lh-1-5-label", + "elementType": "text", + "bindings": { + "text": "Line Height 1.5 (comfortable - recommended):" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#666666", + "lineHeight": 20 + } + }, + { + "type": "element", + "id": "lh-1-5-text", + "elementType": "text", + "bindings": { + "text": "This paragraph demonstrates the most readable line spacing with lineHeight 1.5. This is the recommended value for body text as it provides excellent readability with comfortable spacing between lines. Perfect for longer content." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "lineHeight": 21, + "fontSize": 14 + } + } + ] + }, + { + "type": "container", + "id": "lh-2-0-container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 12 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 4, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 8 + }, + "children": [ + { + "type": "element", + "id": "lh-2-0-label", + "elementType": "text", + "bindings": { + "text": "Line Height 2.0 (loose):" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#666666", + "lineHeight": 20 + } + }, + { + "type": "element", + "id": "lh-2-0-text", + "elementType": "text", + "bindings": { + "text": "This paragraph has very loose line spacing with lineHeight 2.0. There is significant vertical space between each line. This can be useful for emphasis or artistic layouts but may feel too spacious for normal reading." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "lineHeight": 28, + "fontSize": 14 + } + } + ] + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-077-font-families.json b/flutter-sample/assets/test-configs/test-077-font-families.json new file mode 100644 index 0000000..8271d7e --- /dev/null +++ b/flutter-sample/assets/test-configs/test-077-font-families.json @@ -0,0 +1,465 @@ +{ + "theme": { + "id": "font-families-theme", + "defaultStyle": { + "fontSize": 16, + "textColor": "#333333", + "fontWeight": "normal", + "lineHeight": 22 + } + }, + "root": { + "type": "container", + "id": "font-families-root", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 20 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 20, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FAFAFA" + }, + "children": [ + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "Font Family Variations" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 26, + "fontWeight": "bold", + "textAlign": "center", + "textColor": "#1565C0", + "lineHeight": 36 + } + }, + { + "type": "container", + "id": "system-container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 8, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 8, + "borderWidth": 1, + "borderColor": "#E0E0E0" + }, + "children": [ + { + "type": "element", + "id": "system-label", + "elementType": "text", + "bindings": { + "text": "System (default):" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "medium", + "textColor": "#757575", + "lineHeight": 20 + } + }, + { + "type": "element", + "id": "system-text", + "elementType": "text", + "bindings": { + "text": "This text uses the system default font. The quick brown fox jumps over the lazy dog. 0123456789" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontFamily": "system", + "fontSize": 16, + "lineHeight": 22 + } + } + ] + }, + { + "type": "container", + "id": "monospace-container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 8, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 8, + "borderWidth": 1, + "borderColor": "#E0E0E0" + }, + "children": [ + { + "type": "element", + "id": "monospace-label", + "elementType": "text", + "bindings": { + "text": "Monospace (fixed-width):" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "medium", + "textColor": "#757575", + "lineHeight": 20 + } + }, + { + "type": "element", + "id": "monospace-text", + "elementType": "text", + "bindings": { + "text": "Monospace font for code. function() { return true; } 0123456789" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontFamily": "monospace", + "fontSize": 14, + "backgroundColor": "#F5F5F5", + "lineHeight": 20 + } + } + ] + }, + { + "type": "container", + "id": "serif-container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 8, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 8, + "borderWidth": 1, + "borderColor": "#E0E0E0" + }, + "children": [ + { + "type": "element", + "id": "serif-label", + "elementType": "text", + "bindings": { + "text": "Serif (traditional):" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "medium", + "textColor": "#757575", + "lineHeight": 20 + } + }, + { + "type": "element", + "id": "serif-text", + "elementType": "text", + "bindings": { + "text": "This text uses a serif font for a more traditional, elegant appearance. Perfect for long-form reading content." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontFamily": "serif", + "fontSize": 16, + "lineHeight": 26 + } + } + ] + }, + { + "type": "container", + "id": "comparison", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 12, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#E3F2FD", + "borderRadius": 8 + }, + "children": [ + { + "type": "element", + "id": "comp-title", + "elementType": "text", + "bindings": { + "text": "Quick Comparison:" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#1565C0", + "lineHeight": 20 + } + }, + { + "type": "element", + "id": "comp-system", + "elementType": "text", + "bindings": { + "text": "System: ABC abc 123" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontFamily": "system", + "fontSize": 18, + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "comp-monospace", + "elementType": "text", + "bindings": { + "text": "Monospace: ABC abc 123" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontFamily": "monospace", + "fontSize": 18, + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "comp-serif", + "elementType": "text", + "bindings": { + "text": "Serif: ABC abc 123" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontFamily": "serif", + "fontSize": 18, + "lineHeight": 25 + } + } + ] + }, + { + "type": "element", + "id": "code-example", + "elementType": "text", + "bindings": { + "text": "if (condition) {\n console.log('Monospace is ideal for code');\n return value;\n}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontFamily": "monospace", + "fontSize": 14, + "backgroundColor": "#263238", + "textColor": "#AAAAAA", + "borderRadius": 8, + "lineHeight": 20 + } + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-078-border-radius.json b/flutter-sample/assets/test-configs/test-078-border-radius.json new file mode 100644 index 0000000..9d6833d --- /dev/null +++ b/flutter-sample/assets/test-configs/test-078-border-radius.json @@ -0,0 +1,439 @@ +{ + "theme": { + "id": "border-radius-theme", + "defaultStyle": { + "fontSize": 16, + "textColor": "#FFFFFF", + "fontWeight": "medium", + "lineHeight": 22 + } + }, + "root": { + "type": "container", + "id": "border-radius-root", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 20 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 16, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#37474F" + }, + "children": [ + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "Border Radius Examples" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 28, + "fontWeight": "bold", + "textAlign": "center", + "lineHeight": 39 + } + }, + { + "type": "container", + "id": "radius-0", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 80, + "unit": "dp" + }, + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#F44336", + "borderRadius": 0 + }, + "children": [ + { + "type": "element", + "id": "radius-0-text", + "elementType": "text", + "bindings": { + "text": "borderRadius: 0 (sharp corners)" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 16, + "fontWeight": "bold", + "lineHeight": 22 + } + } + ] + }, + { + "type": "container", + "id": "radius-4", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 80, + "unit": "dp" + }, + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#E91E63", + "borderRadius": 4 + }, + "children": [ + { + "type": "element", + "id": "radius-4-text", + "elementType": "text", + "bindings": { + "text": "borderRadius: 4 (subtle rounding)" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 16, + "fontWeight": "bold", + "lineHeight": 22 + } + } + ] + }, + { + "type": "container", + "id": "radius-8", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 80, + "unit": "dp" + }, + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#9C27B0", + "borderRadius": 8 + }, + "children": [ + { + "type": "element", + "id": "radius-8-text", + "elementType": "text", + "bindings": { + "text": "borderRadius: 8 (standard rounding)" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 16, + "fontWeight": "bold", + "lineHeight": 22 + } + } + ] + }, + { + "type": "container", + "id": "radius-16", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 80, + "unit": "dp" + }, + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#3F51B5", + "borderRadius": 16 + }, + "children": [ + { + "type": "element", + "id": "radius-16-text", + "elementType": "text", + "bindings": { + "text": "borderRadius: 16 (rounded)" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 16, + "fontWeight": "bold", + "lineHeight": 22 + } + } + ] + }, + { + "type": "container", + "id": "radius-24", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 80, + "unit": "dp" + }, + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#2196F3", + "borderRadius": 24 + }, + "children": [ + { + "type": "element", + "id": "radius-24-text", + "elementType": "text", + "bindings": { + "text": "borderRadius: 24 (very rounded)" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 16, + "fontWeight": "bold", + "lineHeight": 22 + } + } + ] + }, + { + "type": "container", + "id": "radius-50", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 80, + "unit": "dp" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "strategy": "center" + } + }, + "style": { + "backgroundColor": "#00BCD4", + "borderRadius": 50 + }, + "children": [ + { + "type": "element", + "id": "radius-50-text", + "elementType": "text", + "bindings": { + "text": "borderRadius: 50 (pill shape)" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 16, + "fontWeight": "bold", + "lineHeight": 22 + } + } + ] + }, + { + "type": "container", + "id": "practical-examples", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "arrangement": { + "strategy": "space_evenly" + } + }, + "children": [ + { + "type": "container", + "id": "circle", + "containerType": "box", + "layout": { + "width": { + "value": 60, + "unit": "dp" + }, + "height": { + "value": 60, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#4CAF50", + "borderRadius": 30 + }, + "children": [] + }, + { + "type": "container", + "id": "rounded-square", + "containerType": "box", + "layout": { + "width": { + "value": 60, + "unit": "dp" + }, + "height": { + "value": 60, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF9800", + "borderRadius": 12 + }, + "children": [] + }, + { + "type": "container", + "id": "sharp-square", + "containerType": "box", + "layout": { + "width": { + "value": 60, + "unit": "dp" + }, + "height": { + "value": 60, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#795548", + "borderRadius": 0 + }, + "children": [] + } + ] + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-079-border-width-color.json b/flutter-sample/assets/test-configs/test-079-border-width-color.json new file mode 100644 index 0000000..fcba489 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-079-border-width-color.json @@ -0,0 +1,519 @@ +{ + "theme": { + "id": "border-theme", + "defaultStyle": { + "fontSize": 16, + "textColor": "#333333", + "fontWeight": "medium", + "lineHeight": 22 + } + }, + "root": { + "type": "container", + "id": "border-root", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 20 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 20, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FAFAFA" + }, + "children": [ + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "Border Width & Color Variations" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 24, + "fontWeight": "bold", + "textAlign": "center", + "textColor": "#000000", + "lineHeight": 34 + } + }, + { + "type": "container", + "id": "border-1", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 70, + "unit": "dp" + }, + "padding": { + "all": 12 + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderWidth": 1, + "borderColor": "#2196F3", + "borderRadius": 8 + }, + "children": [ + { + "type": "element", + "id": "border-1-text", + "elementType": "text", + "bindings": { + "text": "1dp border - Blue (#2196F3)" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 16, + "lineHeight": 22 + } + }, + { + "type": "element", + "id": "border-1-desc", + "elementType": "text", + "bindings": { + "text": "Thin border for subtle separation" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 12, + "textColor": "#757575", + "lineHeight": 17 + } + } + ] + }, + { + "type": "container", + "id": "border-2", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 70, + "unit": "dp" + }, + "padding": { + "all": 12 + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderWidth": 2, + "borderColor": "#4CAF50", + "borderRadius": 8 + }, + "children": [ + { + "type": "element", + "id": "border-2-text", + "elementType": "text", + "bindings": { + "text": "2dp border - Green (#4CAF50)" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 16, + "lineHeight": 22 + } + }, + { + "type": "element", + "id": "border-2-desc", + "elementType": "text", + "bindings": { + "text": "Standard border for cards and containers" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 12, + "textColor": "#757575", + "lineHeight": 17 + } + } + ] + }, + { + "type": "container", + "id": "border-4", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 70, + "unit": "dp" + }, + "padding": { + "all": 12 + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderWidth": 4, + "borderColor": "#FF9800", + "borderRadius": 8 + }, + "children": [ + { + "type": "element", + "id": "border-4-text", + "elementType": "text", + "bindings": { + "text": "4dp border - Orange (#FF9800)" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 16, + "lineHeight": 22 + } + }, + { + "type": "element", + "id": "border-4-desc", + "elementType": "text", + "bindings": { + "text": "Prominent border for emphasis" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 12, + "textColor": "#757575", + "lineHeight": 17 + } + } + ] + }, + { + "type": "container", + "id": "border-8", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 70, + "unit": "dp" + }, + "padding": { + "all": 12 + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderWidth": 8, + "borderColor": "#F44336", + "borderRadius": 8 + }, + "children": [ + { + "type": "element", + "id": "border-8-text", + "elementType": "text", + "bindings": { + "text": "8dp border - Red (#F44336)" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 16, + "lineHeight": 22 + } + }, + { + "type": "element", + "id": "border-8-desc", + "elementType": "text", + "bindings": { + "text": "Thick border for maximum attention" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 12, + "textColor": "#757575", + "lineHeight": 17 + } + } + ] + }, + { + "type": "container", + "id": "color-variations", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 60, + "unit": "dp" + }, + "arrangement": { + "strategy": "space_evenly" + } + }, + "children": [ + { + "type": "container", + "id": "black-border", + "containerType": "box", + "layout": { + "width": { + "value": 50, + "unit": "dp" + }, + "height": { + "value": 50, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderWidth": 2, + "borderColor": "#000000", + "borderRadius": 8 + }, + "children": [] + }, + { + "type": "container", + "id": "purple-border", + "containerType": "box", + "layout": { + "width": { + "value": 50, + "unit": "dp" + }, + "height": { + "value": 50, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderWidth": 2, + "borderColor": "#9C27B0", + "borderRadius": 8 + }, + "children": [] + }, + { + "type": "container", + "id": "cyan-border", + "containerType": "box", + "layout": { + "width": { + "value": 50, + "unit": "dp" + }, + "height": { + "value": 50, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderWidth": 2, + "borderColor": "#00BCD4", + "borderRadius": 8 + }, + "children": [] + }, + { + "type": "container", + "id": "pink-border", + "containerType": "box", + "layout": { + "width": { + "value": 50, + "unit": "dp" + }, + "height": { + "value": 50, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderWidth": 2, + "borderColor": "#E91E63", + "borderRadius": 8 + }, + "children": [] + } + ] + }, + { + "type": "container", + "id": "combined-example", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#FFF9C4", + "borderWidth": 3, + "borderColor": "#F57F17", + "borderRadius": 12 + }, + "children": [ + { + "type": "element", + "id": "combined-text", + "elementType": "text", + "bindings": { + "text": "Combined: 3dp border + 12dp radius + Yellow background" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#F57F17", + "lineHeight": 20 + } + } + ] + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-080-shadows-light.json b/flutter-sample/assets/test-configs/test-080-shadows-light.json new file mode 100644 index 0000000..eac7e32 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-080-shadows-light.json @@ -0,0 +1,477 @@ +{ + "theme": { + "id": "shadows-light-theme", + "defaultStyle": { + "fontSize": 16, + "textColor": "#333333", + "fontWeight": "normal", + "lineHeight": 22 + } + }, + "root": { + "type": "container", + "id": "shadows-light-root", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 24 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 24, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#F5F5F5" + }, + "children": [ + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "Light Shadow Examples" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 28, + "fontWeight": "bold", + "textAlign": "center", + "textColor": "#000000", + "lineHeight": 39 + } + }, + { + "type": "container", + "id": "shadow-1", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "dp" + }, + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 8, + "shadowColor": "#000000", + "shadowOpacity": 0.1, + "shadowRadius": 2, + "shadowOffsetX": 0, + "shadowOffsetY": 1 + }, + "children": [ + { + "type": "element", + "id": "shadow-1-title", + "elementType": "text", + "bindings": { + "text": "Subtle Elevation" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "shadow-1-desc", + "elementType": "text", + "bindings": { + "text": "Shadow: opacity 0.1, radius 2, offset (0, 1)" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#757575", + "lineHeight": 20 + } + } + ] + }, + { + "type": "container", + "id": "shadow-2", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "dp" + }, + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 8, + "shadowColor": "#000000", + "shadowOpacity": 0.15, + "shadowRadius": 4, + "shadowOffsetX": 0, + "shadowOffsetY": 2 + }, + "children": [ + { + "type": "element", + "id": "shadow-2-title", + "elementType": "text", + "bindings": { + "text": "Light Elevation" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "shadow-2-desc", + "elementType": "text", + "bindings": { + "text": "Shadow: opacity 0.15, radius 4, offset (0, 2)" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#757575", + "lineHeight": 20 + } + } + ] + }, + { + "type": "container", + "id": "shadow-3", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "dp" + }, + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 8, + "shadowColor": "#000000", + "shadowOpacity": 0.2, + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 3 + }, + "children": [ + { + "type": "element", + "id": "shadow-3-title", + "elementType": "text", + "bindings": { + "text": "Moderate Elevation" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "shadow-3-desc", + "elementType": "text", + "bindings": { + "text": "Shadow: opacity 0.2, radius 6, offset (0, 3)" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#757575", + "lineHeight": 20 + } + } + ] + }, + { + "type": "container", + "id": "colored-shadows", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 80, + "unit": "dp" + }, + "arrangement": { + "strategy": "space_evenly" + } + }, + "children": [ + { + "type": "container", + "id": "blue-shadow", + "containerType": "box", + "layout": { + "width": { + "value": 70, + "unit": "dp" + }, + "height": { + "value": 70, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#2196F3", + "borderRadius": 12, + "shadowColor": "#2196F3", + "shadowOpacity": 0.3, + "shadowRadius": 8, + "shadowOffsetX": 0, + "shadowOffsetY": 4 + }, + "children": [] + }, + { + "type": "container", + "id": "green-shadow", + "containerType": "box", + "layout": { + "width": { + "value": 70, + "unit": "dp" + }, + "height": { + "value": 70, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#4CAF50", + "borderRadius": 12, + "shadowColor": "#4CAF50", + "shadowOpacity": 0.3, + "shadowRadius": 8, + "shadowOffsetX": 0, + "shadowOffsetY": 4 + }, + "children": [] + }, + { + "type": "container", + "id": "purple-shadow", + "containerType": "box", + "layout": { + "width": { + "value": 70, + "unit": "dp" + }, + "height": { + "value": 70, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#9C27B0", + "borderRadius": 12, + "shadowColor": "#9C27B0", + "shadowOpacity": 0.3, + "shadowRadius": 8, + "shadowOffsetX": 0, + "shadowOffsetY": 4 + }, + "children": [] + } + ] + }, + { + "type": "container", + "id": "card-example", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 20 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 8, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 12, + "shadowColor": "#000000", + "shadowOpacity": 0.12, + "shadowRadius": 8, + "shadowOffsetX": 0, + "shadowOffsetY": 2 + }, + "children": [ + { + "type": "element", + "id": "card-title", + "elementType": "text", + "bindings": { + "text": "Card with Light Shadow" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 20, + "fontWeight": "bold", + "textColor": "#000000", + "lineHeight": 28 + } + }, + { + "type": "element", + "id": "card-body", + "elementType": "text", + "bindings": { + "text": "Light shadows are perfect for cards, buttons, and floating elements that need subtle depth without overwhelming the design." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "lineHeight": 21, + "textColor": "#666666" + } + } + ] + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-081-shadows-medium.json b/flutter-sample/assets/test-configs/test-081-shadows-medium.json new file mode 100644 index 0000000..08fa60b --- /dev/null +++ b/flutter-sample/assets/test-configs/test-081-shadows-medium.json @@ -0,0 +1,452 @@ +{ + "theme": { + "id": "shadows-medium-theme", + "defaultStyle": { + "fontSize": 16, + "textColor": "#333333", + "fontWeight": "normal", + "lineHeight": 22 + } + }, + "root": { + "type": "container", + "id": "shadows-medium-root", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 24 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 20, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#ECEFF1" + }, + "children": [ + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "Medium Shadow Examples" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 28, + "fontWeight": "bold", + "textAlign": "center", + "textColor": "#000000", + "lineHeight": 39 + } + }, + { + "type": "container", + "id": "shadow-4", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 110, + "unit": "dp" + }, + "padding": { + "all": 20 + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 12, + "shadowColor": "#000000", + "shadowOpacity": 0.25, + "shadowRadius": 8, + "shadowOffsetX": 0, + "shadowOffsetY": 4 + }, + "children": [ + { + "type": "element", + "id": "shadow-4-title", + "elementType": "text", + "bindings": { + "text": "Medium Elevation" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 20, + "fontWeight": "bold", + "lineHeight": 28 + } + }, + { + "type": "element", + "id": "shadow-4-desc", + "elementType": "text", + "bindings": { + "text": "Shadow: opacity 0.25, radius 8, offset (0, 4)" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#757575", + "lineHeight": 20 + } + } + ] + }, + { + "type": "container", + "id": "shadow-6", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 110, + "unit": "dp" + }, + "padding": { + "all": 20 + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 12, + "shadowColor": "#000000", + "shadowOpacity": 0.3, + "shadowRadius": 12, + "shadowOffsetX": 0, + "shadowOffsetY": 6 + }, + "children": [ + { + "type": "element", + "id": "shadow-6-title", + "elementType": "text", + "bindings": { + "text": "Raised Elevation" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 20, + "fontWeight": "bold", + "lineHeight": 28 + } + }, + { + "type": "element", + "id": "shadow-6-desc", + "elementType": "text", + "bindings": { + "text": "Shadow: opacity 0.3, radius 12, offset (0, 6)" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#757575", + "lineHeight": 20 + } + } + ] + }, + { + "type": "container", + "id": "shadow-8", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 110, + "unit": "dp" + }, + "padding": { + "all": 20 + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 12, + "shadowColor": "#000000", + "shadowOpacity": 0.35, + "shadowRadius": 16, + "shadowOffsetX": 0, + "shadowOffsetY": 8 + }, + "children": [ + { + "type": "element", + "id": "shadow-8-title", + "elementType": "text", + "bindings": { + "text": "High Elevation" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 20, + "fontWeight": "bold", + "lineHeight": 28 + } + }, + { + "type": "element", + "id": "shadow-8-desc", + "elementType": "text", + "bindings": { + "text": "Shadow: opacity 0.35, radius 16, offset (0, 8)" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#757575", + "lineHeight": 20 + } + } + ] + }, + { + "type": "container", + "id": "product-card", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 0 + }, + "arrangement": { + "strategy": "start" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 16, + "shadowColor": "#000000", + "shadowOpacity": 0.28, + "shadowRadius": 12, + "shadowOffsetX": 0, + "shadowOffsetY": 6 + }, + "children": [ + { + "type": "element", + "id": "product-image", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-85.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 150, + "unit": "dp" + } + }, + "style": { + "borderRadius": 16 + } + }, + { + "type": "container", + "id": "product-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 8, + "spacingUnit": "dp" + } + }, + "children": [ + { + "type": "element", + "id": "product-title", + "elementType": "text", + "bindings": { + "text": "Premium Product" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "textColor": "#000000", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "product-desc", + "elementType": "text", + "bindings": { + "text": "This card demonstrates medium shadows perfect for product displays and important content." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#666666", + "lineHeight": 20 + } + }, + { + "type": "element", + "id": "product-price", + "elementType": "text", + "bindings": { + "text": "$99.99" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 24, + "fontWeight": "bold", + "textColor": "#2196F3", + "lineHeight": 34 + } + } + ] + } + ] + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-082-shadows-heavy.json b/flutter-sample/assets/test-configs/test-082-shadows-heavy.json new file mode 100644 index 0000000..4cc3b46 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-082-shadows-heavy.json @@ -0,0 +1,507 @@ +{ + "theme": { + "id": "shadows-heavy-theme", + "defaultStyle": { + "fontSize": 16, + "textColor": "#FFFFFF", + "fontWeight": "normal", + "lineHeight": 22 + } + }, + "root": { + "type": "container", + "id": "shadows-heavy-root", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 24 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 24, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#263238" + }, + "children": [ + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "Heavy Shadow Examples" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 28, + "fontWeight": "bold", + "textAlign": "center", + "lineHeight": 39 + } + }, + { + "type": "container", + "id": "shadow-12", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "dp" + }, + "padding": { + "all": 20 + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 16, + "shadowColor": "#000000", + "shadowOpacity": 0.4, + "shadowRadius": 20, + "shadowOffsetX": 0, + "shadowOffsetY": 12 + }, + "children": [ + { + "type": "element", + "id": "shadow-12-title", + "elementType": "text", + "bindings": { + "text": "Heavy Shadow" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 20, + "fontWeight": "bold", + "textColor": "#000000", + "lineHeight": 28 + } + }, + { + "type": "element", + "id": "shadow-12-desc", + "elementType": "text", + "bindings": { + "text": "opacity 0.4, radius 20, offset (0, 12)" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#666666", + "lineHeight": 20 + } + } + ] + }, + { + "type": "container", + "id": "shadow-16", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "dp" + }, + "padding": { + "all": 20 + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 16, + "shadowColor": "#000000", + "shadowOpacity": 0.5, + "shadowRadius": 24, + "shadowOffsetX": 0, + "shadowOffsetY": 16 + }, + "children": [ + { + "type": "element", + "id": "shadow-16-title", + "elementType": "text", + "bindings": { + "text": "Dramatic Shadow" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 20, + "fontWeight": "bold", + "textColor": "#000000", + "lineHeight": 28 + } + }, + { + "type": "element", + "id": "shadow-16-desc", + "elementType": "text", + "bindings": { + "text": "opacity 0.5, radius 24, offset (0, 16)" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#666666", + "lineHeight": 20 + } + } + ] + }, + { + "type": "container", + "id": "shadow-24", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "dp" + }, + "padding": { + "all": 20 + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 16, + "shadowColor": "#000000", + "shadowOpacity": 0.6, + "shadowRadius": 32, + "shadowOffsetX": 0, + "shadowOffsetY": 24 + }, + "children": [ + { + "type": "element", + "id": "shadow-24-title", + "elementType": "text", + "bindings": { + "text": "Extreme Shadow" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 20, + "fontWeight": "bold", + "textColor": "#000000", + "lineHeight": 28 + } + }, + { + "type": "element", + "id": "shadow-24-desc", + "elementType": "text", + "bindings": { + "text": "opacity 0.6, radius 32, offset (0, 24)" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#666666", + "lineHeight": 20 + } + } + ] + }, + { + "type": "container", + "id": "modal-example", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 24 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 16, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 20, + "shadowColor": "#000000", + "shadowOpacity": 0.55, + "shadowRadius": 28, + "shadowOffsetX": 0, + "shadowOffsetY": 20 + }, + "children": [ + { + "type": "container", + "id": "modal-icon", + "containerType": "box", + "layout": { + "width": { + "value": 60, + "unit": "dp" + }, + "height": { + "value": 60, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF6F00", + "borderRadius": 30 + }, + "children": [] + }, + { + "type": "element", + "id": "modal-title", + "elementType": "text", + "bindings": { + "text": "Important Dialog" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 24, + "fontWeight": "bold", + "textAlign": "center", + "textColor": "#000000", + "lineHeight": 34 + } + }, + { + "type": "element", + "id": "modal-body", + "elementType": "text", + "bindings": { + "text": "Heavy shadows are ideal for modals, dialogs, and floating action buttons that need to clearly float above all other content." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "lineHeight": 21, + "textAlign": "center", + "textColor": "#666666" + } + }, + { + "type": "container", + "id": "modal-button", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 50, + "unit": "dp" + }, + "padding": { + "all": 12 + } + }, + "style": { + "backgroundColor": "#2196F3", + "borderRadius": 25 + }, + "children": [ + { + "type": "element", + "id": "button-text", + "elementType": "text", + "bindings": { + "text": "Understand" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 16, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "lineHeight": 22 + } + } + ] + } + ] + }, + { + "type": "container", + "id": "fab-example", + "containerType": "box", + "layout": { + "width": { + "value": 80, + "unit": "dp" + }, + "height": { + "value": 80, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF4081", + "borderRadius": 40, + "shadowColor": "#FF4081", + "shadowOpacity": 0.5, + "shadowRadius": 20, + "shadowOffsetX": 0, + "shadowOffsetY": 12 + }, + "children": [ + { + "type": "element", + "id": "fab-label", + "elementType": "text", + "bindings": { + "text": "FAB" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 20, + "fontWeight": "bold", + "lineHeight": 28 + } + } + ] + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-083-opacity-variations.json b/flutter-sample/assets/test-configs/test-083-opacity-variations.json new file mode 100644 index 0000000..d1127f5 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-083-opacity-variations.json @@ -0,0 +1,632 @@ +{ + "theme": { + "id": "opacity-theme", + "defaultStyle": { + "fontSize": 16, + "textColor": "#000000", + "fontWeight": "normal", + "lineHeight": 22 + } + }, + "root": { + "type": "container", + "id": "opacity-root", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 20 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 16, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#E0E0E0" + }, + "children": [ + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "Opacity Variations" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 28, + "fontWeight": "bold", + "textAlign": "center", + "lineHeight": 39 + } + }, + { + "type": "container", + "id": "opacity-100", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 80, + "unit": "dp" + }, + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#2196F3", + "borderRadius": 8, + "opacity": 1.0 + }, + "children": [ + { + "type": "element", + "id": "opacity-100-text", + "elementType": "text", + "bindings": { + "text": "Opacity: 1.0 (100% - Fully Opaque)" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 16, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "lineHeight": 22 + } + }, + { + "type": "element", + "id": "opacity-100-desc", + "elementType": "text", + "bindings": { + "text": "Completely solid and visible" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#FFFFFF", + "lineHeight": 20 + } + } + ] + }, + { + "type": "container", + "id": "opacity-80", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 80, + "unit": "dp" + }, + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#2196F3", + "borderRadius": 8, + "opacity": 0.8 + }, + "children": [ + { + "type": "element", + "id": "opacity-80-text", + "elementType": "text", + "bindings": { + "text": "Opacity: 0.8 (80%)" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 16, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "lineHeight": 22 + } + }, + { + "type": "element", + "id": "opacity-80-desc", + "elementType": "text", + "bindings": { + "text": "Slightly transparent, shows background" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#FFFFFF", + "lineHeight": 20 + } + } + ] + }, + { + "type": "container", + "id": "opacity-60", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 80, + "unit": "dp" + }, + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#2196F3", + "borderRadius": 8, + "opacity": 0.6 + }, + "children": [ + { + "type": "element", + "id": "opacity-60-text", + "elementType": "text", + "bindings": { + "text": "Opacity: 0.6 (60%)" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 16, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "lineHeight": 22 + } + }, + { + "type": "element", + "id": "opacity-60-desc", + "elementType": "text", + "bindings": { + "text": "Semi-transparent, background clearly visible" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#FFFFFF", + "lineHeight": 20 + } + } + ] + }, + { + "type": "container", + "id": "opacity-40", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 80, + "unit": "dp" + }, + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#2196F3", + "borderRadius": 8, + "opacity": 0.4 + }, + "children": [ + { + "type": "element", + "id": "opacity-40-text", + "elementType": "text", + "bindings": { + "text": "Opacity: 0.4 (40%)" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 16, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "lineHeight": 22 + } + }, + { + "type": "element", + "id": "opacity-40-desc", + "elementType": "text", + "bindings": { + "text": "Very transparent, mostly shows background" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#FFFFFF", + "lineHeight": 20 + } + } + ] + }, + { + "type": "container", + "id": "opacity-20", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 80, + "unit": "dp" + }, + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#2196F3", + "borderRadius": 8, + "opacity": 0.2 + }, + "children": [ + { + "type": "element", + "id": "opacity-20-text", + "elementType": "text", + "bindings": { + "text": "Opacity: 0.2 (20%)" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 16, + "fontWeight": "bold", + "textColor": "#000000", + "lineHeight": 22 + } + }, + { + "type": "element", + "id": "opacity-20-desc", + "elementType": "text", + "bindings": { + "text": "Barely visible, subtle hint of color" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#000000", + "lineHeight": 20 + } + } + ] + }, + { + "type": "container", + "id": "practical-overlay", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 150, + "unit": "dp" + } + }, + "children": [ + { + "type": "element", + "id": "overlay-image", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-86.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 150, + "unit": "dp" + } + }, + "style": { + "borderRadius": 12 + } + }, + { + "type": "container", + "id": "overlay-dark", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 150, + "unit": "dp" + }, + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#000000", + "opacity": 0.5, + "borderRadius": 12 + }, + "children": [] + }, + { + "type": "element", + "id": "overlay-text", + "elementType": "text", + "bindings": { + "text": "Opacity creates overlays" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "offset": { + "x": 0, + "y": 50 + } + }, + "style": { + "fontSize": 24, + "fontWeight": "bold", + "textAlign": "center", + "textColor": "#FFFFFF", + "lineHeight": 34 + } + } + ] + }, + { + "type": "container", + "id": "comparison", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 60, + "unit": "dp" + }, + "arrangement": { + "strategy": "space_evenly" + } + }, + "children": [ + { + "type": "container", + "id": "comp-1", + "containerType": "box", + "layout": { + "width": { + "value": 50, + "unit": "dp" + }, + "height": { + "value": 50, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#F44336", + "opacity": 1.0, + "borderRadius": 8 + }, + "children": [] + }, + { + "type": "container", + "id": "comp-2", + "containerType": "box", + "layout": { + "width": { + "value": 50, + "unit": "dp" + }, + "height": { + "value": 50, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#F44336", + "opacity": 0.7, + "borderRadius": 8 + }, + "children": [] + }, + { + "type": "container", + "id": "comp-3", + "containerType": "box", + "layout": { + "width": { + "value": 50, + "unit": "dp" + }, + "height": { + "value": 50, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#F44336", + "opacity": 0.4, + "borderRadius": 8 + }, + "children": [] + }, + { + "type": "container", + "id": "comp-4", + "containerType": "box", + "layout": { + "width": { + "value": 50, + "unit": "dp" + }, + "height": { + "value": 50, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#F44336", + "opacity": 0.1, + "borderRadius": 8 + }, + "children": [] + } + ] + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-084-combined-visual-styles.json b/flutter-sample/assets/test-configs/test-084-combined-visual-styles.json new file mode 100644 index 0000000..8c18c7a --- /dev/null +++ b/flutter-sample/assets/test-configs/test-084-combined-visual-styles.json @@ -0,0 +1,562 @@ +{ + "theme": { + "id": "combined-styles-theme", + "defaultStyle": { + "fontSize": 16, + "textColor": "#333333", + "fontWeight": "normal", + "lineHeight": 22 + } + }, + "root": { + "type": "container", + "id": "combined-styles-root", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 20 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 20, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FAFAFA" + }, + "children": [ + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "Combined Visual Styles" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 28, + "fontWeight": "bold", + "textAlign": "center", + "textColor": "#1976D2", + "lineHeight": 39 + } + }, + { + "type": "container", + "id": "card-1", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 20 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 12, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 16, + "borderWidth": 2, + "borderColor": "#2196F3", + "shadowColor": "#2196F3", + "shadowOpacity": 0.3, + "shadowRadius": 12, + "shadowOffsetX": 0, + "shadowOffsetY": 6 + }, + "children": [ + { + "type": "element", + "id": "card-1-title", + "elementType": "text", + "bindings": { + "text": "Card with Border + Shadow" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 20, + "fontWeight": "bold", + "textColor": "#2196F3", + "lineHeight": 28 + } + }, + { + "type": "element", + "id": "card-1-body", + "elementType": "text", + "bindings": { + "text": "This card combines border (2dp, blue), rounded corners (16dp), and a colored shadow for a lifted, emphasized appearance." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "lineHeight": 21, + "textColor": "#666666" + } + } + ] + }, + { + "type": "container", + "id": "card-2", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 20 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 12, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#4CAF50", + "borderRadius": 24, + "opacity": 0.95, + "shadowColor": "#000000", + "shadowOpacity": 0.25, + "shadowRadius": 10, + "shadowOffsetX": 0, + "shadowOffsetY": 4 + }, + "children": [ + { + "type": "element", + "id": "card-2-title", + "elementType": "text", + "bindings": { + "text": "Background + Opacity + Shadow" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 20, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "lineHeight": 28 + } + }, + { + "type": "element", + "id": "card-2-body", + "elementType": "text", + "bindings": { + "text": "Green background with 95% opacity and medium shadow creates depth while maintaining slight transparency." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "lineHeight": 21, + "textColor": "#FFFFFF" + } + } + ] + }, + { + "type": "container", + "id": "card-3", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 0 + }, + "arrangement": { + "strategy": "start" + } + }, + "style": { + "backgroundColor": "#FFF3E0", + "borderRadius": 20, + "borderWidth": 4, + "borderColor": "#FF6F00", + "shadowColor": "#FF6F00", + "shadowOpacity": 0.4, + "shadowRadius": 16, + "shadowOffsetX": 0, + "shadowOffsetY": 8 + }, + "children": [ + { + "type": "container", + "id": "card-3-header", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 60, + "unit": "dp" + }, + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#FF6F00", + "borderRadius": 16 + }, + "children": [ + { + "type": "element", + "id": "card-3-header-text", + "elementType": "text", + "bindings": { + "text": "Complex Styling" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 22, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "lineHeight": 31 + } + } + ] + }, + { + "type": "container", + "id": "card-3-body", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 20 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 8, + "spacingUnit": "dp" + } + }, + "children": [ + { + "type": "element", + "id": "card-3-text", + "elementType": "text", + "bindings": { + "text": "This card showcases multiple visual properties working together: thick colored border, light background, heavy shadow, and nested rounded corners." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "lineHeight": 21, + "textColor": "#333333" + } + } + ] + } + ] + }, + { + "type": "container", + "id": "button-group", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 56, + "unit": "dp" + }, + "arrangement": { + "strategy": "space_evenly" + } + }, + "children": [ + { + "type": "container", + "id": "button-1", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "dp" + }, + "height": { + "value": 48, + "unit": "dp" + }, + "padding": { + "all": 12 + } + }, + "style": { + "backgroundColor": "#E91E63", + "borderRadius": 24, + "borderWidth": 2, + "borderColor": "#FFFFFF", + "shadowColor": "#000000", + "shadowOpacity": 0.2, + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 3 + }, + "children": [ + { + "type": "element", + "id": "button-1-text", + "elementType": "text", + "bindings": { + "text": "Button 1" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "lineHeight": 20 + } + } + ] + }, + { + "type": "container", + "id": "button-2", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "dp" + }, + "height": { + "value": 48, + "unit": "dp" + }, + "padding": { + "all": 12 + } + }, + "style": { + "backgroundColor": "#9C27B0", + "borderRadius": 24, + "borderWidth": 2, + "borderColor": "#FFFFFF", + "shadowColor": "#000000", + "shadowOpacity": 0.2, + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 3 + }, + "children": [ + { + "type": "element", + "id": "button-2-text", + "elementType": "text", + "bindings": { + "text": "Button 2" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "lineHeight": 20 + } + } + ] + } + ] + }, + { + "type": "container", + "id": "badge", + "containerType": "box", + "layout": { + "width": { + "value": 80, + "unit": "dp" + }, + "height": { + "value": 80, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF5722", + "borderRadius": 40, + "borderWidth": 4, + "borderColor": "#FFFFFF", + "opacity": 0.95, + "shadowColor": "#FF5722", + "shadowOpacity": 0.5, + "shadowRadius": 12, + "shadowOffsetX": 0, + "shadowOffsetY": 6 + }, + "children": [ + { + "type": "element", + "id": "badge-text", + "elementType": "text", + "bindings": { + "text": "99+" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 24, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "lineHeight": 34 + } + } + ] + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-085-text-style-inheritance.json b/flutter-sample/assets/test-configs/test-085-text-style-inheritance.json new file mode 100644 index 0000000..a1f5f12 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-085-text-style-inheritance.json @@ -0,0 +1,483 @@ +{ + "theme": { + "id": "inheritance-theme", + "defaultStyle": { + "fontSize": 14, + "textColor": "#666666", + "fontWeight": "normal", + "lineHeight": 20 + } + }, + "root": { + "type": "container", + "id": "inheritance-root", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 20 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 20, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#F5F5F5", + "textColor": "#000000", + "fontSize": 16, + "lineHeight": 22 + }, + "children": [ + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "Text Style Inheritance Demo" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 26, + "fontWeight": "bold", + "textAlign": "center", + "lineHeight": 36 + } + }, + { + "type": "container", + "id": "level-1-container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 12, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#E3F2FD", + "borderRadius": 12, + "textColor": "#1565C0", + "fontSize": 18, + "fontWeight": "medium", + "lineHeight": 25 + }, + "children": [ + { + "type": "element", + "id": "level-1-text", + "elementType": "text", + "bindings": { + "text": "Level 1: Blue text (18sp, medium) - inherited from container" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + } + }, + { + "type": "container", + "id": "level-2-container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 12 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 8, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#BBDEFB", + "borderRadius": 8, + "fontSize": 16, + "lineHeight": 22 + }, + "children": [ + { + "type": "element", + "id": "level-2-text", + "elementType": "text", + "bindings": { + "text": "Level 2: Still blue, but smaller (16sp) - inherits color, overrides size" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + } + }, + { + "type": "container", + "id": "level-3-container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 8 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 4, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#90CAF9", + "borderRadius": 6, + "textColor": "#0D47A1", + "fontWeight": "bold" + }, + "children": [ + { + "type": "element", + "id": "level-3-text", + "elementType": "text", + "bindings": { + "text": "Level 3: Dark blue and bold - overrides color and weight" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + } + } + ] + } + ] + } + ] + }, + { + "type": "container", + "id": "inheritance-demo-2", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 12, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FFEBEE", + "borderRadius": 12, + "textColor": "#C62828", + "fontSize": 16, + "lineHeight": 26 + }, + "children": [ + { + "type": "element", + "id": "demo-2-title", + "elementType": "text", + "bindings": { + "text": "Red Theme Section" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 20, + "fontWeight": "bold", + "lineHeight": 28 + } + }, + { + "type": "element", + "id": "demo-2-body", + "elementType": "text", + "bindings": { + "text": "All text in this section inherits red color and 1.6 line height from the parent container. This demonstrates how text properties cascade down the tree." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + } + }, + { + "type": "element", + "id": "demo-2-override", + "elementType": "text", + "bindings": { + "text": "This text overrides the inherited color to green." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "textColor": "#2E7D32" + } + } + ] + }, + { + "type": "container", + "id": "mixed-inheritance", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 12, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FFF3E0", + "borderRadius": 12, + "textColor": "#E65100", + "fontFamily": "serif" + }, + "children": [ + { + "type": "element", + "id": "mixed-title", + "elementType": "text", + "bindings": { + "text": "Font Family Inheritance" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 20, + "fontWeight": "bold", + "lineHeight": 28 + } + }, + { + "type": "element", + "id": "mixed-serif", + "elementType": "text", + "bindings": { + "text": "This text inherits the serif font family from the container." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + } + }, + { + "type": "element", + "id": "mixed-monospace", + "elementType": "text", + "bindings": { + "text": "This text overrides to use monospace: console.log('Hello');" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontFamily": "monospace", + "fontSize": 14, + "lineHeight": 20 + } + } + ] + }, + { + "type": "container", + "id": "visual-not-inherited", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 12, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#E8F5E9", + "borderRadius": 12, + "borderWidth": 2, + "borderColor": "#2E7D32", + "shadowColor": "#000000", + "shadowOpacity": 0.2, + "shadowRadius": 8, + "shadowOffsetX": 0, + "shadowOffsetY": 4 + }, + "children": [ + { + "type": "element", + "id": "not-inherited-note", + "elementType": "text", + "bindings": { + "text": "Note: Visual properties (backgroundColor, borderRadius, shadows) do NOT inherit. Each child must define its own visual styling." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "medium", + "textColor": "#2E7D32", + "lineHeight": 20 + } + } + ] + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-086-style-class-usage.json b/flutter-sample/assets/test-configs/test-086-style-class-usage.json new file mode 100644 index 0000000..52d49a5 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-086-style-class-usage.json @@ -0,0 +1,648 @@ +{ + "theme": { + "id": "style-class-theme", + "defaultStyle": { + "fontSize": 14, + "textColor": "#333333", + "fontWeight": "normal", + "lineHeight": 20 + } + }, + "styleClasses": [ + { + "id": "primary-button", + "style": { + "backgroundColor": "#2196F3", + "textColor": "#FFFFFF", + "fontSize": 16, + "fontWeight": "bold", + "borderRadius": 24, + "shadowColor": "#2196F3", + "shadowOpacity": 0.3, + "shadowRadius": 8, + "shadowOffsetX": 0, + "shadowOffsetY": 4, + "lineHeight": 22 + } + }, + { + "id": "secondary-button", + "style": { + "backgroundColor": "#FFFFFF", + "textColor": "#2196F3", + "fontSize": 16, + "fontWeight": "bold", + "borderRadius": 24, + "borderWidth": 2, + "borderColor": "#2196F3", + "lineHeight": 22 + } + }, + { + "id": "card-style", + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 12, + "shadowColor": "#000000", + "shadowOpacity": 0.15, + "shadowRadius": 8, + "shadowOffsetX": 0, + "shadowOffsetY": 2 + } + }, + { + "id": "heading-text", + "style": { + "fontSize": 24, + "fontWeight": "bold", + "textColor": "#000000", + "lineHeight": 34 + } + }, + { + "id": "body-text", + "style": { + "fontSize": 14, + "lineHeight": 21, + "textColor": "#666666" + } + }, + { + "id": "success-badge", + "style": { + "backgroundColor": "#4CAF50", + "textColor": "#FFFFFF", + "fontSize": 12, + "fontWeight": "bold", + "borderRadius": 12, + "lineHeight": 17 + } + }, + { + "id": "error-badge", + "style": { + "backgroundColor": "#F44336", + "textColor": "#FFFFFF", + "fontSize": 12, + "fontWeight": "bold", + "borderRadius": 12, + "lineHeight": 17 + } + } + ], + "root": { + "type": "container", + "id": "style-class-root", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 20 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 20, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#F5F5F5" + }, + "children": [ + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "Style Class Usage Demo" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "styleClass": "heading-text" + }, + { + "type": "element", + "id": "description", + "elementType": "text", + "bindings": { + "text": "This example demonstrates how styleClasses enable reusable styling across multiple components." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "styleClass": "body-text" + }, + { + "type": "container", + "id": "button-demo", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "arrangement": { + "strategy": "spaced", + "spacing": 12, + "spacingUnit": "dp" + } + }, + "children": [ + { + "type": "container", + "id": "primary-btn-1", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 48, + "unit": "dp" + }, + "padding": { + "all": 12 + } + }, + "styleClass": "primary-button", + "children": [ + { + "type": "element", + "id": "primary-btn-1-text", + "elementType": "text", + "bindings": { + "text": "Primary Button 1" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + } + } + ] + }, + { + "type": "container", + "id": "primary-btn-2", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 48, + "unit": "dp" + }, + "padding": { + "all": 12 + } + }, + "styleClass": "primary-button", + "children": [ + { + "type": "element", + "id": "primary-btn-2-text", + "elementType": "text", + "bindings": { + "text": "Primary Button 2" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + } + } + ] + }, + { + "type": "container", + "id": "secondary-btn", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 48, + "unit": "dp" + }, + "padding": { + "all": 12 + } + }, + "styleClass": "secondary-button", + "children": [ + { + "type": "element", + "id": "secondary-btn-text", + "elementType": "text", + "bindings": { + "text": "Secondary Button" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + } + } + ] + } + ] + }, + { + "type": "container", + "id": "card-1", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 8, + "spacingUnit": "dp" + } + }, + "styleClass": "card-style", + "children": [ + { + "type": "element", + "id": "card-1-title", + "elementType": "text", + "bindings": { + "text": "Card Using Style Class" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "styleClass": "heading-text", + "style": { + "fontSize": 18, + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "card-1-body", + "elementType": "text", + "bindings": { + "text": "This card uses the 'card-style' class for consistent appearance across the app." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "styleClass": "body-text" + } + ] + }, + { + "type": "container", + "id": "card-2", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 8, + "spacingUnit": "dp" + } + }, + "styleClass": "card-style", + "children": [ + { + "type": "element", + "id": "card-2-title", + "elementType": "text", + "bindings": { + "text": "Another Card" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "styleClass": "heading-text", + "style": { + "fontSize": 18, + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "card-2-body", + "elementType": "text", + "bindings": { + "text": "Same styling, different content. Style classes make this easy!" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "styleClass": "body-text" + } + ] + }, + { + "type": "container", + "id": "badges", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "arrangement": { + "strategy": "space_evenly" + } + }, + "children": [ + { + "type": "container", + "id": "success-1", + "containerType": "box", + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "horizontal": 12, + "vertical": 6 + } + }, + "styleClass": "success-badge", + "children": [ + { + "type": "element", + "id": "success-1-text", + "elementType": "text", + "bindings": { + "text": "ACTIVE" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + } + } + ] + }, + { + "type": "container", + "id": "error-1", + "containerType": "box", + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "horizontal": 12, + "vertical": 6 + } + }, + "styleClass": "error-badge", + "children": [ + { + "type": "element", + "id": "error-1-text", + "elementType": "text", + "bindings": { + "text": "ERROR" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + } + } + ] + }, + { + "type": "container", + "id": "success-2", + "containerType": "box", + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "horizontal": 12, + "vertical": 6 + } + }, + "styleClass": "success-badge", + "children": [ + { + "type": "element", + "id": "success-2-text", + "elementType": "text", + "bindings": { + "text": "VERIFIED" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + } + } + ] + } + ] + }, + { + "type": "container", + "id": "override-example", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 48, + "unit": "dp" + }, + "padding": { + "all": 12 + } + }, + "styleClass": "primary-button", + "style": { + "backgroundColor": "#9C27B0", + "shadowColor": "#9C27B0" + }, + "children": [ + { + "type": "element", + "id": "override-text", + "elementType": "text", + "bindings": { + "text": "Primary Style + Purple Override" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + } + } + ] + } + ] + }, + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-087-inline-vs-inherited.json b/flutter-sample/assets/test-configs/test-087-inline-vs-inherited.json new file mode 100644 index 0000000..b83c404 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-087-inline-vs-inherited.json @@ -0,0 +1,627 @@ +{ + "theme": { + "id": "override-theme", + "defaultStyle": { + "fontSize": 14, + "textColor": "#999999", + "fontWeight": "normal", + "lineHeight": 20 + } + }, + "styleClasses": [ + { + "id": "base-container", + "style": { + "backgroundColor": "#E3F2FD", + "borderRadius": 12, + "textColor": "#1976D2", + "fontSize": 16, + "lineHeight": 22 + } + } + ], + "root": { + "type": "container", + "id": "override-root", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 20 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 20, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FAFAFA" + }, + "children": [ + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "Inline Styles Override Inheritance" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 26, + "fontWeight": "bold", + "textAlign": "center", + "textColor": "#000000", + "lineHeight": 36 + } + }, + { + "type": "element", + "id": "explanation", + "elementType": "text", + "bindings": { + "text": "Style Resolution Order: 1. Theme Default → 2. Style Class → 3. Inline Style → 4. Parent Inherited" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textAlign": "center", + "textColor": "#666666", + "lineHeight": 21 + } + }, + { + "type": "container", + "id": "example-1", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 12, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 12, + "borderWidth": 1, + "borderColor": "#E0E0E0", + "textColor": "#2196F3", + "fontSize": 18, + "lineHeight": 25 + }, + "children": [ + { + "type": "element", + "id": "ex1-label", + "elementType": "text", + "bindings": { + "text": "Example 1: Theme → Inherited → Inline" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontWeight": "bold", + "textColor": "#000000" + } + }, + { + "type": "element", + "id": "ex1-inherited", + "elementType": "text", + "bindings": { + "text": "This text is blue (18sp) from parent container." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + } + }, + { + "type": "element", + "id": "ex1-override-color", + "elementType": "text", + "bindings": { + "text": "This text overrides color to red (inline), keeps 18sp." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "textColor": "#F44336" + } + }, + { + "type": "element", + "id": "ex1-override-both", + "elementType": "text", + "bindings": { + "text": "This text overrides both: green, 14sp." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "textColor": "#4CAF50", + "fontSize": 14, + "lineHeight": 20 + } + } + ] + }, + { + "type": "container", + "id": "example-2", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 12, + "spacingUnit": "dp" + } + }, + "styleClass": "base-container", + "children": [ + { + "type": "element", + "id": "ex2-label", + "elementType": "text", + "bindings": { + "text": "Example 2: Style Class + Override" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontWeight": "bold", + "textColor": "#000000" + } + }, + { + "type": "element", + "id": "ex2-class", + "elementType": "text", + "bindings": { + "text": "Inherits from styleClass: blue text, 16sp." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + } + }, + { + "type": "element", + "id": "ex2-override", + "elementType": "text", + "bindings": { + "text": "Inline style overrides: purple text, bold, 20sp." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "textColor": "#9C27B0", + "fontSize": 20, + "fontWeight": "bold", + "lineHeight": 28 + } + } + ] + }, + { + "type": "container", + "id": "example-3", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 12, + "spacingUnit": "dp" + } + }, + "styleClass": "base-container", + "style": { + "backgroundColor": "#FFF3E0", + "textColor": "#E65100", + "fontSize": 14, + "lineHeight": 20 + }, + "children": [ + { + "type": "element", + "id": "ex3-label", + "elementType": "text", + "bindings": { + "text": "Example 3: Style Class + Container Inline + Child Override" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontWeight": "bold", + "textColor": "#000000" + } + }, + { + "type": "element", + "id": "ex3-inherited", + "elementType": "text", + "bindings": { + "text": "Container overrides styleClass: orange text, 14sp (not blue 16sp)." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + } + }, + { + "type": "element", + "id": "ex3-child-override", + "elementType": "text", + "bindings": { + "text": "Child further overrides: cyan, 18sp." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "textColor": "#00BCD4", + "fontSize": 18, + "lineHeight": 25 + } + } + ] + }, + { + "type": "container", + "id": "nested-demo", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 8, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#E8F5E9", + "borderRadius": 12, + "textColor": "#2E7D32", + "fontSize": 16, + "fontWeight": "medium", + "lineHeight": 22 + }, + "children": [ + { + "type": "element", + "id": "nested-level-1", + "elementType": "text", + "bindings": { + "text": "Level 1: Green, 16sp, medium (from container)" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + } + }, + { + "type": "container", + "id": "nested-container-2", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 12 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 4, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#C8E6C9", + "borderRadius": 8, + "fontSize": 14, + "lineHeight": 20 + }, + "children": [ + { + "type": "element", + "id": "nested-level-2", + "elementType": "text", + "bindings": { + "text": "Level 2: Still green & medium, but 14sp (size overridden)" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + } + }, + { + "type": "element", + "id": "nested-level-2-override", + "elementType": "text", + "bindings": { + "text": "Level 2 with inline: Dark green, bold, 12sp" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "textColor": "#1B5E20", + "fontWeight": "bold", + "fontSize": 12, + "lineHeight": 17 + } + } + ] + } + ] + }, + { + "type": "container", + "id": "summary", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 8, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FFF9C4", + "borderRadius": 12 + }, + "children": [ + { + "type": "element", + "id": "summary-title", + "elementType": "text", + "bindings": { + "text": "Key Takeaway:" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "textColor": "#F57F17", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "summary-body", + "elementType": "text", + "bindings": { + "text": "Inline styles always win! They override inherited styles from parents and styleClasses. Use inline styles sparingly for specific overrides." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "lineHeight": 21, + "textColor": "#333333" + } + } + ] + } + ] + }, + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-088-theme-default-styles.json b/flutter-sample/assets/test-configs/test-088-theme-default-styles.json new file mode 100644 index 0000000..f14c533 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-088-theme-default-styles.json @@ -0,0 +1,567 @@ +{ + "theme": { + "id": "app-theme", + "defaultStyle": { + "fontSize": 16, + "textColor": "#37474F", + "fontWeight": "normal", + "lineHeight": 24, + "fontFamily": "system" + } + }, + "root": { + "type": "container", + "id": "theme-root", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 20 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 20, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#ECEFF1" + }, + "children": [ + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "Theme Default Styles Demo" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 28, + "fontWeight": "bold", + "textColor": "#000000", + "textAlign": "center", + "lineHeight": 39 + } + }, + { + "type": "element", + "id": "description", + "elementType": "text", + "bindings": { + "text": "The theme's defaultStyle provides base styling for all text elements. Unless overridden, all text inherits: 16sp, dark gray color, 1.5 line height, and system font." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + } + }, + { + "type": "container", + "id": "section-1", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 12, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 12, + "shadowColor": "#000000", + "shadowOpacity": 0.1, + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 2 + }, + "children": [ + { + "type": "element", + "id": "section-1-title", + "elementType": "text", + "bindings": { + "text": "Section 1: Using Theme Defaults" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 20, + "fontWeight": "bold", + "lineHeight": 28 + } + }, + { + "type": "element", + "id": "paragraph-1", + "elementType": "text", + "bindings": { + "text": "This paragraph uses the theme defaults entirely. No inline styles or styleClasses applied. It shows 16sp text in dark gray with 1.5 line height for comfortable reading." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + } + }, + { + "type": "element", + "id": "paragraph-2", + "elementType": "text", + "bindings": { + "text": "Another paragraph with theme defaults. Notice the consistent typography throughout the application." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + } + } + ] + }, + { + "type": "container", + "id": "section-2", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 12, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 12, + "shadowColor": "#000000", + "shadowOpacity": 0.1, + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 2 + }, + "children": [ + { + "type": "element", + "id": "section-2-title", + "elementType": "text", + "bindings": { + "text": "Section 2: Selective Overrides" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 20, + "fontWeight": "bold", + "lineHeight": 28 + } + }, + { + "type": "element", + "id": "small-text", + "elementType": "text", + "bindings": { + "text": "This text overrides only fontSize to 12sp. Color, weight, lineHeight, and font stay from theme." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 12, + "lineHeight": 17 + } + }, + { + "type": "element", + "id": "large-text", + "elementType": "text", + "bindings": { + "text": "This text overrides fontSize to 20sp." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 20, + "lineHeight": 28 + } + }, + { + "type": "element", + "id": "colored-text", + "elementType": "text", + "bindings": { + "text": "This text overrides only color to blue. Size and other properties come from theme." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "textColor": "#2196F3" + } + }, + { + "type": "element", + "id": "bold-text", + "elementType": "text", + "bindings": { + "text": "This text overrides only weight to bold." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontWeight": "bold" + } + } + ] + }, + { + "type": "container", + "id": "section-3", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 12, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 12, + "shadowColor": "#000000", + "shadowOpacity": 0.1, + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 2, + "textColor": "#D32F2F", + "fontSize": 18, + "lineHeight": 25 + }, + "children": [ + { + "type": "element", + "id": "section-3-title", + "elementType": "text", + "bindings": { + "text": "Section 3: Container Override" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 20, + "fontWeight": "bold", + "lineHeight": 28 + } + }, + { + "type": "element", + "id": "inherited-red", + "elementType": "text", + "bindings": { + "text": "This container overrides theme defaults with red color and 18sp. All children inherit these instead of theme defaults." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + } + }, + { + "type": "element", + "id": "child-override", + "elementType": "text", + "bindings": { + "text": "But this child can still override: back to theme gray color." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "textColor": "#37474F" + } + } + ] + }, + { + "type": "container", + "id": "benefits", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 12, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#E8F5E9", + "borderRadius": 12 + }, + "children": [ + { + "type": "element", + "id": "benefits-title", + "elementType": "text", + "bindings": { + "text": "Benefits of Theme Default Styles:" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "textColor": "#2E7D32", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "benefit-1", + "elementType": "text", + "bindings": { + "text": "• Consistency: All text starts with the same baseline styling" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + } + }, + { + "type": "element", + "id": "benefit-2", + "elementType": "text", + "bindings": { + "text": "• Efficiency: Less code - only override what's different" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + } + }, + { + "type": "element", + "id": "benefit-3", + "elementType": "text", + "bindings": { + "text": "• Maintainability: Change theme defaults to update entire app" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + } + }, + { + "type": "element", + "id": "benefit-4", + "elementType": "text", + "bindings": { + "text": "• Readability: Cleaner JSON with fewer style properties" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + } + } + ] + } + ] + }, + "styleClasses": [], + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-089-styled-product-card.json b/flutter-sample/assets/test-configs/test-089-styled-product-card.json new file mode 100644 index 0000000..205bbce --- /dev/null +++ b/flutter-sample/assets/test-configs/test-089-styled-product-card.json @@ -0,0 +1,707 @@ +{ + "theme": { + "id": "product-theme", + "defaultStyle": { + "fontSize": 14, + "textColor": "#333333", + "fontWeight": "normal", + "lineHeight": 20 + } + }, + "styleClasses": [ + { + "id": "price-old", + "style": { + "fontSize": 16, + "textColor": "#999999", + "textDecoration": "line-through", + "lineHeight": 22 + } + }, + { + "id": "price-new", + "style": { + "fontSize": 28, + "textColor": "#F44336", + "fontWeight": "bold", + "lineHeight": 39 + } + }, + { + "id": "badge", + "style": { + "backgroundColor": "#FF5722", + "textColor": "#FFFFFF", + "fontSize": 12, + "fontWeight": "bold", + "borderRadius": 4, + "lineHeight": 17 + } + } + ], + "root": { + "type": "container", + "id": "product-card-root", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 20 + }, + "arrangement": { + "strategy": "center" + } + }, + "style": { + "backgroundColor": "#F5F5F5" + }, + "children": [ + { + "type": "container", + "id": "product-card", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 0 + }, + "arrangement": { + "strategy": "start" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 16, + "shadowColor": "#000000", + "shadowOpacity": 0.2, + "shadowRadius": 12, + "shadowOffsetX": 0, + "shadowOffsetY": 6 + }, + "children": [ + { + "type": "container", + "id": "image-container", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 200, + "unit": "dp" + } + }, + "children": [ + { + "type": "element", + "id": "product-image", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-87.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 200, + "unit": "dp" + } + }, + "style": { + "borderRadius": 16 + } + }, + { + "type": "container", + "id": "sale-badge", + "containerType": "box", + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "horizontal": 12, + "vertical": 6 + }, + "offset": { + "x": 16, + "y": 16 + } + }, + "styleClass": "badge", + "style": { + "shadowColor": "#FF5722", + "shadowOpacity": 0.5, + "shadowRadius": 8, + "shadowOffsetX": 0, + "shadowOffsetY": 4 + }, + "children": [ + { + "type": "element", + "id": "badge-text", + "elementType": "text", + "bindings": { + "text": "50% OFF" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + } + } + ] + }, + { + "type": "container", + "id": "favorite-button", + "containerType": "box", + "layout": { + "width": { + "value": 40, + "unit": "dp" + }, + "height": { + "value": 40, + "unit": "dp" + }, + "offset": { + "x": -56, + "y": 16 + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 20, + "shadowColor": "#000000", + "shadowOpacity": 0.15, + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 2 + }, + "children": [ + { + "type": "element", + "id": "heart-icon", + "elementType": "text", + "bindings": { + "text": "♥" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 20, + "textColor": "#E91E63", + "lineHeight": 28 + } + } + ] + } + ] + }, + { + "type": "container", + "id": "content", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 20 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 12, + "spacingUnit": "dp" + } + }, + "children": [ + { + "type": "element", + "id": "category", + "elementType": "text", + "bindings": { + "text": "ELECTRONICS" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 12, + "fontWeight": "bold", + "textColor": "#2196F3", + "textAlign": "left", + "lineHeight": 17 + } + }, + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "Premium Wireless Headphones" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 22, + "fontWeight": "bold", + "textColor": "#000000", + "lineHeight": 29 + } + }, + { + "type": "element", + "id": "description", + "elementType": "text", + "bindings": { + "text": "High-quality sound with active noise cancellation. Up to 30 hours of battery life." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#666666", + "lineHeight": 21 + } + }, + { + "type": "container", + "id": "rating-container", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "arrangement": { + "strategy": "start" + } + }, + "children": [ + { + "type": "element", + "id": "stars", + "elementType": "text", + "bindings": { + "text": "★★★★★" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 16, + "textColor": "#FFC107", + "lineHeight": 22 + } + }, + { + "type": "element", + "id": "rating-count", + "elementType": "text", + "bindings": { + "text": "(4.8 - 2,847 reviews)" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#999999", + "lineHeight": 20 + } + } + ] + }, + { + "type": "container", + "id": "price-row", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "arrangement": { + "strategy": "space_between" + } + }, + "children": [ + { + "type": "container", + "id": "prices", + "containerType": "horizontal", + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "arrangement": { + "strategy": "start" + } + }, + "children": [ + { + "type": "element", + "id": "price-new", + "elementType": "text", + "bindings": { + "text": "$149.99" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "styleClass": "price-new" + }, + { + "type": "element", + "id": "price-old", + "elementType": "text", + "bindings": { + "text": "$299.99" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "styleClass": "price-old" + } + ] + }, + { + "type": "container", + "id": "stock-badge", + "containerType": "box", + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "horizontal": 10, + "vertical": 4 + } + }, + "style": { + "backgroundColor": "#4CAF50", + "borderRadius": 12 + }, + "children": [ + { + "type": "element", + "id": "stock-text", + "elementType": "text", + "bindings": { + "text": "In Stock" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 12, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "lineHeight": 17 + } + } + ] + } + ] + }, + { + "type": "element", + "id": "divider", + "elementType": "divider", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 1, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#E0E0E0" + } + }, + { + "type": "container", + "id": "actions", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 56, + "unit": "dp" + }, + "arrangement": { + "strategy": "spaced", + "spacing": 12, + "spacingUnit": "dp" + } + }, + "children": [ + { + "type": "container", + "id": "add-cart-button", + "containerType": "box", + "layout": { + "width": { + "value": 47, + "unit": "percent" + }, + "height": { + "value": 56, + "unit": "dp" + }, + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#2196F3", + "borderRadius": 28, + "shadowColor": "#2196F3", + "shadowOpacity": 0.3, + "shadowRadius": 8, + "shadowOffsetX": 0, + "shadowOffsetY": 4 + }, + "children": [ + { + "type": "element", + "id": "cart-button-text", + "elementType": "text", + "bindings": { + "text": "Add to Cart" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 16, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "lineHeight": 22 + } + } + ] + }, + { + "type": "container", + "id": "buy-now-button", + "containerType": "box", + "layout": { + "width": { + "value": 47, + "unit": "percent" + }, + "height": { + "value": 56, + "unit": "dp" + }, + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 28, + "borderWidth": 2, + "borderColor": "#2196F3" + }, + "children": [ + { + "type": "element", + "id": "buy-button-text", + "elementType": "text", + "bindings": { + "text": "Buy Now" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 16, + "fontWeight": "bold", + "textColor": "#2196F3", + "lineHeight": 22 + } + } + ] + } + ] + } + ] + } + ] + } + ] + }, + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-090-styled-profile-card.json b/flutter-sample/assets/test-configs/test-090-styled-profile-card.json new file mode 100644 index 0000000..eb06b71 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-090-styled-profile-card.json @@ -0,0 +1,781 @@ +{ + "theme": { + "id": "profile-theme", + "defaultStyle": { + "fontSize": 14, + "textColor": "#333333", + "fontWeight": "normal", + "lineHeight": 20 + } + }, + "styleClasses": [ + { + "id": "stat-value", + "style": { + "fontSize": 24, + "fontWeight": "bold", + "textColor": "#000000", + "lineHeight": 34 + } + }, + { + "id": "stat-label", + "style": { + "fontSize": 12, + "textColor": "#999999", + "lineHeight": 17 + } + }, + { + "id": "action-button", + "style": { + "backgroundColor": "#2196F3", + "textColor": "#FFFFFF", + "fontSize": 14, + "fontWeight": "bold", + "borderRadius": 20, + "shadowColor": "#2196F3", + "shadowOpacity": 0.25, + "shadowRadius": 8, + "shadowOffsetX": 0, + "shadowOffsetY": 4, + "lineHeight": 20 + } + } + ], + "root": { + "type": "container", + "id": "profile-root", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 20 + }, + "arrangement": { + "strategy": "center" + } + }, + "style": { + "backgroundColor": "#F0F4F8" + }, + "children": [ + { + "type": "container", + "id": "profile-card", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 0 + }, + "arrangement": { + "strategy": "start" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 20, + "shadowColor": "#000000", + "shadowOpacity": 0.15, + "shadowRadius": 16, + "shadowOffsetX": 0, + "shadowOffsetY": 8 + }, + "children": [ + { + "type": "container", + "id": "header-background", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 150, + "unit": "dp" + } + }, + "children": [ + { + "type": "element", + "id": "gradient-bg", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-88.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 150, + "unit": "dp" + } + }, + "style": { + "borderRadius": 20, + "opacity": 0.8 + } + }, + { + "type": "container", + "id": "overlay", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 150, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#2196F3", + "opacity": 0.7, + "borderRadius": 20 + }, + "children": [] + }, + { + "type": "container", + "id": "verified-badge", + "containerType": "box", + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "horizontal": 12, + "vertical": 6 + }, + "offset": { + "x": -16, + "y": 16 + } + }, + "style": { + "backgroundColor": "#4CAF50", + "borderRadius": 12, + "shadowColor": "#4CAF50", + "shadowOpacity": 0.4, + "shadowRadius": 8, + "shadowOffsetX": 0, + "shadowOffsetY": 4 + }, + "children": [ + { + "type": "element", + "id": "verified-text", + "elementType": "text", + "bindings": { + "text": "✓ Verified" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 12, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "lineHeight": 17 + } + } + ] + } + ] + }, + { + "type": "container", + "id": "profile-content", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 24 + }, + "arrangement": { + "strategy": "spaced", + "spacing": 20, + "spacingUnit": "dp" + } + }, + "children": [ + { + "type": "container", + "id": "avatar-section", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "arrangement": { + "strategy": "center" + } + }, + "children": [ + { + "type": "container", + "id": "avatar-container", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "dp" + }, + "height": { + "value": 100, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#E3F2FD", + "borderRadius": 50, + "borderWidth": 4, + "borderColor": "#FFFFFF", + "shadowColor": "#000000", + "shadowOpacity": 0.2, + "shadowRadius": 12, + "shadowOffsetX": 0, + "shadowOffsetY": 6 + }, + "children": [ + { + "type": "element", + "id": "avatar-initials", + "elementType": "text", + "bindings": { + "text": "AJ" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 40, + "fontWeight": "bold", + "textColor": "#2196F3", + "lineHeight": 56 + } + } + ] + } + ] + }, + { + "type": "container", + "id": "name-section", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "arrangement": { + "strategy": "center" + } + }, + "children": [ + { + "type": "element", + "id": "name", + "elementType": "text", + "bindings": { + "text": "Alex Johnson" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 26, + "fontWeight": "bold", + "textAlign": "center", + "textColor": "#000000", + "lineHeight": 36 + } + }, + { + "type": "element", + "id": "username", + "elementType": "text", + "bindings": { + "text": "@alexj" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 16, + "textAlign": "center", + "textColor": "#2196F3", + "lineHeight": 22 + } + } + ] + }, + { + "type": "element", + "id": "bio", + "elementType": "text", + "bindings": { + "text": "Product Designer | Travel Enthusiast | Coffee Lover ☕️ Sharing my journey through design and life." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textAlign": "center", + "textColor": "#666666", + "lineHeight": 22 + } + }, + { + "type": "container", + "id": "stats-container", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "vertical": 16 + }, + "arrangement": { + "strategy": "space_evenly" + } + }, + "style": { + "backgroundColor": "#F5F5F5", + "borderRadius": 12 + }, + "children": [ + { + "type": "container", + "id": "posts-stat", + "containerType": "vertical", + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "arrangement": { + "strategy": "center" + } + }, + "children": [ + { + "type": "element", + "id": "posts-value", + "elementType": "text", + "bindings": { + "text": "1,247" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "styleClass": "stat-value" + }, + { + "type": "element", + "id": "posts-label", + "elementType": "text", + "bindings": { + "text": "Posts" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "styleClass": "stat-label" + } + ] + }, + { + "type": "container", + "id": "followers-stat", + "containerType": "vertical", + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "arrangement": { + "strategy": "center" + } + }, + "children": [ + { + "type": "element", + "id": "followers-value", + "elementType": "text", + "bindings": { + "text": "42.5K" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "styleClass": "stat-value" + }, + { + "type": "element", + "id": "followers-label", + "elementType": "text", + "bindings": { + "text": "Followers" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "styleClass": "stat-label" + } + ] + }, + { + "type": "container", + "id": "following-stat", + "containerType": "vertical", + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "arrangement": { + "strategy": "center" + } + }, + "children": [ + { + "type": "element", + "id": "following-value", + "elementType": "text", + "bindings": { + "text": "892" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "styleClass": "stat-value" + }, + { + "type": "element", + "id": "following-label", + "elementType": "text", + "bindings": { + "text": "Following" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "styleClass": "stat-label" + } + ] + } + ] + }, + { + "type": "element", + "id": "divider", + "elementType": "divider", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 1, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#E0E0E0" + } + }, + { + "type": "container", + "id": "action-buttons", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 48, + "unit": "dp" + }, + "arrangement": { + "strategy": "spaced", + "spacing": 12, + "spacingUnit": "dp" + } + }, + "children": [ + { + "type": "container", + "id": "follow-button", + "containerType": "box", + "layout": { + "width": { + "value": 0, + "unit": "percent", + "weight": 1 + }, + "height": { + "value": 48, + "unit": "dp" + }, + "padding": { + "all": 12 + } + }, + "styleClass": "action-button", + "children": [ + { + "type": "element", + "id": "follow-text", + "elementType": "text", + "bindings": { + "text": "Follow" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + } + } + ] + }, + { + "type": "container", + "id": "message-button", + "containerType": "box", + "layout": { + "width": { + "value": 0, + "unit": "percent", + "weight": 1 + }, + "height": { + "value": 48, + "unit": "dp" + }, + "padding": { + "all": 12 + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 20, + "borderWidth": 2, + "borderColor": "#2196F3" + }, + "children": [ + { + "type": "element", + "id": "message-text", + "elementType": "text", + "bindings": { + "text": "Message" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#2196F3", + "lineHeight": 20 + } + } + ] + } + ] + } + ] + } + ] + } + ] + }, + "variables": {} +} diff --git a/flutter-sample/assets/test-configs/test-091-offset-percent-box-basic.json b/flutter-sample/assets/test-configs/test-091-offset-percent-box-basic.json new file mode 100644 index 0000000..a02b924 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-091-offset-percent-box-basic.json @@ -0,0 +1,176 @@ +{ + "theme": { + "id": "test-091", + "defaultStyle": { + "textColor": "#000000", + "fontSize": 14, + "lineHeight": 20 + } + }, + "variables": { + "testName": "Percentage Offset - Box Basic" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16, + "unit": "dp" + }, + "arrangement": { + "spacing": 16, + "spacingUnit": "dp", + "strategy": "spaced" + } + }, + "style": { + "backgroundColor": "#F5F5F5" + }, + "children": [ + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "{{testName}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "textColor": "#333333", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "description", + "elementType": "text", + "bindings": { + "text": "Tests basic percentage-based offset positioning in a Box container. Blue box at 10%,10%. Green box at 50%,50%. Red box at 80%,80%." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 12, + "textColor": "#666666", + "lineHeight": 17 + } + }, + { + "type": "container", + "id": "test-box", + "containerType": "box", + "layout": { + "width": { + "value": 300, + "unit": "dp" + }, + "height": { + "value": 300, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderWidth": 1, + "borderColor": "#CCCCCC" + }, + "children": [ + { + "type": "container", + "id": "box-10-10", + "containerType": "box", + "layout": { + "width": { + "value": 40, + "unit": "dp" + }, + "height": { + "value": 40, + "unit": "dp" + }, + "offset": { + "x": 10, + "y": 10, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#2196F3" + }, + "children": [] + }, + { + "type": "container", + "id": "box-50-50", + "containerType": "box", + "layout": { + "width": { + "value": 40, + "unit": "dp" + }, + "height": { + "value": 40, + "unit": "dp" + }, + "offset": { + "x": 50, + "y": 50, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#4CAF50" + }, + "children": [] + }, + { + "type": "container", + "id": "box-80-80", + "containerType": "box", + "layout": { + "width": { + "value": 40, + "unit": "dp" + }, + "height": { + "value": 40, + "unit": "dp" + }, + "offset": { + "x": 80, + "y": 80, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#F44336" + }, + "children": [] + } + ] + } + ] + }, + "styleClasses": [] +} diff --git a/flutter-sample/assets/test-configs/test-092-offset-percent-stack-layers.json b/flutter-sample/assets/test-configs/test-092-offset-percent-stack-layers.json new file mode 100644 index 0000000..1245505 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-092-offset-percent-stack-layers.json @@ -0,0 +1,204 @@ +{ + "theme": { + "id": "test-092", + "defaultStyle": { + "textColor": "#000000", + "fontSize": 14, + "lineHeight": 20 + } + }, + "variables": { + "testName": "Percentage Offset - Stack Layers" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16, + "unit": "dp" + }, + "arrangement": { + "spacing": 16, + "spacingUnit": "dp", + "strategy": "spaced" + } + }, + "style": { + "backgroundColor": "#F5F5F5" + }, + "children": [ + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "{{testName}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "textColor": "#333333", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "description", + "elementType": "text", + "bindings": { + "text": "Tests percentage offset with layered elements in a Stack container. Multiple boxes offset at different percentages to create a layered effect." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 12, + "textColor": "#666666", + "lineHeight": 17 + } + }, + { + "type": "container", + "id": "test-stack", + "containerType": "box", + "layout": { + "width": { + "value": 300, + "unit": "dp" + }, + "height": { + "value": 300, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderWidth": 1, + "borderColor": "#CCCCCC" + }, + "children": [ + { + "type": "container", + "id": "layer-0", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "dp" + }, + "height": { + "value": 100, + "unit": "dp" + }, + "offset": { + "x": 0, + "y": 0, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#2196F3", + "opacity": 0.8 + }, + "children": [] + }, + { + "type": "container", + "id": "layer-25", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "dp" + }, + "height": { + "value": 100, + "unit": "dp" + }, + "offset": { + "x": 25, + "y": 25, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#4CAF50", + "opacity": 0.8 + }, + "children": [] + }, + { + "type": "container", + "id": "layer-50", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "dp" + }, + "height": { + "value": 100, + "unit": "dp" + }, + "offset": { + "x": 50, + "y": 50, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#F44336", + "opacity": 0.8 + }, + "children": [] + }, + { + "type": "container", + "id": "layer-75", + "containerType": "box", + "layout": { + "width": { + "value": 50, + "unit": "dp" + }, + "height": { + "value": 50, + "unit": "dp" + }, + "offset": { + "x": 75, + "y": 75, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#FF9800", + "opacity": 0.9 + }, + "children": [] + } + ] + } + ] + }, + "styleClasses": [] +} diff --git a/flutter-sample/assets/test-configs/test-093-offset-percent-negative.json b/flutter-sample/assets/test-configs/test-093-offset-percent-negative.json new file mode 100644 index 0000000..a919872 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-093-offset-percent-negative.json @@ -0,0 +1,180 @@ +{ + "theme": { + "id": "test-093", + "defaultStyle": { + "textColor": "#000000", + "fontSize": 14, + "lineHeight": 20 + } + }, + "variables": { + "testName": "Percentage Offset - Negative Values" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16, + "unit": "dp" + }, + "arrangement": { + "spacing": 16, + "spacingUnit": "dp", + "strategy": "spaced" + } + }, + "style": { + "backgroundColor": "#F5F5F5" + }, + "children": [ + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "{{testName}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "textColor": "#333333", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "description", + "elementType": "text", + "bindings": { + "text": "Tests negative percentage offset values. Boxes should position outside or at edges of the container. Green box at -10%,-10%. Red box at 110%,110%." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 12, + "textColor": "#666666", + "lineHeight": 17 + } + }, + { + "type": "container", + "id": "test-box", + "containerType": "box", + "layout": { + "width": { + "value": 300, + "unit": "dp" + }, + "height": { + "value": 300, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderWidth": 2, + "borderColor": "#000000" + }, + "children": [ + { + "type": "container", + "id": "box-center", + "containerType": "box", + "layout": { + "width": { + "value": 60, + "unit": "dp" + }, + "height": { + "value": 60, + "unit": "dp" + }, + "offset": { + "x": 50, + "y": 50, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#2196F3" + }, + "children": [] + }, + { + "type": "container", + "id": "box-negative", + "containerType": "box", + "layout": { + "width": { + "value": 60, + "unit": "dp" + }, + "height": { + "value": 60, + "unit": "dp" + }, + "offset": { + "x": -10, + "y": -10, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#4CAF50", + "borderWidth": 2, + "borderColor": "#2E7D32" + }, + "children": [] + }, + { + "type": "container", + "id": "box-overflow", + "containerType": "box", + "layout": { + "width": { + "value": 40, + "unit": "dp" + }, + "height": { + "value": 40, + "unit": "dp" + }, + "offset": { + "x": 110, + "y": 110, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#F44336", + "borderWidth": 2, + "borderColor": "#C62828" + }, + "children": [] + } + ] + } + ] + }, + "styleClasses": [] +} diff --git a/flutter-sample/assets/test-configs/test-094-offset-percent-overflow.json b/flutter-sample/assets/test-configs/test-094-offset-percent-overflow.json new file mode 100644 index 0000000..405cbdd --- /dev/null +++ b/flutter-sample/assets/test-configs/test-094-offset-percent-overflow.json @@ -0,0 +1,208 @@ +{ + "theme": { + "id": "test-094", + "defaultStyle": { + "textColor": "#000000", + "fontSize": 14, + "lineHeight": 20 + } + }, + "variables": { + "testName": "Percentage Offset - Overflow Values" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16, + "unit": "dp" + }, + "arrangement": { + "spacing": 16, + "spacingUnit": "dp", + "strategy": "spaced" + } + }, + "style": { + "backgroundColor": "#F5F5F5" + }, + "children": [ + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "{{testName}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "textColor": "#333333", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "description", + "elementType": "text", + "bindings": { + "text": "Tests offset values > 100%. Blue at 100%,0%. Green at 0%,100%. Orange at 150%,150%. Red at 200%,200%." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 12, + "textColor": "#666666", + "lineHeight": 17 + } + }, + { + "type": "container", + "id": "test-box", + "containerType": "box", + "layout": { + "width": { + "value": 300, + "unit": "dp" + }, + "height": { + "value": 300, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderWidth": 2, + "borderColor": "#000000" + }, + "children": [ + { + "type": "container", + "id": "box-100-0", + "containerType": "box", + "layout": { + "width": { + "value": 50, + "unit": "dp" + }, + "height": { + "value": 50, + "unit": "dp" + }, + "offset": { + "x": 100, + "y": 0, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#2196F3", + "borderWidth": 2, + "borderColor": "#1976D2" + }, + "children": [] + }, + { + "type": "container", + "id": "box-0-100", + "containerType": "box", + "layout": { + "width": { + "value": 50, + "unit": "dp" + }, + "height": { + "value": 50, + "unit": "dp" + }, + "offset": { + "x": 0, + "y": 100, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#4CAF50", + "borderWidth": 2, + "borderColor": "#388E3C" + }, + "children": [] + }, + { + "type": "container", + "id": "box-150-150", + "containerType": "box", + "layout": { + "width": { + "value": 40, + "unit": "dp" + }, + "height": { + "value": 40, + "unit": "dp" + }, + "offset": { + "x": 150, + "y": 150, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#FF9800", + "borderWidth": 2, + "borderColor": "#F57C00" + }, + "children": [] + }, + { + "type": "container", + "id": "box-200-200", + "containerType": "box", + "layout": { + "width": { + "value": 30, + "unit": "dp" + }, + "height": { + "value": 30, + "unit": "dp" + }, + "offset": { + "x": 200, + "y": 200, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#F44336", + "borderWidth": 2, + "borderColor": "#D32F2F" + }, + "children": [] + } + ] + } + ] + }, + "styleClasses": [] +} diff --git a/flutter-sample/assets/test-configs/test-095-offset-percent-zero.json b/flutter-sample/assets/test-configs/test-095-offset-percent-zero.json new file mode 100644 index 0000000..52ff230 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-095-offset-percent-zero.json @@ -0,0 +1,196 @@ +{ + "theme": { + "id": "test-095", + "defaultStyle": { + "textColor": "#000000", + "fontSize": 14, + "lineHeight": 20 + } + }, + "variables": { + "testName": "Percentage Offset - Zero Values" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16, + "unit": "dp" + }, + "arrangement": { + "spacing": 16, + "spacingUnit": "dp", + "strategy": "spaced" + } + }, + "style": { + "backgroundColor": "#F5F5F5" + }, + "children": [ + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "{{testName}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "textColor": "#333333", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "description", + "elementType": "text", + "bindings": { + "text": "Tests zero percentage offset (0%,0%). Should position at top-left corner, respecting padding if present." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 12, + "textColor": "#666666", + "lineHeight": 17 + } + }, + { + "type": "container", + "id": "test-container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "arrangement": { + "spacing": 16, + "spacingUnit": "dp", + "strategy": "spaced" + } + }, + "children": [ + { + "type": "container", + "id": "box-no-padding", + "containerType": "box", + "layout": { + "width": { + "value": 300, + "unit": "dp" + }, + "height": { + "value": 150, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderWidth": 2, + "borderColor": "#000000" + }, + "children": [ + { + "type": "container", + "id": "zero-offset-no-padding", + "containerType": "box", + "layout": { + "width": { + "value": 60, + "unit": "dp" + }, + "height": { + "value": 60, + "unit": "dp" + }, + "offset": { + "x": 0, + "y": 0, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#2196F3" + }, + "children": [] + } + ] + }, + { + "type": "container", + "id": "box-with-padding", + "containerType": "box", + "layout": { + "width": { + "value": 300, + "unit": "dp" + }, + "height": { + "value": 150, + "unit": "dp" + }, + "padding": { + "all": 20, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderWidth": 2, + "borderColor": "#000000" + }, + "children": [ + { + "type": "container", + "id": "zero-offset-with-padding", + "containerType": "box", + "layout": { + "width": { + "value": 60, + "unit": "dp" + }, + "height": { + "value": 60, + "unit": "dp" + }, + "offset": { + "x": 0, + "y": 0, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#4CAF50" + }, + "children": [] + } + ] + } + ] + } + ] + }, + "styleClasses": [] +} diff --git a/flutter-sample/assets/test-configs/test-096-offset-percent-responsive.json b/flutter-sample/assets/test-configs/test-096-offset-percent-responsive.json new file mode 100644 index 0000000..22b7bdf --- /dev/null +++ b/flutter-sample/assets/test-configs/test-096-offset-percent-responsive.json @@ -0,0 +1,238 @@ +{ + "theme": { + "id": "test-096", + "defaultStyle": { + "textColor": "#000000", + "fontSize": 14, + "lineHeight": 20 + } + }, + "variables": { + "testName": "Percentage Offset - Responsive Parent Sizes" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16, + "unit": "dp" + }, + "arrangement": { + "spacing": 16, + "spacingUnit": "dp", + "strategy": "spaced" + } + }, + "style": { + "backgroundColor": "#F5F5F5" + }, + "children": [ + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "{{testName}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "textColor": "#333333", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "description", + "elementType": "text", + "bindings": { + "text": "Tests percentage offset with different parent container sizes. Same 50%,50% offset applied to small, medium, and large containers." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 12, + "textColor": "#666666", + "lineHeight": 17 + } + }, + { + "type": "container", + "id": "containers-row", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "arrangement": { + "spacing": 16, + "spacingUnit": "dp", + "strategy": "spaced" + } + }, + "children": [ + { + "type": "container", + "id": "small-container", + "containerType": "box", + "layout": { + "width": { + "value": 150, + "unit": "dp" + }, + "height": { + "value": 150, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#E3F2FD", + "borderWidth": 2, + "borderColor": "#2196F3" + }, + "children": [ + { + "type": "container", + "id": "small-box", + "containerType": "box", + "layout": { + "width": { + "value": 30, + "unit": "dp" + }, + "height": { + "value": 30, + "unit": "dp" + }, + "offset": { + "x": 50, + "y": 50, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#2196F3" + }, + "children": [] + } + ] + }, + { + "type": "container", + "id": "medium-container", + "containerType": "box", + "layout": { + "width": { + "value": 200, + "unit": "dp" + }, + "height": { + "value": 200, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#E8F5E9", + "borderWidth": 2, + "borderColor": "#4CAF50" + }, + "children": [ + { + "type": "container", + "id": "medium-box", + "containerType": "box", + "layout": { + "width": { + "value": 40, + "unit": "dp" + }, + "height": { + "value": 40, + "unit": "dp" + }, + "offset": { + "x": 50, + "y": 50, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#4CAF50" + }, + "children": [] + } + ] + }, + { + "type": "container", + "id": "large-container", + "containerType": "box", + "layout": { + "width": { + "value": 250, + "unit": "dp" + }, + "height": { + "value": 250, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FFF3E0", + "borderWidth": 2, + "borderColor": "#FF9800" + }, + "children": [ + { + "type": "container", + "id": "large-box", + "containerType": "box", + "layout": { + "width": { + "value": 50, + "unit": "dp" + }, + "height": { + "value": 50, + "unit": "dp" + }, + "offset": { + "x": 50, + "y": 50, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#FF9800" + }, + "children": [] + } + ] + } + ] + } + ] + }, + "styleClasses": [] +} diff --git a/flutter-sample/assets/test-configs/test-097-offset-mixed-units.json b/flutter-sample/assets/test-configs/test-097-offset-mixed-units.json new file mode 100644 index 0000000..87d7a23 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-097-offset-mixed-units.json @@ -0,0 +1,208 @@ +{ + "theme": { + "id": "test-097", + "defaultStyle": { + "textColor": "#000000", + "fontSize": 14, + "lineHeight": 20 + } + }, + "variables": { + "testName": "Percentage Offset - Mixed with DP Units" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16, + "unit": "dp" + }, + "arrangement": { + "spacing": 16, + "spacingUnit": "dp", + "strategy": "spaced" + } + }, + "style": { + "backgroundColor": "#F5F5F5" + }, + "children": [ + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "{{testName}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "textColor": "#333333", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "description", + "elementType": "text", + "bindings": { + "text": "Tests mixing percentage and DP offset units in the same container. Shows different offset types side by side." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 12, + "textColor": "#666666", + "lineHeight": 17 + } + }, + { + "type": "container", + "id": "test-box", + "containerType": "box", + "layout": { + "width": { + "value": 300, + "unit": "dp" + }, + "height": { + "value": 300, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderWidth": 2, + "borderColor": "#000000" + }, + "children": [ + { + "type": "container", + "id": "box-dp-20-20", + "containerType": "box", + "layout": { + "width": { + "value": 50, + "unit": "dp" + }, + "height": { + "value": 50, + "unit": "dp" + }, + "offset": { + "x": 20, + "y": 20, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#2196F3", + "borderWidth": 2, + "borderColor": "#1976D2" + }, + "children": [] + }, + { + "type": "container", + "id": "box-percent-50-50", + "containerType": "box", + "layout": { + "width": { + "value": 50, + "unit": "dp" + }, + "height": { + "value": 50, + "unit": "dp" + }, + "offset": { + "x": 50, + "y": 50, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#4CAF50", + "borderWidth": 2, + "borderColor": "#388E3C" + }, + "children": [] + }, + { + "type": "container", + "id": "box-dp-100-200", + "containerType": "box", + "layout": { + "width": { + "value": 40, + "unit": "dp" + }, + "height": { + "value": 40, + "unit": "dp" + }, + "offset": { + "x": 100, + "y": 200, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF9800", + "borderWidth": 2, + "borderColor": "#F57C00" + }, + "children": [] + }, + { + "type": "container", + "id": "box-percent-75-25", + "containerType": "box", + "layout": { + "width": { + "value": 40, + "unit": "dp" + }, + "height": { + "value": 40, + "unit": "dp" + }, + "offset": { + "x": 75, + "y": 25, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#F44336", + "borderWidth": 2, + "borderColor": "#D32F2F" + }, + "children": [] + } + ] + } + ] + }, + "styleClasses": [] +} diff --git a/flutter-sample/assets/test-configs/test-098-offset-percent-nested.json b/flutter-sample/assets/test-configs/test-098-offset-percent-nested.json new file mode 100644 index 0000000..c694f8a --- /dev/null +++ b/flutter-sample/assets/test-configs/test-098-offset-percent-nested.json @@ -0,0 +1,184 @@ +{ + "theme": { + "id": "test-098", + "defaultStyle": { + "textColor": "#000000", + "fontSize": 14, + "lineHeight": 20 + } + }, + "variables": { + "testName": "Percentage Offset - Nested Containers" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16, + "unit": "dp" + }, + "arrangement": { + "spacing": 16, + "spacingUnit": "dp", + "strategy": "spaced" + } + }, + "style": { + "backgroundColor": "#F5F5F5" + }, + "children": [ + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "{{testName}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "textColor": "#333333", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "description", + "elementType": "text", + "bindings": { + "text": "Tests percentage offset with nested Box containers. Each level calculates offset relative to its immediate parent, not the root." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 12, + "textColor": "#666666", + "lineHeight": 17 + } + }, + { + "type": "container", + "id": "outer-box", + "containerType": "box", + "layout": { + "width": { + "value": 300, + "unit": "dp" + }, + "height": { + "value": 300, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#E3F2FD", + "borderWidth": 3, + "borderColor": "#2196F3" + }, + "children": [ + { + "type": "container", + "id": "middle-box", + "containerType": "box", + "layout": { + "width": { + "value": 200, + "unit": "dp" + }, + "height": { + "value": 200, + "unit": "dp" + }, + "offset": { + "x": 25, + "y": 25, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#E8F5E9", + "borderWidth": 3, + "borderColor": "#4CAF50" + }, + "children": [ + { + "type": "container", + "id": "inner-box", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "dp" + }, + "height": { + "value": 100, + "unit": "dp" + }, + "offset": { + "x": 25, + "y": 25, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#FFF3E0", + "borderWidth": 3, + "borderColor": "#FF9800" + }, + "children": [ + { + "type": "container", + "id": "innermost-box", + "containerType": "box", + "layout": { + "width": { + "value": 50, + "unit": "dp" + }, + "height": { + "value": 50, + "unit": "dp" + }, + "offset": { + "x": 25, + "y": 25, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#FFEBEE", + "borderWidth": 3, + "borderColor": "#F44336" + }, + "children": [] + } + ] + } + ] + } + ] + } + ] + }, + "styleClasses": [] +} diff --git a/flutter-sample/assets/test-configs/test-099-offset-percent-with-padding.json b/flutter-sample/assets/test-configs/test-099-offset-percent-with-padding.json new file mode 100644 index 0000000..b46d2c1 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-099-offset-percent-with-padding.json @@ -0,0 +1,275 @@ +{ + "theme": { + "id": "test-099", + "defaultStyle": { + "textColor": "#000000", + "fontSize": 14, + "lineHeight": 20 + } + }, + "variables": { + "testName": "Percentage Offset - With Padding" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16, + "unit": "dp" + }, + "arrangement": { + "spacing": 16, + "spacingUnit": "dp", + "strategy": "spaced" + } + }, + "style": { + "backgroundColor": "#F5F5F5" + }, + "children": [ + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "{{testName}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "textColor": "#333333", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "description", + "elementType": "text", + "bindings": { + "text": "Tests percentage offset interaction with container padding. Percentage should calculate from content area (after padding)." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 12, + "textColor": "#666666", + "lineHeight": 17 + } + }, + { + "type": "container", + "id": "test-container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "arrangement": { + "spacing": 16, + "spacingUnit": "dp", + "strategy": "spaced" + } + }, + "children": [ + { + "type": "container", + "id": "box-uniform-padding", + "containerType": "box", + "layout": { + "width": { + "value": 300, + "unit": "dp" + }, + "height": { + "value": 200, + "unit": "dp" + }, + "padding": { + "all": 20, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#E3F2FD", + "borderWidth": 2, + "borderColor": "#2196F3" + }, + "children": [ + { + "type": "container", + "id": "box-0-0", + "containerType": "box", + "layout": { + "width": { + "value": 40, + "unit": "dp" + }, + "height": { + "value": 40, + "unit": "dp" + }, + "offset": { + "x": 0, + "y": 0, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#2196F3" + }, + "children": [] + }, + { + "type": "container", + "id": "box-50-50", + "containerType": "box", + "layout": { + "width": { + "value": 40, + "unit": "dp" + }, + "height": { + "value": 40, + "unit": "dp" + }, + "offset": { + "x": 50, + "y": 50, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#4CAF50" + }, + "children": [] + }, + { + "type": "container", + "id": "box-100-100", + "containerType": "box", + "layout": { + "width": { + "value": 40, + "unit": "dp" + }, + "height": { + "value": 40, + "unit": "dp" + }, + "offset": { + "x": 100, + "y": 100, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#F44336" + }, + "children": [] + } + ] + }, + { + "type": "container", + "id": "box-asymmetric-padding", + "containerType": "box", + "layout": { + "width": { + "value": 300, + "unit": "dp" + }, + "height": { + "value": 200, + "unit": "dp" + }, + "padding": { + "left": 40, + "right": 10, + "top": 30, + "bottom": 10, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FFF3E0", + "borderWidth": 2, + "borderColor": "#FF9800" + }, + "children": [ + { + "type": "container", + "id": "asym-box-0-0", + "containerType": "box", + "layout": { + "width": { + "value": 40, + "unit": "dp" + }, + "height": { + "value": 40, + "unit": "dp" + }, + "offset": { + "x": 0, + "y": 0, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#FF9800" + }, + "children": [] + }, + { + "type": "container", + "id": "asym-box-50-50", + "containerType": "box", + "layout": { + "width": { + "value": 40, + "unit": "dp" + }, + "height": { + "value": 40, + "unit": "dp" + }, + "offset": { + "x": 50, + "y": 50, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#9C27B0" + }, + "children": [] + } + ] + } + ] + } + ] + }, + "styleClasses": [] +} diff --git a/flutter-sample/assets/test-configs/test-100-offset-percent-gallery-peek.json b/flutter-sample/assets/test-configs/test-100-offset-percent-gallery-peek.json new file mode 100644 index 0000000..55e83bf --- /dev/null +++ b/flutter-sample/assets/test-configs/test-100-offset-percent-gallery-peek.json @@ -0,0 +1,189 @@ +{ + "theme": { + "id": "test-100", + "defaultStyle": { + "textColor": "#000000", + "fontSize": 14, + "lineHeight": 20 + } + }, + "variables": { + "testName": "Percentage Offset - Gallery Peek" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16, + "unit": "dp" + }, + "arrangement": { + "spacing": 16, + "spacingUnit": "dp", + "strategy": "spaced" + } + }, + "style": { + "backgroundColor": "#F5F5F5" + }, + "children": [ + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "{{testName}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "textColor": "#333333", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "description", + "elementType": "text", + "bindings": { + "text": "Tests percentage offset for creating gallery peek effect. Use Stack container to layer elements with percentage-based positioning." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 12, + "textColor": "#666666", + "lineHeight": 17 + } + }, + { + "type": "container", + "id": "gallery-stack", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 300, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF" + }, + "children": [ + { + "type": "container", + "id": "card-1", + "containerType": "box", + "layout": { + "width": { + "value": 70, + "unit": "percent" + }, + "height": { + "value": 250, + "unit": "dp" + }, + "offset": { + "x": 0, + "y": 10, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#2196F3", + "borderRadius": 8, + "shadowColor": "#00000040", + "shadowRadius": 8, + "shadowOffsetX": 0, + "shadowOffsetY": 2 + }, + "children": [] + }, + { + "type": "container", + "id": "card-2", + "containerType": "box", + "layout": { + "width": { + "value": 70, + "unit": "percent" + }, + "height": { + "value": 250, + "unit": "dp" + }, + "offset": { + "x": 15, + "y": 12, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#4CAF50", + "borderRadius": 8, + "shadowColor": "#00000040", + "shadowRadius": 8, + "shadowOffsetX": 0, + "shadowOffsetY": 2 + }, + "children": [] + }, + { + "type": "container", + "id": "card-3", + "containerType": "box", + "layout": { + "width": { + "value": 70, + "unit": "percent" + }, + "height": { + "value": 250, + "unit": "dp" + }, + "offset": { + "x": 30, + "y": 14, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#FF9800", + "borderRadius": 8, + "shadowColor": "#00000040", + "shadowRadius": 8, + "shadowOffsetX": 0, + "shadowOffsetY": 2 + }, + "children": [] + } + ] + } + ] + }, + "styleClasses": [] +} diff --git a/flutter-sample/assets/test-configs/test-101-aspect-ratio-square-fixed-width.json b/flutter-sample/assets/test-configs/test-101-aspect-ratio-square-fixed-width.json new file mode 100644 index 0000000..8cc8fe5 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-101-aspect-ratio-square-fixed-width.json @@ -0,0 +1,193 @@ +{ + "theme": { + "id": "test-101", + "defaultStyle": { + "textColor": "#000000", + "fontSize": 14, + "lineHeight": 20 + } + }, + "variables": { + "testName": "Aspect Ratio - Square (1:1) with Fixed Width" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16, + "unit": "dp" + }, + "arrangement": { + "spacing": 16, + "spacingUnit": "dp", + "strategy": "spaced" + } + }, + "style": { + "backgroundColor": "#F5F5F5" + }, + "children": [ + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "{{testName}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "textColor": "#333333", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "description", + "elementType": "text", + "bindings": { + "text": "Tests square aspect ratio (1:1). Three boxes with different fixed widths should all render as perfect squares." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 12, + "textColor": "#666666", + "lineHeight": 17 + } + }, + { + "type": "container", + "id": "examples-row", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "arrangement": { + "spacing": 16, + "spacingUnit": "dp", + "strategy": "spaced" + } + }, + "children": [ + { + "type": "container", + "id": "square-100", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "dp" + }, + "aspectRatio": 1.0 + }, + "style": { + "backgroundColor": "#2196F3", + "borderRadius": 8 + }, + "children": [] + }, + { + "type": "container", + "id": "square-150", + "containerType": "box", + "layout": { + "width": { + "value": 150, + "unit": "dp" + }, + "aspectRatio": 1.0 + }, + "style": { + "backgroundColor": "#4CAF50", + "borderRadius": 8 + }, + "children": [] + }, + { + "type": "container", + "id": "square-200", + "containerType": "box", + "layout": { + "width": { + "value": 200, + "unit": "dp" + }, + "aspectRatio": 1.0 + }, + "style": { + "backgroundColor": "#FF9800", + "borderRadius": 8 + }, + "children": [] + } + ] + }, + { + "type": "container", + "id": "with-content", + "containerType": "box", + "layout": { + "width": { + "value": 250, + "unit": "dp" + }, + "aspectRatio": 1.0, + "padding": { + "all": 16, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#9C27B0", + "borderRadius": 8 + }, + "children": [ + { + "type": "element", + "id": "content-text", + "elementType": "text", + "bindings": { + "text": "This is a perfect square box with 1:1 aspect ratio and some content inside." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 14, + "textColor": "#FFFFFF", + "textAlign": "center", + "lineHeight": 20 + } + } + ] + } + ] + }, + "styleClasses": [] +} diff --git a/flutter-sample/assets/test-configs/test-102-aspect-ratio-16-9-fixed-width.json b/flutter-sample/assets/test-configs/test-102-aspect-ratio-16-9-fixed-width.json new file mode 100644 index 0000000..bbd9ec3 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-102-aspect-ratio-16-9-fixed-width.json @@ -0,0 +1,197 @@ +{ + "theme": { + "id": "test-102", + "defaultStyle": { + "textColor": "#000000", + "fontSize": 14, + "lineHeight": 20 + } + }, + "variables": { + "testName": "Aspect Ratio - Widescreen (16:9) with Fixed Width" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16, + "unit": "dp" + }, + "arrangement": { + "spacing": 16, + "spacingUnit": "dp", + "strategy": "spaced" + } + }, + "style": { + "backgroundColor": "#F5F5F5" + }, + "children": [ + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "{{testName}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "textColor": "#333333", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "description", + "elementType": "text", + "bindings": { + "text": "Tests widescreen 16:9 aspect ratio (1.777). Common for media content. 320dp width = 180dp height." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 12, + "textColor": "#666666", + "lineHeight": 17 + } + }, + { + "type": "container", + "id": "image-frame-1", + "containerType": "box", + "layout": { + "width": { + "value": 320, + "unit": "dp" + }, + "aspectRatio": 1.777 + }, + "style": { + "backgroundColor": "#2196F3", + "borderRadius": 8 + }, + "children": [ + { + "type": "element", + "id": "image-1", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-89.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + } + } + } + ] + }, + { + "type": "container", + "id": "image-frame-2", + "containerType": "box", + "layout": { + "width": { + "value": 280, + "unit": "dp" + }, + "aspectRatio": 1.777 + }, + "style": { + "backgroundColor": "#4CAF50", + "borderRadius": 8 + }, + "children": [ + { + "type": "element", + "id": "image-2", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-90.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + } + } + } + ] + }, + { + "type": "container", + "id": "placeholder-frame", + "containerType": "box", + "layout": { + "width": { + "value": 240, + "unit": "dp" + }, + "aspectRatio": 1.777, + "padding": { + "all": 16, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#263238", + "borderRadius": 8 + }, + "children": [ + { + "type": "element", + "id": "placeholder-text", + "elementType": "text", + "bindings": { + "text": "16:9 Widescreen Format" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 16, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "textAlign": "center", + "lineHeight": 22 + } + } + ] + } + ] + }, + "styleClasses": [] +} diff --git a/flutter-sample/assets/test-configs/test-103-aspect-ratio-4-3-fixed-width.json b/flutter-sample/assets/test-configs/test-103-aspect-ratio-4-3-fixed-width.json new file mode 100644 index 0000000..bd2b449 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-103-aspect-ratio-4-3-fixed-width.json @@ -0,0 +1,259 @@ +{ + "theme": { + "id": "test-103", + "defaultStyle": { + "textColor": "#000000", + "fontSize": 14, + "lineHeight": 20 + } + }, + "variables": { + "testName": "Aspect Ratio - Photo (4:3) with Fixed Width" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16, + "unit": "dp" + }, + "arrangement": { + "spacing": 16, + "spacingUnit": "dp", + "strategy": "spaced" + } + }, + "style": { + "backgroundColor": "#F5F5F5" + }, + "children": [ + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "{{testName}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "textColor": "#333333", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "description", + "elementType": "text", + "bindings": { + "text": "Tests 4:3 aspect ratio (1.333). Classic photo format. 240dp width = 180dp height." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 12, + "textColor": "#666666", + "lineHeight": 17 + } + }, + { + "type": "container", + "id": "photo-gallery", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "arrangement": { + "spacing": 12, + "spacingUnit": "dp", + "strategy": "spaced" + } + }, + "children": [ + { + "type": "container", + "id": "photo-1", + "containerType": "box", + "layout": { + "width": { + "value": 160, + "unit": "dp" + }, + "aspectRatio": 1.333 + }, + "style": { + "backgroundColor": "#2196F3", + "borderRadius": 8, + "shadowColor": "#00000030", + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 2 + }, + "children": [ + { + "type": "element", + "id": "photo-1-image", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-91.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + } + } + } + ] + }, + { + "type": "container", + "id": "photo-2", + "containerType": "box", + "layout": { + "width": { + "value": 160, + "unit": "dp" + }, + "aspectRatio": 1.333 + }, + "style": { + "backgroundColor": "#4CAF50", + "borderRadius": 8, + "shadowColor": "#00000030", + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 2 + }, + "children": [ + { + "type": "element", + "id": "photo-2-image", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-92.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + } + } + } + ] + }, + { + "type": "container", + "id": "photo-3", + "containerType": "box", + "layout": { + "width": { + "value": 160, + "unit": "dp" + }, + "aspectRatio": 1.333 + }, + "style": { + "backgroundColor": "#FF9800", + "borderRadius": 8, + "shadowColor": "#00000030", + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 2 + }, + "children": [ + { + "type": "element", + "id": "photo-3-image", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-93.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + } + } + } + ] + } + ] + }, + { + "type": "container", + "id": "large-photo", + "containerType": "box", + "layout": { + "width": { + "value": 300, + "unit": "dp" + }, + "aspectRatio": 1.333 + }, + "style": { + "backgroundColor": "#9C27B0", + "borderRadius": 12, + "shadowColor": "#00000040", + "shadowRadius": 12, + "shadowOffsetX": 0, + "shadowOffsetY": 4 + }, + "children": [ + { + "type": "element", + "id": "large-photo-image", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-94.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + } + } + } + ] + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-104-aspect-ratio-fixed-height.json b/flutter-sample/assets/test-configs/test-104-aspect-ratio-fixed-height.json new file mode 100644 index 0000000..e7df482 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-104-aspect-ratio-fixed-height.json @@ -0,0 +1,253 @@ +{ + "theme": { + "id": "test-104", + "defaultStyle": { + "textColor": "#000000", + "fontSize": 14, + "lineHeight": 20 + } + }, + "variables": { + "testName": "Aspect Ratio - Fixed Height, Calculated Width" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16, + "unit": "dp" + }, + "arrangement": { + "spacing": 16, + "spacingUnit": "dp", + "strategy": "spaced" + } + }, + "style": { + "backgroundColor": "#F5F5F5" + }, + "children": [ + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "{{testName}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "textColor": "#333333", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "description", + "elementType": "text", + "bindings": { + "text": "Tests aspect ratio with fixed height. Width is calculated based on aspect ratio. 16:9 at 180dp height = 320dp width." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 12, + "textColor": "#666666", + "lineHeight": 17 + } + }, + { + "type": "container", + "id": "fixed-height-16-9", + "containerType": "box", + "layout": { + "height": { + "value": 180, + "unit": "dp" + }, + "aspectRatio": 1.777 + }, + "style": { + "backgroundColor": "#2196F3", + "borderRadius": 8 + }, + "children": [ + { + "type": "element", + "id": "fixed-height-content-1", + "elementType": "text", + "bindings": { + "text": "16:9\n180dp height → 320dp width" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16, + "unit": "dp" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "textAlign": "center", + "lineHeight": 20 + } + } + ] + }, + { + "type": "container", + "id": "fixed-height-4-3", + "containerType": "box", + "layout": { + "height": { + "value": 200, + "unit": "dp" + }, + "aspectRatio": 1.333 + }, + "style": { + "backgroundColor": "#4CAF50", + "borderRadius": 8 + }, + "children": [ + { + "type": "element", + "id": "fixed-height-content-2", + "elementType": "text", + "bindings": { + "text": "4:3\n200dp height → 267dp width" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16, + "unit": "dp" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "textAlign": "center", + "lineHeight": 20 + } + } + ] + }, + { + "type": "container", + "id": "fixed-height-square", + "containerType": "box", + "layout": { + "height": { + "value": 150, + "unit": "dp" + }, + "aspectRatio": 1.0 + }, + "style": { + "backgroundColor": "#FF9800", + "borderRadius": 8 + }, + "children": [ + { + "type": "element", + "id": "fixed-height-content-3", + "elementType": "text", + "bindings": { + "text": "1:1\n150dp height → 150dp width" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16, + "unit": "dp" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "textAlign": "center", + "lineHeight": 20 + } + } + ] + }, + { + "type": "container", + "id": "fixed-height-portrait", + "containerType": "box", + "layout": { + "height": { + "value": 250, + "unit": "dp" + }, + "aspectRatio": 0.75 + }, + "style": { + "backgroundColor": "#9C27B0", + "borderRadius": 8 + }, + "children": [ + { + "type": "element", + "id": "fixed-height-content-4", + "elementType": "text", + "bindings": { + "text": "3:4 Portrait\n250dp height → 188dp width" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16, + "unit": "dp" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "textAlign": "center", + "lineHeight": 20 + } + } + ] + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-105-aspect-ratio-percent-width.json b/flutter-sample/assets/test-configs/test-105-aspect-ratio-percent-width.json new file mode 100644 index 0000000..7bd2393 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-105-aspect-ratio-percent-width.json @@ -0,0 +1,266 @@ +{ + "theme": { + "id": "test-105", + "defaultStyle": { + "textColor": "#000000", + "fontSize": 14, + "lineHeight": 20 + } + }, + "variables": { + "testName": "Aspect Ratio - Responsive Width (Percentage)" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16, + "unit": "dp" + }, + "arrangement": { + "spacing": 16, + "spacingUnit": "dp", + "strategy": "spaced" + } + }, + "style": { + "backgroundColor": "#F5F5F5" + }, + "children": [ + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "{{testName}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "textColor": "#333333", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "description", + "elementType": "text", + "bindings": { + "text": "Tests aspect ratio with percentage-based width. Elements scale responsively while maintaining aspect ratio." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 12, + "textColor": "#666666", + "lineHeight": 17 + } + }, + { + "type": "container", + "id": "full-width-16-9", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 1.777 + }, + "style": { + "backgroundColor": "#2196F3", + "borderRadius": 8 + }, + "children": [ + { + "type": "element", + "id": "full-width-image", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-95.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + } + } + } + ] + }, + { + "type": "container", + "id": "half-width-square", + "containerType": "box", + "layout": { + "width": { + "value": 50, + "unit": "percent" + }, + "aspectRatio": 1.0 + }, + "style": { + "backgroundColor": "#4CAF50", + "borderRadius": 8 + }, + "children": [ + { + "type": "element", + "id": "half-width-content", + "elementType": "text", + "bindings": { + "text": "50% width\n1:1 square" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16, + "unit": "dp" + } + }, + "style": { + "fontSize": 16, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "textAlign": "center", + "lineHeight": 22 + } + } + ] + }, + { + "type": "container", + "id": "responsive-row", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "arrangement": { + "spacing": 12, + "spacingUnit": "dp", + "strategy": "space_between" + } + }, + "children": [ + { + "type": "container", + "id": "card-1", + "containerType": "box", + "layout": { + "width": { + "value": 30, + "unit": "percent" + }, + "aspectRatio": 1.0 + }, + "style": { + "backgroundColor": "#FF9800", + "borderRadius": 8 + }, + "children": [] + }, + { + "type": "container", + "id": "card-2", + "containerType": "box", + "layout": { + "width": { + "value": 30, + "unit": "percent" + }, + "aspectRatio": 1.0 + }, + "style": { + "backgroundColor": "#9C27B0", + "borderRadius": 8 + }, + "children": [] + }, + { + "type": "container", + "id": "card-3", + "containerType": "box", + "layout": { + "width": { + "value": 30, + "unit": "percent" + }, + "aspectRatio": 1.0 + }, + "style": { + "backgroundColor": "#F44336", + "borderRadius": 8 + }, + "children": [] + } + ] + }, + { + "type": "container", + "id": "nested-responsive", + "containerType": "box", + "layout": { + "width": { + "value": 80, + "unit": "percent" + }, + "aspectRatio": 1.5, + "padding": { + "all": 16, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#607D8B", + "borderRadius": 8 + }, + "children": [ + { + "type": "element", + "id": "nested-image", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-96.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 1.5 + } + } + ] + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-106-aspect-ratio-wrap-content.json b/flutter-sample/assets/test-configs/test-106-aspect-ratio-wrap-content.json new file mode 100644 index 0000000..2f0c546 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-106-aspect-ratio-wrap-content.json @@ -0,0 +1,207 @@ +{ + "theme": { + "id": "test-106", + "defaultStyle": { + "textColor": "#000000", + "fontSize": 14, + "lineHeight": 20 + } + }, + "variables": { + "testName": "Aspect Ratio - WRAP_CONTENT Edge Case" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16, + "unit": "dp" + }, + "arrangement": { + "spacing": 16, + "spacingUnit": "dp", + "strategy": "spaced" + } + }, + "style": { + "backgroundColor": "#F5F5F5" + }, + "children": [ + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "{{testName}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "textColor": "#333333", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "description", + "elementType": "text", + "bindings": { + "text": "Tests aspect ratio with WRAP_CONTENT dimensions. Should wrap content while respecting aspect ratio constraints where possible." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 12, + "textColor": "#666666", + "lineHeight": 17 + } + }, + { + "type": "container", + "id": "wrap-width-with-ratio", + "containerType": "box", + "layout": { + "width": { + "special": "wrap_content" + }, + "aspectRatio": 1.0, + "padding": { + "all": 16, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#2196F3", + "borderRadius": 8 + }, + "children": [ + { + "type": "element", + "id": "wrap-text-1", + "elementType": "text", + "bindings": { + "text": "WRAP_CONTENT width + 1:1 ratio" + }, + "layout": { + "width": { + "value": 200, + "unit": "dp" + } + }, + "style": { + "fontSize": 14, + "textColor": "#FFFFFF", + "textAlign": "center", + "lineHeight": 20 + } + } + ] + }, + { + "type": "container", + "id": "wrap-height-with-ratio", + "containerType": "box", + "layout": { + "height": { + "special": "wrap_content" + }, + "aspectRatio": 2.0, + "padding": { + "all": 16, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#4CAF50", + "borderRadius": 8 + }, + "children": [ + { + "type": "element", + "id": "wrap-text-2", + "elementType": "text", + "bindings": { + "text": "WRAP_CONTENT height\n2:1 ratio" + }, + "layout": { + "width": { + "value": 250, + "unit": "dp" + } + }, + "style": { + "fontSize": 14, + "textColor": "#FFFFFF", + "textAlign": "center", + "lineHeight": 20 + } + } + ] + }, + { + "type": "container", + "id": "both-wrap-with-ratio", + "containerType": "box", + "layout": { + "width": { + "special": "wrap_content" + }, + "height": { + "special": "wrap_content" + }, + "aspectRatio": 1.5, + "padding": { + "all": 20, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF9800", + "borderRadius": 8 + }, + "children": [ + { + "type": "element", + "id": "wrap-text-3", + "elementType": "text", + "bindings": { + "text": "Both WRAP_CONTENT\n1.5:1 ratio" + }, + "layout": { + "width": { + "value": 180, + "unit": "dp" + } + }, + "style": { + "fontSize": 14, + "textColor": "#FFFFFF", + "textAlign": "center", + "lineHeight": 20 + } + } + ] + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-107-aspect-ratio-match-parent.json b/flutter-sample/assets/test-configs/test-107-aspect-ratio-match-parent.json new file mode 100644 index 0000000..1462414 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-107-aspect-ratio-match-parent.json @@ -0,0 +1,207 @@ +{ + "theme": { + "id": "test-107", + "defaultStyle": { + "textColor": "#000000", + "fontSize": 14, + "lineHeight": 20 + } + }, + "variables": { + "testName": "Aspect Ratio - MATCH_PARENT Edge Case" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16, + "unit": "dp" + }, + "arrangement": { + "spacing": 16, + "spacingUnit": "dp", + "strategy": "spaced" + } + }, + "style": { + "backgroundColor": "#F5F5F5" + }, + "children": [ + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "{{testName}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "textColor": "#333333", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "description", + "elementType": "text", + "bindings": { + "text": "Tests aspect ratio with MATCH_PARENT dimensions. Should fill parent while maintaining aspect ratio where applicable." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 12, + "textColor": "#666666", + "lineHeight": 17 + } + }, + { + "type": "container", + "id": "fixed-container", + "containerType": "box", + "layout": { + "width": { + "value": 300, + "unit": "dp" + }, + "height": { + "value": 200, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#E3F2FD", + "borderWidth": 2, + "borderColor": "#2196F3" + }, + "children": [ + { + "type": "container", + "id": "match-width-with-ratio", + "containerType": "box", + "layout": { + "width": { + "special": "match_parent" + }, + "aspectRatio": 1.5 + }, + "style": { + "backgroundColor": "#2196F3", + "borderRadius": 8 + }, + "children": [ + { + "type": "element", + "id": "match-text", + "elementType": "text", + "bindings": { + "text": "MATCH_PARENT width\n1.5:1 aspect ratio" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16, + "unit": "dp" + } + }, + "style": { + "fontSize": 14, + "textColor": "#FFFFFF", + "textAlign": "center", + "lineHeight": 20 + } + } + ] + } + ] + }, + { + "type": "container", + "id": "tall-container", + "containerType": "box", + "layout": { + "width": { + "value": 200, + "unit": "dp" + }, + "height": { + "value": 300, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#E8F5E9", + "borderWidth": 2, + "borderColor": "#4CAF50" + }, + "children": [ + { + "type": "container", + "id": "match-height-with-ratio", + "containerType": "box", + "layout": { + "height": { + "special": "match_parent" + }, + "aspectRatio": 0.75 + }, + "style": { + "backgroundColor": "#4CAF50", + "borderRadius": 8 + }, + "children": [ + { + "type": "element", + "id": "match-text-2", + "elementType": "text", + "bindings": { + "text": "MATCH_PARENT height\n3:4 portrait ratio" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16, + "unit": "dp" + } + }, + "style": { + "fontSize": 14, + "textColor": "#FFFFFF", + "textAlign": "center", + "lineHeight": 20 + } + } + ] + } + ] + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-108-aspect-ratio-extreme-wide.json b/flutter-sample/assets/test-configs/test-108-aspect-ratio-extreme-wide.json new file mode 100644 index 0000000..1c5dc65 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-108-aspect-ratio-extreme-wide.json @@ -0,0 +1,308 @@ +{ + "theme": { + "id": "test-108", + "defaultStyle": { + "textColor": "#000000", + "fontSize": 14, + "lineHeight": 20 + } + }, + "variables": { + "testName": "Aspect Ratio - Extreme Wide (10:1, 20:1)" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16, + "unit": "dp" + }, + "arrangement": { + "spacing": 16, + "spacingUnit": "dp", + "strategy": "spaced" + } + }, + "style": { + "backgroundColor": "#F5F5F5" + }, + "children": [ + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "{{testName}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "textColor": "#333333", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "description", + "elementType": "text", + "bindings": { + "text": "Tests extremely wide aspect ratios. Useful for banners, progress bars, and ultra-wide elements." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 12, + "textColor": "#666666", + "lineHeight": 17 + } + }, + { + "type": "container", + "id": "ratio-5-1", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 5.0, + "padding": { + "all": 12, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#2196F3", + "borderRadius": 4 + }, + "children": [ + { + "type": "element", + "id": "text-5-1", + "elementType": "text", + "bindings": { + "text": "5:1 Aspect Ratio - Wide Banner" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "textAlign": "center", + "lineHeight": 20 + } + } + ] + }, + { + "type": "container", + "id": "ratio-10-1", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 10.0, + "padding": { + "vertical": 8, + "horizontal": 16, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#4CAF50", + "borderRadius": 4 + }, + "children": [ + { + "type": "element", + "id": "text-10-1", + "elementType": "text", + "bindings": { + "text": "10:1 Aspect Ratio - Progress Bar" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 12, + "textColor": "#FFFFFF", + "textAlign": "center", + "lineHeight": 17 + } + } + ] + }, + { + "type": "container", + "id": "ratio-15-1", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 15.0, + "padding": { + "vertical": 6, + "horizontal": 12, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF9800", + "borderRadius": 4 + }, + "children": [ + { + "type": "element", + "id": "text-15-1", + "elementType": "text", + "bindings": { + "text": "15:1 - Ultra-wide Divider Bar" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 11, + "textColor": "#FFFFFF", + "textAlign": "center", + "lineHeight": 15 + } + } + ] + }, + { + "type": "container", + "id": "ratio-20-1", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 20.0, + "padding": { + "vertical": 4, + "horizontal": 8, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#F44336", + "borderRadius": 2 + }, + "children": [ + { + "type": "element", + "id": "text-20-1", + "elementType": "text", + "bindings": { + "text": "20:1 Extreme" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 10, + "textColor": "#FFFFFF", + "textAlign": "center", + "lineHeight": 14 + } + } + ] + }, + { + "type": "container", + "id": "progress-bar-example", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "arrangement": { + "spacing": 8, + "spacingUnit": "dp", + "strategy": "spaced" + } + }, + "children": [ + { + "type": "element", + "id": "progress-label", + "elementType": "text", + "bindings": { + "text": "Progress Bar Example" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 12, + "fontWeight": "bold", + "textColor": "#666666", + "lineHeight": 17 + } + }, + { + "type": "container", + "id": "progress-bar", + "containerType": "box", + "layout": { + "width": { + "value": 70, + "unit": "percent" + }, + "aspectRatio": 12.0 + }, + "style": { + "backgroundColor": "#9C27B0", + "borderRadius": 4 + }, + "children": [] + } + ] + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-109-aspect-ratio-extreme-tall.json b/flutter-sample/assets/test-configs/test-109-aspect-ratio-extreme-tall.json new file mode 100644 index 0000000..356becb --- /dev/null +++ b/flutter-sample/assets/test-configs/test-109-aspect-ratio-extreme-tall.json @@ -0,0 +1,398 @@ +{ + "theme": { + "id": "test-109", + "defaultStyle": { + "textColor": "#000000", + "fontSize": 14, + "lineHeight": 20 + } + }, + "variables": { + "testName": "Aspect Ratio - Extreme Tall (1:10, 1:20)" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16, + "unit": "dp" + }, + "arrangement": { + "spacing": 16, + "spacingUnit": "dp", + "strategy": "spaced" + } + }, + "style": { + "backgroundColor": "#F5F5F5" + }, + "children": [ + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "{{testName}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "textColor": "#333333", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "description", + "elementType": "text", + "bindings": { + "text": "Tests extremely tall aspect ratios. Useful for vertical progress bars, sidebars, and narrow columns." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 12, + "textColor": "#666666", + "lineHeight": 17 + } + }, + { + "type": "container", + "id": "tall-bars-container", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "arrangement": { + "spacing": 16, + "spacingUnit": "dp", + "strategy": "center" + } + }, + "children": [ + { + "type": "container", + "id": "ratio-1-5", + "containerType": "box", + "layout": { + "width": { + "value": 60, + "unit": "dp" + }, + "aspectRatio": 0.2, + "padding": { + "vertical": 16, + "horizontal": 8, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#2196F3", + "borderRadius": 4 + }, + "children": [ + { + "type": "element", + "id": "text-1-5", + "elementType": "text", + "bindings": { + "text": "1:5" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 10, + "textColor": "#FFFFFF", + "textAlign": "center", + "lineHeight": 14 + } + } + ] + }, + { + "type": "container", + "id": "ratio-1-8", + "containerType": "box", + "layout": { + "width": { + "value": 50, + "unit": "dp" + }, + "aspectRatio": 0.125, + "padding": { + "vertical": 16, + "horizontal": 6, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#4CAF50", + "borderRadius": 4 + }, + "children": [ + { + "type": "element", + "id": "text-1-8", + "elementType": "text", + "bindings": { + "text": "1:8" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 9, + "textColor": "#FFFFFF", + "textAlign": "center", + "lineHeight": 13 + } + } + ] + }, + { + "type": "container", + "id": "ratio-1-10", + "containerType": "box", + "layout": { + "width": { + "value": 40, + "unit": "dp" + }, + "aspectRatio": 0.1, + "padding": { + "vertical": 16, + "horizontal": 4, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF9800", + "borderRadius": 4 + }, + "children": [ + { + "type": "element", + "id": "text-1-10", + "elementType": "text", + "bindings": { + "text": "1:10" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 8, + "textColor": "#FFFFFF", + "textAlign": "center", + "lineHeight": 11 + } + } + ] + }, + { + "type": "container", + "id": "ratio-1-15", + "containerType": "box", + "layout": { + "width": { + "value": 30, + "unit": "dp" + }, + "aspectRatio": 0.0667, + "padding": { + "vertical": 16, + "horizontal": 3, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#F44336", + "borderRadius": 4 + }, + "children": [ + { + "type": "element", + "id": "text-1-15", + "elementType": "text", + "bindings": { + "text": "1:15" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 7, + "textColor": "#FFFFFF", + "textAlign": "center", + "lineHeight": 10 + } + } + ] + }, + { + "type": "container", + "id": "ratio-1-20", + "containerType": "box", + "layout": { + "width": { + "value": 20, + "unit": "dp" + }, + "aspectRatio": 0.05, + "padding": { + "vertical": 16, + "horizontal": 2, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#9C27B0", + "borderRadius": 2 + }, + "children": [ + { + "type": "element", + "id": "text-1-20", + "elementType": "text", + "bindings": { + "text": "1:20" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 6, + "textColor": "#FFFFFF", + "textAlign": "center", + "lineHeight": 8 + } + } + ] + } + ] + }, + { + "type": "container", + "id": "sidebar-example", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 280, + "unit": "dp" + }, + "arrangement": { + "spacing": 12, + "spacingUnit": "dp", + "strategy": "start" + } + }, + "children": [ + { + "type": "container", + "id": "sidebar", + "containerType": "box", + "layout": { + "width": { + "value": 50, + "unit": "dp" + }, + "aspectRatio": 0.15 + }, + "style": { + "backgroundColor": "#607D8B", + "borderRadius": 4 + }, + "children": [] + }, + { + "type": "container", + "id": "content-area", + "containerType": "box", + "layout": { + "width": { + "value": 250, + "unit": "dp" + }, + "height": { + "special": "match_parent" + }, + "padding": { + "all": 16, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 8, + "borderWidth": 1, + "borderColor": "#E0E0E0" + }, + "children": [ + { + "type": "element", + "id": "content-text", + "elementType": "text", + "bindings": { + "text": "Main content area with tall sidebar (1:6.7 aspect ratio)" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 14, + "textColor": "#333333", + "lineHeight": 20 + } + } + ] + } + ] + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-110-aspect-ratio-mixed-container.json b/flutter-sample/assets/test-configs/test-110-aspect-ratio-mixed-container.json new file mode 100644 index 0000000..3809fec --- /dev/null +++ b/flutter-sample/assets/test-configs/test-110-aspect-ratio-mixed-container.json @@ -0,0 +1,407 @@ +{ + "theme": { + "id": "test-110", + "defaultStyle": { + "textColor": "#000000", + "fontSize": 14, + "lineHeight": 20 + } + }, + "variables": { + "testName": "Aspect Ratio - Mixed Container with Multiple Ratios" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16, + "unit": "dp" + }, + "arrangement": { + "spacing": 16, + "spacingUnit": "dp", + "strategy": "spaced" + } + }, + "style": { + "backgroundColor": "#F5F5F5" + }, + "children": [ + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "{{testName}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "textColor": "#333333", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "description", + "elementType": "text", + "bindings": { + "text": "Tests multiple elements with different aspect ratios in a single container. Verifies that different ratios coexist correctly." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 12, + "textColor": "#666666", + "lineHeight": 17 + } + }, + { + "type": "container", + "id": "mixed-grid", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "arrangement": { + "spacing": 12, + "spacingUnit": "dp", + "strategy": "space_between" + } + }, + "children": [ + { + "type": "container", + "id": "square-box", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "dp" + }, + "aspectRatio": 1.0 + }, + "style": { + "backgroundColor": "#2196F3", + "borderRadius": 8 + }, + "children": [ + { + "type": "element", + "id": "square-text", + "elementType": "text", + "bindings": { + "text": "1:1" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 8, + "unit": "dp" + } + }, + "style": { + "fontSize": 12, + "textColor": "#FFFFFF", + "textAlign": "center", + "lineHeight": 17 + } + } + ] + }, + { + "type": "container", + "id": "portrait-box", + "containerType": "box", + "layout": { + "width": { + "value": 80, + "unit": "dp" + }, + "aspectRatio": 0.75 + }, + "style": { + "backgroundColor": "#4CAF50", + "borderRadius": 8 + }, + "children": [ + { + "type": "element", + "id": "portrait-text", + "elementType": "text", + "bindings": { + "text": "3:4" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 8, + "unit": "dp" + } + }, + "style": { + "fontSize": 12, + "textColor": "#FFFFFF", + "textAlign": "center", + "lineHeight": 17 + } + } + ] + }, + { + "type": "container", + "id": "landscape-box", + "containerType": "box", + "layout": { + "width": { + "value": 120, + "unit": "dp" + }, + "aspectRatio": 1.5 + }, + "style": { + "backgroundColor": "#FF9800", + "borderRadius": 8 + }, + "children": [ + { + "type": "element", + "id": "landscape-text", + "elementType": "text", + "bindings": { + "text": "3:2" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 8, + "unit": "dp" + } + }, + "style": { + "fontSize": 12, + "textColor": "#FFFFFF", + "textAlign": "center", + "lineHeight": 17 + } + } + ] + } + ] + }, + { + "type": "container", + "id": "widescreen-banner", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 1.777, + "padding": { + "all": 16, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#9C27B0", + "borderRadius": 8 + }, + "children": [ + { + "type": "element", + "id": "banner-text", + "elementType": "text", + "bindings": { + "text": "16:9 Widescreen Banner" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "textAlign": "center", + "lineHeight": 25 + } + } + ] + }, + { + "type": "container", + "id": "complex-layout", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "arrangement": { + "spacing": 12, + "spacingUnit": "dp", + "strategy": "start" + } + }, + "children": [ + { + "type": "container", + "id": "vertical-sidebar", + "containerType": "box", + "layout": { + "width": { + "value": 60, + "unit": "dp" + }, + "aspectRatio": 0.3 + }, + "style": { + "backgroundColor": "#607D8B", + "borderRadius": 8 + }, + "children": [] + }, + { + "type": "container", + "id": "content-cards", + "containerType": "vertical", + "layout": { + "width": { + "value": 250, + "unit": "dp" + }, + "arrangement": { + "spacing": 8, + "spacingUnit": "dp", + "strategy": "spaced" + } + }, + "children": [ + { + "type": "container", + "id": "card-1", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 2.5, + "padding": { + "all": 12, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 6, + "borderWidth": 1, + "borderColor": "#E0E0E0" + }, + "children": [ + { + "type": "element", + "id": "card-1-text", + "elementType": "text", + "bindings": { + "text": "Card 1 - 2.5:1 ratio" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 12, + "textColor": "#333333", + "lineHeight": 17 + } + } + ] + }, + { + "type": "container", + "id": "card-2", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 2.5, + "padding": { + "all": 12, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 6, + "borderWidth": 1, + "borderColor": "#E0E0E0" + }, + "children": [ + { + "type": "element", + "id": "card-2-text", + "elementType": "text", + "bindings": { + "text": "Card 2 - 2.5:1 ratio" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 12, + "textColor": "#333333", + "lineHeight": 17 + } + } + ] + } + ] + } + ] + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-111-combined-aspect-offset-box.json b/flutter-sample/assets/test-configs/test-111-combined-aspect-offset-box.json new file mode 100644 index 0000000..af54148 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-111-combined-aspect-offset-box.json @@ -0,0 +1,215 @@ +{ + "theme": { + "id": "test-111", + "defaultStyle": { + "textColor": "#000000", + "fontSize": 14, + "lineHeight": 20 + } + }, + "variables": { + "testName": "Combined - Aspect Ratio + Percentage Offset in Box" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16, + "unit": "dp" + }, + "arrangement": { + "spacing": 16, + "spacingUnit": "dp", + "strategy": "spaced" + } + }, + "style": { + "backgroundColor": "#F5F5F5" + }, + "children": [ + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "{{testName}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "textColor": "#333333", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "description", + "elementType": "text", + "bindings": { + "text": "Tests combining aspect ratio with percentage-based offset in Box containers. Elements maintain their aspect ratios while positioned at specific percentages." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 12, + "textColor": "#666666", + "lineHeight": 17 + } + }, + { + "type": "container", + "id": "demo-box", + "containerType": "box", + "layout": { + "width": { + "value": 320, + "unit": "dp" + }, + "height": { + "value": 320, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderWidth": 2, + "borderColor": "#000000" + }, + "children": [ + { + "type": "container", + "id": "square-top-left", + "containerType": "box", + "layout": { + "width": { + "value": 80, + "unit": "dp" + }, + "aspectRatio": 1.0, + "offset": { + "x": 5, + "y": 5, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#2196F3", + "borderRadius": 8 + }, + "children": [] + }, + { + "type": "container", + "id": "wide-center", + "containerType": "box", + "layout": { + "width": { + "value": 180, + "unit": "dp" + }, + "aspectRatio": 2.5, + "offset": { + "x": 25, + "y": 40, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#4CAF50", + "borderRadius": 8, + "opacity": 0.9 + }, + "children": [] + }, + { + "type": "container", + "id": "portrait-right", + "containerType": "box", + "layout": { + "width": { + "value": 70, + "unit": "dp" + }, + "aspectRatio": 0.6, + "offset": { + "x": 75, + "y": 10, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#FF9800", + "borderRadius": 8 + }, + "children": [] + }, + { + "type": "container", + "id": "square-bottom-right", + "containerType": "box", + "layout": { + "width": { + "value": 90, + "unit": "dp" + }, + "aspectRatio": 1.0, + "offset": { + "x": 70, + "y": 70, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#F44336", + "borderRadius": 8 + }, + "children": [] + }, + { + "type": "container", + "id": "16-9-center", + "containerType": "box", + "layout": { + "width": { + "value": 160, + "unit": "dp" + }, + "aspectRatio": 1.777, + "offset": { + "x": 35, + "y": 55, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#9C27B0", + "borderRadius": 8, + "opacity": 0.85 + }, + "children": [] + } + ] + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-112-combined-nested-complex.json b/flutter-sample/assets/test-configs/test-112-combined-nested-complex.json new file mode 100644 index 0000000..508b083 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-112-combined-nested-complex.json @@ -0,0 +1,301 @@ +{ + "theme": { + "id": "test-112", + "defaultStyle": { + "textColor": "#000000", + "fontSize": 14, + "lineHeight": 20 + } + }, + "variables": { + "testName": "Combined - Nested Complex Layout" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16, + "unit": "dp" + }, + "arrangement": { + "spacing": 16, + "spacingUnit": "dp", + "strategy": "spaced" + } + }, + "style": { + "backgroundColor": "#F5F5F5" + }, + "children": [ + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "{{testName}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "textColor": "#333333", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "description", + "elementType": "text", + "bindings": { + "text": "Tests complex nested containers with both aspect ratios and percentage offsets at multiple levels." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 12, + "textColor": "#666666", + "lineHeight": 17 + } + }, + { + "type": "container", + "id": "outer-container", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 1.5 + }, + "style": { + "backgroundColor": "#E3F2FD", + "borderRadius": 12, + "borderWidth": 3, + "borderColor": "#2196F3" + }, + "children": [ + { + "type": "container", + "id": "level-1-box", + "containerType": "box", + "layout": { + "width": { + "value": 70, + "unit": "percent" + }, + "aspectRatio": 1.333, + "offset": { + "x": 15, + "y": 10, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#E8F5E9", + "borderRadius": 10, + "borderWidth": 2, + "borderColor": "#4CAF50" + }, + "children": [ + { + "type": "container", + "id": "level-2-left", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "dp" + }, + "aspectRatio": 1.0, + "offset": { + "x": 5, + "y": 15, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#FFF3E0", + "borderRadius": 8, + "borderWidth": 2, + "borderColor": "#FF9800" + }, + "children": [ + { + "type": "container", + "id": "level-3-inner", + "containerType": "box", + "layout": { + "width": { + "value": 50, + "unit": "percent" + }, + "aspectRatio": 1.0, + "offset": { + "x": 25, + "y": 25, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#FFEBEE", + "borderRadius": 6, + "borderWidth": 2, + "borderColor": "#F44336" + }, + "children": [] + } + ] + }, + { + "type": "container", + "id": "level-2-right", + "containerType": "box", + "layout": { + "width": { + "value": 120, + "unit": "dp" + }, + "aspectRatio": 0.75, + "offset": { + "x": 60, + "y": 30, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#F3E5F5", + "borderRadius": 8, + "borderWidth": 2, + "borderColor": "#9C27B0" + }, + "children": [] + } + ] + } + ] + }, + { + "type": "container", + "id": "horizontal-mix", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "arrangement": { + "spacing": 12, + "spacingUnit": "dp", + "strategy": "space_between" + } + }, + "children": [ + { + "type": "container", + "id": "card-1-outer", + "containerType": "box", + "layout": { + "width": { + "value": 150, + "unit": "dp" + }, + "aspectRatio": 1.2 + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 8, + "borderWidth": 1, + "borderColor": "#E0E0E0" + }, + "children": [ + { + "type": "container", + "id": "card-1-image", + "containerType": "box", + "layout": { + "width": { + "value": 80, + "unit": "percent" + }, + "aspectRatio": 1.0, + "offset": { + "x": 10, + "y": 5, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#2196F3", + "borderRadius": 6 + }, + "children": [] + } + ] + }, + { + "type": "container", + "id": "card-2-outer", + "containerType": "box", + "layout": { + "width": { + "value": 150, + "unit": "dp" + }, + "aspectRatio": 1.2 + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 8, + "borderWidth": 1, + "borderColor": "#E0E0E0" + }, + "children": [ + { + "type": "container", + "id": "card-2-image", + "containerType": "box", + "layout": { + "width": { + "value": 80, + "unit": "percent" + }, + "aspectRatio": 1.0, + "offset": { + "x": 10, + "y": 5, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#4CAF50", + "borderRadius": 6 + }, + "children": [] + } + ] + } + ] + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-113-combined-gallery-aspect-peek.json b/flutter-sample/assets/test-configs/test-113-combined-gallery-aspect-peek.json new file mode 100644 index 0000000..9e69b2c --- /dev/null +++ b/flutter-sample/assets/test-configs/test-113-combined-gallery-aspect-peek.json @@ -0,0 +1,334 @@ +{ + "theme": { + "id": "test-113", + "defaultStyle": { + "textColor": "#000000", + "fontSize": 14, + "lineHeight": 20 + } + }, + "variables": { + "testName": "Combined - Gallery-Style Peek with Aspect Ratio" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16, + "unit": "dp" + }, + "arrangement": { + "spacing": 16, + "spacingUnit": "dp", + "strategy": "spaced" + } + }, + "style": { + "backgroundColor": "#F5F5F5" + }, + "children": [ + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "{{testName}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "textColor": "#333333", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "description", + "elementType": "text", + "bindings": { + "text": "Tests gallery peek effect using Stack with percentage offsets and aspect ratios. Cards maintain 16:9 ratio while layered." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 12, + "textColor": "#666666", + "lineHeight": 17 + } + }, + { + "type": "container", + "id": "gallery-peek-1", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 280, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF" + }, + "children": [ + { + "type": "container", + "id": "card-1", + "containerType": "box", + "layout": { + "width": { + "value": 75, + "unit": "percent" + }, + "aspectRatio": 1.777, + "offset": { + "x": 0, + "y": 8, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#2196F3", + "borderRadius": 12, + "shadowColor": "#00000040", + "shadowRadius": 12, + "shadowOffsetX": 0, + "shadowOffsetY": 4 + }, + "children": [ + { + "type": "element", + "id": "card-1-image", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-97.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + } + } + } + ] + }, + { + "type": "container", + "id": "card-2", + "containerType": "box", + "layout": { + "width": { + "value": 75, + "unit": "percent" + }, + "aspectRatio": 1.777, + "offset": { + "x": 12, + "y": 11, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#4CAF50", + "borderRadius": 12, + "shadowColor": "#00000040", + "shadowRadius": 12, + "shadowOffsetX": 0, + "shadowOffsetY": 4 + }, + "children": [ + { + "type": "element", + "id": "card-2-image", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-98.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + } + } + } + ] + }, + { + "type": "container", + "id": "card-3", + "containerType": "box", + "layout": { + "width": { + "value": 75, + "unit": "percent" + }, + "aspectRatio": 1.777, + "offset": { + "x": 24, + "y": 14, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#FF9800", + "borderRadius": 12, + "shadowColor": "#00000040", + "shadowRadius": 12, + "shadowOffsetX": 0, + "shadowOffsetY": 4 + }, + "children": [ + { + "type": "element", + "id": "card-3-image", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-99.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + } + } + } + ] + } + ] + }, + { + "type": "container", + "id": "gallery-peek-2", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 200, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FAFAFA" + }, + "children": [ + { + "type": "container", + "id": "square-card-1", + "containerType": "box", + "layout": { + "width": { + "value": 140, + "unit": "dp" + }, + "aspectRatio": 1.0, + "offset": { + "x": 5, + "y": 10, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#9C27B0", + "borderRadius": 8, + "shadowColor": "#00000030", + "shadowRadius": 8, + "shadowOffsetX": 0, + "shadowOffsetY": 2 + }, + "children": [] + }, + { + "type": "container", + "id": "square-card-2", + "containerType": "box", + "layout": { + "width": { + "value": 140, + "unit": "dp" + }, + "aspectRatio": 1.0, + "offset": { + "x": 35, + "y": 10, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#F44336", + "borderRadius": 8, + "shadowColor": "#00000030", + "shadowRadius": 8, + "shadowOffsetX": 0, + "shadowOffsetY": 2 + }, + "children": [] + }, + { + "type": "container", + "id": "square-card-3", + "containerType": "box", + "layout": { + "width": { + "value": 140, + "unit": "dp" + }, + "aspectRatio": 1.0, + "offset": { + "x": 65, + "y": 10, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#00BCD4", + "borderRadius": 8, + "shadowColor": "#00000030", + "shadowRadius": 8, + "shadowOffsetX": 0, + "shadowOffsetY": 2 + }, + "children": [] + } + ] + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-114-combined-product-grid.json b/flutter-sample/assets/test-configs/test-114-combined-product-grid.json new file mode 100644 index 0000000..f2da596 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-114-combined-product-grid.json @@ -0,0 +1,468 @@ +{ + "theme": { + "id": "test-114", + "defaultStyle": { + "textColor": "#000000", + "fontSize": 14, + "lineHeight": 20 + } + }, + "variables": { + "testName": "Combined - E-Commerce Product Grid" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16, + "unit": "dp" + }, + "arrangement": { + "spacing": 16, + "spacingUnit": "dp", + "strategy": "spaced" + } + }, + "style": { + "backgroundColor": "#F5F5F5" + }, + "children": [ + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "{{testName}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "textColor": "#333333", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "description", + "elementType": "text", + "bindings": { + "text": "Tests realistic e-commerce product grid using aspect ratios for product images and percentage positioning for badges." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 12, + "textColor": "#666666", + "lineHeight": 17 + } + }, + { + "type": "container", + "id": "product-row-1", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "arrangement": { + "spacing": 12, + "spacingUnit": "dp", + "strategy": "space_between" + } + }, + "children": [ + { + "type": "container", + "id": "product-card-1", + "containerType": "vertical", + "layout": { + "width": { + "value": 48, + "unit": "percent" + }, + "arrangement": { + "spacing": 8, + "spacingUnit": "dp", + "strategy": "spaced" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 8, + "shadowColor": "#00000020", + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 2 + }, + "children": [ + { + "type": "container", + "id": "product-1-image-container", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 1.0 + }, + "style": { + "backgroundColor": "#E0E0E0", + "borderRadius": 8 + }, + "children": [ + { + "type": "element", + "id": "product-1-image", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-100.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + } + } + }, + { + "type": "container", + "id": "badge-1", + "containerType": "box", + "layout": { + "width": { + "value": 60, + "unit": "dp" + }, + "height": { + "value": 24, + "unit": "dp" + }, + "offset": { + "x": 5, + "y": 5, + "unit": "percent" + }, + "padding": { + "horizontal": 8, + "vertical": 4, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#F44336", + "borderRadius": 4 + }, + "children": [ + { + "type": "element", + "id": "badge-1-text", + "elementType": "text", + "bindings": { + "text": "Sale" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 10, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "textAlign": "center", + "lineHeight": 14 + } + } + ] + } + ] + }, + { + "type": "element", + "id": "product-1-name", + "elementType": "text", + "bindings": { + "text": "Product Name" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "padding": { + "horizontal": 12, + "unit": "dp" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#333333", + "lineHeight": 20 + } + }, + { + "type": "element", + "id": "product-1-price", + "elementType": "text", + "bindings": { + "text": "$99.99" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "padding": { + "horizontal": 12, + "bottom": 12, + "unit": "dp" + } + }, + "style": { + "fontSize": 16, + "fontWeight": "bold", + "textColor": "#2196F3", + "lineHeight": 22 + } + } + ] + }, + { + "type": "container", + "id": "product-card-2", + "containerType": "vertical", + "layout": { + "width": { + "value": 48, + "unit": "percent" + }, + "arrangement": { + "spacing": 8, + "spacingUnit": "dp", + "strategy": "spaced" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 8, + "shadowColor": "#00000020", + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 2 + }, + "children": [ + { + "type": "container", + "id": "product-2-image-container", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 1.0 + }, + "style": { + "backgroundColor": "#E0E0E0", + "borderRadius": 8 + }, + "children": [ + { + "type": "element", + "id": "product-2-image", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-101.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + } + } + }, + { + "type": "container", + "id": "badge-2", + "containerType": "box", + "layout": { + "width": { + "value": 60, + "unit": "dp" + }, + "height": { + "value": 24, + "unit": "dp" + }, + "offset": { + "x": 5, + "y": 5, + "unit": "percent" + }, + "padding": { + "horizontal": 8, + "vertical": 4, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#4CAF50", + "borderRadius": 4 + }, + "children": [ + { + "type": "element", + "id": "badge-2-text", + "elementType": "text", + "bindings": { + "text": "New" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 10, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "textAlign": "center", + "lineHeight": 14 + } + } + ] + } + ] + }, + { + "type": "element", + "id": "product-2-name", + "elementType": "text", + "bindings": { + "text": "Another Product" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "padding": { + "horizontal": 12, + "unit": "dp" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#333333", + "lineHeight": 20 + } + }, + { + "type": "element", + "id": "product-2-price", + "elementType": "text", + "bindings": { + "text": "$149.99" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "padding": { + "horizontal": 12, + "bottom": 12, + "unit": "dp" + } + }, + "style": { + "fontSize": 16, + "fontWeight": "bold", + "textColor": "#2196F3", + "lineHeight": 22 + } + } + ] + } + ] + }, + { + "type": "container", + "id": "banner-with-aspect", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 3.5, + "padding": { + "all": 16, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF9800", + "borderRadius": 8 + }, + "children": [ + { + "type": "element", + "id": "banner-text", + "elementType": "text", + "bindings": { + "text": "Special Offer: 50% OFF" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "textAlign": "center", + "lineHeight": 25 + } + } + ] + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-115-combined-showcase-all.json b/flutter-sample/assets/test-configs/test-115-combined-showcase-all.json new file mode 100644 index 0000000..3cbf86b --- /dev/null +++ b/flutter-sample/assets/test-configs/test-115-combined-showcase-all.json @@ -0,0 +1,480 @@ +{ + "theme": { + "id": "test-115", + "defaultStyle": { + "textColor": "#000000", + "fontSize": 14, + "lineHeight": 20 + } + }, + "variables": { + "testName": "Combined - Complete Showcase (Stress Test)" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16, + "unit": "dp" + }, + "arrangement": { + "spacing": 16, + "spacingUnit": "dp", + "strategy": "spaced" + } + }, + "style": { + "backgroundColor": "#F5F5F5" + }, + "children": [ + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "{{testName}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "textColor": "#333333", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "description", + "elementType": "text", + "bindings": { + "text": "Comprehensive stress test combining all features: aspect ratios, percentage offsets, nested layouts, multiple container types, and complex positioning." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 12, + "textColor": "#666666", + "lineHeight": 17 + } + }, + { + "type": "container", + "id": "hero-section", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 2.0 + }, + "style": { + "backgroundColor": "#2196F3", + "borderRadius": 12 + }, + "children": [ + { + "type": "element", + "id": "hero-image", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-102.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + } + } + }, + { + "type": "container", + "id": "hero-overlay", + "containerType": "box", + "layout": { + "width": { + "value": 70, + "unit": "percent" + }, + "aspectRatio": 3.0, + "offset": { + "x": 15, + "y": 30, + "unit": "percent" + }, + "padding": { + "all": 16, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#00000080", + "borderRadius": 8 + }, + "children": [ + { + "type": "element", + "id": "hero-text", + "elementType": "text", + "bindings": { + "text": "Hero Banner" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 20, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "textAlign": "center", + "lineHeight": 28 + } + } + ] + } + ] + }, + { + "type": "container", + "id": "complex-stack", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 250, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 8 + }, + "children": [ + { + "type": "container", + "id": "stack-layer-1", + "containerType": "box", + "layout": { + "width": { + "value": 60, + "unit": "percent" + }, + "aspectRatio": 1.5, + "offset": { + "x": 5, + "y": 10, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#4CAF50", + "borderRadius": 8, + "opacity": 0.9 + }, + "children": [] + }, + { + "type": "container", + "id": "stack-layer-2", + "containerType": "box", + "layout": { + "width": { + "value": 50, + "unit": "percent" + }, + "aspectRatio": 1.0, + "offset": { + "x": 35, + "y": 25, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#FF9800", + "borderRadius": 8, + "opacity": 0.9 + }, + "children": [] + }, + { + "type": "container", + "id": "stack-layer-3", + "containerType": "box", + "layout": { + "width": { + "value": 45, + "unit": "percent" + }, + "aspectRatio": 0.75, + "offset": { + "x": 50, + "y": 45, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#F44336", + "borderRadius": 8, + "opacity": 0.9 + }, + "children": [] + } + ] + }, + { + "type": "container", + "id": "grid-section", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "arrangement": { + "spacing": 12, + "spacingUnit": "dp", + "strategy": "space_evenly" + } + }, + "children": [ + { + "type": "container", + "id": "grid-item-1", + "containerType": "box", + "layout": { + "width": { + "value": 28, + "unit": "percent" + }, + "aspectRatio": 1.0 + }, + "style": { + "backgroundColor": "#9C27B0", + "borderRadius": 8 + }, + "children": [ + { + "type": "container", + "id": "grid-badge-1", + "containerType": "box", + "layout": { + "width": { + "value": 30, + "unit": "dp" + }, + "height": { + "value": 30, + "unit": "dp" + }, + "offset": { + "x": -5, + "y": -5, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#F44336", + "borderRadius": 15 + }, + "children": [] + } + ] + }, + { + "type": "container", + "id": "grid-item-2", + "containerType": "box", + "layout": { + "width": { + "value": 28, + "unit": "percent" + }, + "aspectRatio": 1.0 + }, + "style": { + "backgroundColor": "#00BCD4", + "borderRadius": 8 + }, + "children": [] + }, + { + "type": "container", + "id": "grid-item-3", + "containerType": "box", + "layout": { + "width": { + "value": 28, + "unit": "percent" + }, + "aspectRatio": 1.0 + }, + "style": { + "backgroundColor": "#FFEB3B", + "borderRadius": 8 + }, + "children": [] + } + ] + }, + { + "type": "container", + "id": "nested-complex", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 1.3 + }, + "style": { + "backgroundColor": "#E0E0E0", + "borderRadius": 8 + }, + "children": [ + { + "type": "container", + "id": "nested-level-1", + "containerType": "box", + "layout": { + "width": { + "value": 80, + "unit": "percent" + }, + "aspectRatio": 1.5, + "offset": { + "x": 10, + "y": 10, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 6 + }, + "children": [ + { + "type": "container", + "id": "nested-level-2", + "containerType": "box", + "layout": { + "width": { + "value": 60, + "unit": "percent" + }, + "aspectRatio": 1.0, + "offset": { + "x": 20, + "y": 20, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#2196F3", + "borderRadius": 4 + }, + "children": [ + { + "type": "container", + "id": "nested-level-3", + "containerType": "box", + "layout": { + "width": { + "value": 50, + "unit": "percent" + }, + "aspectRatio": 1.0, + "offset": { + "x": 25, + "y": 25, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#F44336", + "borderRadius": 2 + }, + "children": [] + } + ] + } + ] + } + ] + }, + { + "type": "container", + "id": "footer-banner", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 5.0, + "padding": { + "vertical": 12, + "horizontal": 16, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#607D8B", + "borderRadius": 8 + }, + "children": [ + { + "type": "element", + "id": "footer-text", + "elementType": "text", + "bindings": { + "text": "Complete Feature Showcase - All Tests Passed" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "textAlign": "center", + "lineHeight": 20 + } + } + ] + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-116-match-parent-comprehensive.json b/flutter-sample/assets/test-configs/test-116-match-parent-comprehensive.json new file mode 100644 index 0000000..486debc --- /dev/null +++ b/flutter-sample/assets/test-configs/test-116-match-parent-comprehensive.json @@ -0,0 +1,465 @@ +{ + "theme": { + "id": "test-116", + "defaultStyle": { + "textColor": "#FFFFFF", + "fontSize": 14, + "fontWeight": "normal", + "lineHeight": 20 + } + }, + "variables": { + "testName": "MATCH_PARENT Comprehensive Test", + "testDescription": "Demonstrates MATCH_PARENT behavior across all container types (VERTICAL, HORIZONTAL, BOX, STACK)" + }, + "root": { + "id": "root-container", + "type": "container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 600, + "unit": "dp" + }, + "padding": { + "all": 0 + }, + "arrangement": { + "spacing": 8, + "strategy": "spaced", + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#F5F5F5" + }, + "children": [ + { + "id": "section-vertical-match", + "type": "container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 130, + "unit": "dp" + }, + "padding": { + "all": 8 + }, + "arrangement": { + "spacing": 4, + "strategy": "spaced", + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#E3F2FD", + "borderRadius": 8, + "borderWidth": 2, + "borderColor": "#2196F3" + }, + "children": [ + { + "id": "label-vertical", + "type": "element", + "elementType": "text", + "bindings": { + "text": "VERTICAL: Child with MATCH_PARENT width" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 24, + "unit": "dp" + } + }, + "style": { + "textColor": "#1976D2", + "fontSize": 12, + "fontWeight": "bold", + "lineHeight": 17 + } + }, + { + "id": "child-match-vertical", + "type": "element", + "elementType": "text", + "bindings": { + "text": "I fill the full width using MATCH_PARENT" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "match_parent" + }, + "height": { + "value": 80, + "unit": "dp" + }, + "padding": { + "all": 12 + } + }, + "style": { + "backgroundColor": "#4CAF50", + "textColor": "#FFFFFF", + "fontSize": 14, + "fontWeight": "bold", + "borderRadius": 4, + "lineHeight": 20 + } + } + ] + }, + { + "id": "section-horizontal-match", + "type": "container", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 110, + "unit": "dp" + }, + "padding": { + "all": 8 + }, + "arrangement": { + "spacing": 8, + "strategy": "spaced", + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FFF3E0", + "borderRadius": 8, + "borderWidth": 2, + "borderColor": "#FF9800" + }, + "children": [ + { + "id": "horizontal-child-1", + "type": "element", + "elementType": "text", + "bindings": { + "text": "HORIZONTAL: MATCH_PARENT height fills container" + }, + "layout": { + "width": { + "value": 120, + "unit": "dp" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "match_parent" + }, + "padding": { + "all": 8 + } + }, + "style": { + "backgroundColor": "#FF5722", + "textColor": "#FFFFFF", + "fontSize": 11, + "fontWeight": "bold", + "borderRadius": 4, + "lineHeight": 15 + } + }, + { + "id": "horizontal-child-2", + "type": "element", + "elementType": "text", + "bindings": { + "text": "Also MATCH_PARENT height" + }, + "layout": { + "width": { + "value": 100, + "unit": "dp" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "match_parent" + }, + "padding": { + "all": 8 + } + }, + "style": { + "backgroundColor": "#FFC107", + "textColor": "#000000", + "fontSize": 11, + "fontWeight": "bold", + "borderRadius": 4, + "lineHeight": 15 + } + }, + { + "id": "horizontal-child-3", + "type": "element", + "elementType": "text", + "bindings": { + "text": "Full height" + }, + "layout": { + "width": { + "value": 80, + "unit": "dp" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "match_parent" + }, + "padding": { + "all": 8 + } + }, + "style": { + "backgroundColor": "#FF9800", + "textColor": "#FFFFFF", + "fontSize": 11, + "fontWeight": "bold", + "borderRadius": 4, + "lineHeight": 15 + } + } + ] + }, + { + "id": "section-box-match", + "type": "container", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 150, + "unit": "dp" + }, + "padding": { + "all": 8 + } + }, + "style": { + "backgroundColor": "#F3E5F5", + "borderRadius": 8, + "borderWidth": 2, + "borderColor": "#9C27B0" + }, + "children": [ + { + "id": "box-background", + "type": "element", + "elementType": "text", + "bindings": { + "text": "BOX: Background with MATCH_PARENT fills entire container" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "match_parent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "match_parent" + }, + "padding": { + "all": 16 + } + }, + "style": { + "backgroundColor": "#9C27B0AA", + "textColor": "#FFFFFF", + "fontSize": 13, + "fontWeight": "bold", + "borderRadius": 4, + "lineHeight": 18 + } + }, + { + "id": "box-overlay", + "type": "element", + "elementType": "text", + "bindings": { + "text": "Overlay" + }, + "layout": { + "width": { + "value": 80, + "unit": "dp" + }, + "height": { + "value": 40, + "unit": "dp" + }, + "padding": { + "all": 8 + }, + "offset": { + "x": 10, + "y": 10, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#E91E63", + "textColor": "#FFFFFF", + "fontSize": 11, + "fontWeight": "bold", + "borderRadius": 4, + "lineHeight": 15 + } + } + ] + }, + { + "id": "section-stack-match", + "type": "container", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 180, + "unit": "dp" + }, + "padding": { + "all": 8 + } + }, + "style": { + "backgroundColor": "#E8F5E9", + "borderRadius": 8, + "borderWidth": 2, + "borderColor": "#4CAF50" + }, + "children": [ + { + "id": "stack-layer-1", + "type": "element", + "elementType": "text", + "bindings": { + "text": "STACK: Layer 1 - MATCH_PARENT fills base" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "match_parent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "match_parent" + }, + "padding": { + "all": 20 + } + }, + "style": { + "backgroundColor": "#4CAF50", + "textColor": "#FFFFFF", + "fontSize": 13, + "fontWeight": "bold", + "borderRadius": 4, + "lineHeight": 18 + } + }, + { + "id": "stack-layer-2", + "type": "element", + "elementType": "text", + "bindings": { + "text": "Layer 2 - Partial overlay" + }, + "layout": { + "width": { + "value": 180, + "unit": "dp" + }, + "height": { + "value": 60, + "unit": "dp" + }, + "padding": { + "all": 12 + }, + "offset": { + "x": 50, + "y": 50, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#8BC34AAA", + "textColor": "#000000", + "fontSize": 12, + "fontWeight": "bold", + "borderRadius": 4, + "lineHeight": 17 + } + }, + { + "id": "stack-layer-3", + "type": "element", + "elementType": "text", + "bindings": { + "text": "Badge" + }, + "layout": { + "width": { + "value": 70, + "unit": "dp" + }, + "height": { + "value": 30, + "unit": "dp" + }, + "padding": { + "all": 6 + }, + "offset": { + "x": 10, + "y": 10, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF5722", + "textColor": "#FFFFFF", + "fontSize": 11, + "fontWeight": "bold", + "borderRadius": 15, + "lineHeight": 15 + } + } + ] + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-117-wrap-content-comprehensive.json b/flutter-sample/assets/test-configs/test-117-wrap-content-comprehensive.json new file mode 100644 index 0000000..4032d72 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-117-wrap-content-comprehensive.json @@ -0,0 +1,624 @@ +{ + "theme": { + "id": "test-117", + "defaultStyle": { + "textColor": "#000000", + "fontSize": 14, + "fontWeight": "normal", + "lineHeight": 20 + } + }, + "variables": { + "testName": "WRAP_CONTENT Comprehensive Test", + "testDescription": "Demonstrates WRAP_CONTENT behavior with various content types and containers" + }, + "root": { + "id": "root-container", + "type": "container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 600, + "unit": "dp" + }, + "padding": { + "all": 0 + }, + "arrangement": { + "spacing": 8, + "strategy": "spaced", + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FAFAFA" + }, + "children": [ + { + "id": "section-text-wrap", + "type": "container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 8 + }, + "arrangement": { + "spacing": 8, + "strategy": "spaced", + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#E3F2FD", + "borderRadius": 8, + "borderWidth": 2, + "borderColor": "#2196F3" + }, + "children": [ + { + "id": "label-text-wrap", + "type": "element", + "elementType": "text", + "bindings": { + "text": "TEXT: WRAP_CONTENT wraps around text" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 0 + } + }, + "style": { + "textColor": "#1976D2", + "fontSize": 12, + "fontWeight": "bold", + "lineHeight": 17 + } + }, + { + "id": "short-text-wrap", + "type": "element", + "elementType": "text", + "bindings": { + "text": "Short" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 8 + } + }, + "style": { + "backgroundColor": "#4CAF50", + "textColor": "#FFFFFF", + "fontSize": 14, + "fontWeight": "bold", + "borderRadius": 4, + "lineHeight": 20 + } + }, + { + "id": "medium-text-wrap", + "type": "element", + "elementType": "text", + "bindings": { + "text": "Medium length text wraps content" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 10 + } + }, + "style": { + "backgroundColor": "#2196F3", + "textColor": "#FFFFFF", + "fontSize": 14, + "fontWeight": "bold", + "borderRadius": 4, + "lineHeight": 20 + } + } + ] + }, + { + "id": "section-button-wrap", + "type": "container", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 8 + }, + "arrangement": { + "spacing": 8, + "strategy": "spaced", + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FFF3E0", + "borderRadius": 8, + "borderWidth": 2, + "borderColor": "#FF9800" + }, + "children": [ + { + "id": "label-button-wrap", + "type": "element", + "elementType": "text", + "bindings": { + "text": "BUTTONS: WRAP_CONTENT adapts to button text" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 0 + } + }, + "style": { + "textColor": "#F57C00", + "fontSize": 12, + "fontWeight": "bold", + "lineHeight": 17 + } + } + ] + }, + { + "id": "section-buttons-row", + "type": "container", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "top": 0, + "right": 8, + "bottom": 8, + "left": 8 + }, + "arrangement": { + "spacing": 8, + "strategy": "spaced", + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FFF3E0" + }, + "children": [ + { + "id": "btn-small", + "type": "element", + "elementType": "button", + "bindings": { + "text": "OK" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "top": 8, + "right": 16, + "bottom": 8, + "left": 16 + } + }, + "style": { + "backgroundColor": "#FF9800", + "textColor": "#FFFFFF", + "fontSize": 13, + "fontWeight": "bold", + "borderRadius": 4, + "lineHeight": 18 + } + }, + { + "id": "btn-medium", + "type": "element", + "elementType": "button", + "bindings": { + "text": "Cancel" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "top": 8, + "right": 16, + "bottom": 8, + "left": 16 + } + }, + "style": { + "backgroundColor": "#FFC107", + "textColor": "#000000", + "fontSize": 13, + "fontWeight": "bold", + "borderRadius": 4, + "lineHeight": 18 + } + }, + { + "id": "btn-large", + "type": "element", + "elementType": "button", + "bindings": { + "text": "Submit Form" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "top": 10, + "right": 20, + "bottom": 10, + "left": 20 + } + }, + "style": { + "backgroundColor": "#FF5722", + "textColor": "#FFFFFF", + "fontSize": 13, + "fontWeight": "bold", + "borderRadius": 4, + "lineHeight": 18 + } + } + ] + }, + { + "id": "section-image-wrap", + "type": "container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 8 + }, + "arrangement": { + "spacing": 8, + "strategy": "spaced", + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#F3E5F5", + "borderRadius": 8, + "borderWidth": 2, + "borderColor": "#9C27B0" + }, + "children": [ + { + "id": "label-image-wrap", + "type": "element", + "elementType": "text", + "bindings": { + "text": "IMAGE: WRAP_CONTENT wraps around image" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 0 + } + }, + "style": { + "textColor": "#7B1FA2", + "fontSize": 12, + "fontWeight": "bold", + "lineHeight": 17 + } + }, + { + "id": "image-wrap", + "type": "element", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-103.jpg" + }, + "layout": { + "width": { + "value": 200, + "unit": "dp" + }, + "height": { + "value": 100, + "unit": "dp" + } + }, + "style": { + "borderRadius": 8 + } + } + ] + }, + { + "id": "section-container-wrap", + "type": "container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 8 + }, + "arrangement": { + "spacing": 8, + "strategy": "spaced", + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#E8F5E9", + "borderRadius": 8, + "borderWidth": 2, + "borderColor": "#4CAF50" + }, + "children": [ + { + "id": "label-container-wrap", + "type": "element", + "elementType": "text", + "bindings": { + "text": "CONTAINER: WRAP_CONTENT wraps all children" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 0 + } + }, + "style": { + "textColor": "#388E3C", + "fontSize": 12, + "fontWeight": "bold", + "lineHeight": 17 + } + }, + { + "id": "nested-wrap-container", + "type": "container", + "containerType": "horizontal", + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 8 + }, + "arrangement": { + "spacing": 8, + "strategy": "spaced", + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#4CAF50AA", + "borderRadius": 4 + }, + "children": [ + { + "id": "wrap-child-1", + "type": "element", + "elementType": "text", + "bindings": { + "text": "One" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 8 + } + }, + "style": { + "backgroundColor": "#8BC34A", + "textColor": "#FFFFFF", + "fontSize": 13, + "fontWeight": "bold", + "borderRadius": 4, + "lineHeight": 18 + } + }, + { + "id": "wrap-child-2", + "type": "element", + "elementType": "text", + "bindings": { + "text": "Two" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 8 + } + }, + "style": { + "backgroundColor": "#CDDC39", + "textColor": "#000000", + "fontSize": 13, + "fontWeight": "bold", + "borderRadius": 4, + "lineHeight": 18 + } + }, + { + "id": "wrap-child-3", + "type": "element", + "elementType": "text", + "bindings": { + "text": "Three" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 8 + } + }, + "style": { + "backgroundColor": "#FFC107", + "textColor": "#000000", + "fontSize": 13, + "fontWeight": "bold", + "borderRadius": 4, + "lineHeight": 18 + } + } + ] + } + ] + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-118-mixed-special-dimensions.json b/flutter-sample/assets/test-configs/test-118-mixed-special-dimensions.json new file mode 100644 index 0000000..0e3ef61 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-118-mixed-special-dimensions.json @@ -0,0 +1,742 @@ +{ + "theme": { + "id": "test-118", + "defaultStyle": { + "textColor": "#000000", + "fontSize": 14, + "fontWeight": "normal", + "lineHeight": 20 + } + }, + "variables": { + "testName": "Mixed Special Dimensions Test", + "testDescription": "Demonstrates mixed special dimensions (MATCH_PARENT + WRAP_CONTENT) in practical layouts", + "userName": "John Doe", + "userEmail": "john@example.com", + "cardTitle": "Product Card", + "cardPrice": "$29.99" + }, + "root": { + "id": "root-container", + "type": "container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 600, + "unit": "dp" + }, + "padding": { + "all": 0 + }, + "arrangement": { + "spacing": 12, + "strategy": "spaced", + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF" + }, + "children": [ + { + "id": "card-example", + "type": "container", + "containerType": "vertical", + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "match_parent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 12 + }, + "arrangement": { + "spacing": 8, + "strategy": "spaced", + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 8, + "borderWidth": 1, + "borderColor": "#E0E0E0", + "shadowRadius": 4, + "shadowOffsetX": 0, + "shadowOffsetY": 2, + "shadowColor": "#00000033" + }, + "children": [ + { + "id": "card-image", + "type": "element", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-104.jpg" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "match_parent" + }, + "height": { + "value": 150, + "unit": "dp" + } + }, + "style": { + "borderRadius": 4 + } + }, + { + "id": "card-title", + "type": "element", + "elementType": "text", + "bindings": { + "text": "{{cardTitle}}" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "match_parent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 0 + } + }, + "style": { + "textColor": "#212121", + "fontSize": 18, + "fontWeight": "bold", + "lineHeight": 25 + } + }, + { + "id": "card-description", + "type": "element", + "elementType": "text", + "bindings": { + "text": "High-quality product with excellent features. Perfect for everyday use." + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "match_parent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 0 + } + }, + "style": { + "textColor": "#757575", + "fontSize": 14, + "lineHeight": 20 + } + }, + { + "id": "card-footer", + "type": "container", + "containerType": "horizontal", + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "match_parent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "top": 8, + "right": 0, + "bottom": 0, + "left": 0 + }, + "arrangement": { + "strategy": "space_between" + } + }, + "style": {}, + "children": [ + { + "id": "card-price", + "type": "element", + "elementType": "text", + "bindings": { + "text": "{{cardPrice}}" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 0 + } + }, + "style": { + "textColor": "#4CAF50", + "fontSize": 20, + "fontWeight": "bold", + "lineHeight": 28 + } + }, + { + "id": "card-button", + "type": "element", + "elementType": "button", + "bindings": { + "text": "Add to Cart" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "top": 8, + "right": 16, + "bottom": 8, + "left": 16 + } + }, + "style": { + "backgroundColor": "#2196F3", + "textColor": "#FFFFFF", + "fontSize": 13, + "fontWeight": "bold", + "borderRadius": 4, + "lineHeight": 18 + } + } + ] + } + ] + }, + { + "id": "form-example", + "type": "container", + "containerType": "vertical", + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "match_parent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "spacing": 12, + "strategy": "spaced", + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#F5F5F5", + "borderRadius": 8 + }, + "children": [ + { + "id": "form-title", + "type": "element", + "elementType": "text", + "bindings": { + "text": "User Profile" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "match_parent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 0 + } + }, + "style": { + "textColor": "#212121", + "fontSize": 16, + "fontWeight": "bold", + "lineHeight": 22 + } + }, + { + "id": "form-field-name", + "type": "container", + "containerType": "vertical", + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "match_parent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 0 + }, + "arrangement": { + "spacing": 4, + "strategy": "spaced", + "spacingUnit": "dp" + } + }, + "style": {}, + "children": [ + { + "id": "label-name", + "type": "element", + "elementType": "text", + "bindings": { + "text": "Name" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "match_parent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 0 + } + }, + "style": { + "textColor": "#616161", + "fontSize": 12, + "fontWeight": "bold", + "lineHeight": 17 + } + }, + { + "id": "value-name", + "type": "element", + "elementType": "text", + "bindings": { + "text": "{{userName}}" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "match_parent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 12 + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "textColor": "#212121", + "fontSize": 14, + "borderRadius": 4, + "borderWidth": 1, + "borderColor": "#E0E0E0", + "lineHeight": 20 + } + } + ] + }, + { + "id": "form-field-email", + "type": "container", + "containerType": "vertical", + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "match_parent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 0 + }, + "arrangement": { + "spacing": 4, + "strategy": "spaced", + "spacingUnit": "dp" + } + }, + "style": {}, + "children": [ + { + "id": "label-email", + "type": "element", + "elementType": "text", + "bindings": { + "text": "Email" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "match_parent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 0 + } + }, + "style": { + "textColor": "#616161", + "fontSize": 12, + "fontWeight": "bold", + "lineHeight": 17 + } + }, + { + "id": "value-email", + "type": "element", + "elementType": "text", + "bindings": { + "text": "{{userEmail}}" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "match_parent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 12 + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "textColor": "#212121", + "fontSize": 14, + "borderRadius": 4, + "borderWidth": 1, + "borderColor": "#E0E0E0", + "lineHeight": 20 + } + } + ] + }, + { + "id": "form-actions", + "type": "container", + "containerType": "horizontal", + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "match_parent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "top": 8, + "right": 0, + "bottom": 0, + "left": 0 + }, + "arrangement": { + "strategy": "space_evenly" + } + }, + "style": {}, + "children": [ + { + "id": "btn-cancel", + "type": "element", + "elementType": "button", + "bindings": { + "text": "Cancel" + }, + "layout": { + "width": { + "value": 120, + "unit": "dp" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "top": 10, + "right": 16, + "bottom": 10, + "left": 16 + } + }, + "style": { + "backgroundColor": "#9E9E9E", + "textColor": "#FFFFFF", + "fontSize": 13, + "fontWeight": "bold", + "borderRadius": 4, + "lineHeight": 18 + } + }, + { + "id": "btn-save", + "type": "element", + "elementType": "button", + "bindings": { + "text": "Save" + }, + "layout": { + "width": { + "value": 120, + "unit": "dp" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "top": 10, + "right": 16, + "bottom": 10, + "left": 16 + } + }, + "style": { + "backgroundColor": "#4CAF50", + "textColor": "#FFFFFF", + "fontSize": 13, + "fontWeight": "bold", + "borderRadius": 4, + "lineHeight": 18 + } + } + ] + } + ] + }, + { + "id": "navigation-example", + "type": "container", + "containerType": "horizontal", + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "match_parent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 8 + }, + "arrangement": { + "strategy": "space_evenly" + } + }, + "style": { + "backgroundColor": "#263238", + "borderRadius": 8 + }, + "children": [ + { + "id": "nav-home", + "type": "element", + "elementType": "text", + "bindings": { + "text": "Home" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "top": 8, + "right": 16, + "bottom": 8, + "left": 16 + } + }, + "style": { + "textColor": "#FFFFFF", + "fontSize": 14, + "fontWeight": "bold", + "lineHeight": 20 + } + }, + { + "id": "nav-search", + "type": "element", + "elementType": "text", + "bindings": { + "text": "Search" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "top": 8, + "right": 16, + "bottom": 8, + "left": 16 + } + }, + "style": { + "textColor": "#FFFFFF", + "fontSize": 14, + "lineHeight": 20 + } + }, + { + "id": "nav-profile", + "type": "element", + "elementType": "text", + "bindings": { + "text": "Profile" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "top": 8, + "right": 16, + "bottom": 8, + "left": 16 + } + }, + "style": { + "textColor": "#FFFFFF", + "fontSize": 14, + "lineHeight": 20 + } + }, + { + "id": "nav-settings", + "type": "element", + "elementType": "text", + "bindings": { + "text": "Settings" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "top": 8, + "right": 16, + "bottom": 8, + "left": 16 + } + }, + "style": { + "textColor": "#FFFFFF", + "fontSize": 14, + "lineHeight": 20 + } + } + ] + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-119-match-parent-stack-box.json b/flutter-sample/assets/test-configs/test-119-match-parent-stack-box.json new file mode 100644 index 0000000..9eeab9f --- /dev/null +++ b/flutter-sample/assets/test-configs/test-119-match-parent-stack-box.json @@ -0,0 +1,576 @@ +{ + "theme": { + "id": "test-119", + "defaultStyle": { + "textColor": "#FFFFFF", + "fontSize": 14, + "fontWeight": "normal", + "lineHeight": 20 + } + }, + "variables": { + "testName": "MATCH_PARENT in Stack and Box Test", + "testDescription": "Demonstrates MATCH_PARENT behavior in overlay containers (BOX and STACK) with layering" + }, + "root": { + "id": "root-container", + "type": "container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 600, + "unit": "dp" + }, + "padding": { + "all": 0 + }, + "arrangement": { + "spacing": 12, + "strategy": "spaced", + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#263238" + }, + "children": [ + { + "id": "box-fullscreen-background", + "type": "container", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 180, + "unit": "dp" + }, + "padding": { + "all": 0 + } + }, + "style": {}, + "children": [ + { + "id": "background-layer", + "type": "element", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-105.jpg" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "match_parent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "match_parent" + } + }, + "style": {} + }, + { + "id": "overlay-gradient", + "type": "element", + "elementType": "text", + "bindings": { + "text": "" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "match_parent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "match_parent" + } + }, + "style": { + "background": { + "type": "linear_gradient", + "colors": [ + "#00000000", + "#000000CC" + ], + "angle": 180 + } + } + }, + { + "id": "overlay-title", + "type": "element", + "elementType": "text", + "bindings": { + "text": "BOX: Full-screen background with MATCH_PARENT" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "match_parent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 16 + }, + "offset": { + "x": 0, + "y": 100, + "unit": "percent", + "anchorX": 0, + "anchorY": 1 + } + }, + "style": { + "textColor": "#FFFFFF", + "fontSize": 16, + "fontWeight": "bold", + "lineHeight": 22 + } + }, + { + "id": "badge-top-right", + "type": "element", + "elementType": "text", + "bindings": { + "text": "NEW" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "top": 6, + "right": 12, + "bottom": 6, + "left": 12 + }, + "offset": { + "x": 100, + "y": 0, + "unit": "percent", + "anchorX": 1, + "anchorY": 0 + } + }, + "style": { + "backgroundColor": "#FF5722", + "textColor": "#FFFFFF", + "fontSize": 12, + "fontWeight": "bold", + "borderRadius": 8, + "lineHeight": 17 + } + } + ] + }, + { + "id": "stack-layered-content", + "type": "container", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 170, + "unit": "dp" + }, + "padding": { + "all": 0 + } + }, + "style": {}, + "children": [ + { + "id": "stack-base", + "type": "element", + "elementType": "text", + "bindings": { + "text": "" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "match_parent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "match_parent" + } + }, + "style": { + "background": { + "type": "linear_gradient", + "colors": [ + "#673AB7", + "#9C27B0" + ], + "angle": 135 + } + } + }, + { + "id": "stack-content-container", + "type": "container", + "containerType": "vertical", + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "match_parent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "match_parent" + }, + "padding": { + "all": 16 + }, + "arrangement": { + "strategy": "space_between" + } + }, + "style": {}, + "children": [ + { + "id": "stack-header", + "type": "element", + "elementType": "text", + "bindings": { + "text": "STACK: Full-sized layers with MATCH_PARENT" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "match_parent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 0 + } + }, + "style": { + "textColor": "#FFFFFF", + "fontSize": 16, + "fontWeight": "bold", + "lineHeight": 22 + } + }, + { + "id": "stack-description", + "type": "element", + "elementType": "text", + "bindings": { + "text": "Base layer fills entire container. Content layer also uses MATCH_PARENT to align properly." + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "match_parent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 0 + } + }, + "style": { + "textColor": "#FFFFFFCC", + "fontSize": 13, + "lineHeight": 18 + } + } + ] + }, + { + "id": "stack-floating-button", + "type": "element", + "elementType": "button", + "bindings": { + "text": "Action" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "top": 10, + "right": 20, + "bottom": 10, + "left": 20 + }, + "offset": { + "x": 50, + "y": 100, + "unit": "percent", + "anchorX": 0.5, + "anchorY": 1 + } + }, + "style": { + "backgroundColor": "#FF5722", + "textColor": "#FFFFFF", + "fontSize": 14, + "fontWeight": "bold", + "borderRadius": 24, + "shadowRadius": 8, + "shadowOffsetX": 0, + "shadowOffsetY": 4, + "shadowColor": "#00000044", + "lineHeight": 20 + } + } + ] + }, + { + "id": "box-hero-section", + "type": "container", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 220, + "unit": "dp" + }, + "padding": { + "all": 0 + } + }, + "style": {}, + "children": [ + { + "id": "hero-background", + "type": "element", + "elementType": "text", + "bindings": { + "text": "" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "match_parent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "match_parent" + } + }, + "style": { + "background": { + "type": "radial_gradient", + "colors": [ + "#FF9800", + "#F44336" + ], + "centerX": 50, + "centerY": 50, + "radius": 100 + } + } + }, + { + "id": "hero-pattern-overlay", + "type": "element", + "elementType": "text", + "bindings": { + "text": "" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "match_parent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "match_parent" + } + }, + "style": { + "background": { + "type": "pattern", + "pattern_type": "dots", + "primary_color": "#FFFFFF22", + "secondary_color": "#AABBFF22", + "spacing": 20, + "size": 4 + } + } + }, + { + "id": "hero-content", + "type": "container", + "containerType": "vertical", + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "match_parent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "match_parent" + }, + "padding": { + "all": 24 + }, + "arrangement": { + "strategy": "center" + } + }, + "style": {}, + "children": [ + { + "id": "hero-title", + "type": "element", + "elementType": "text", + "bindings": { + "text": "Hero Section" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "match_parent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 0 + } + }, + "style": { + "textColor": "#FFFFFF", + "fontSize": 28, + "fontWeight": "bold", + "textAlign": "center", + "lineHeight": 39 + } + }, + { + "id": "hero-subtitle", + "type": "element", + "elementType": "text", + "bindings": { + "text": "Layered backgrounds with MATCH_PARENT create beautiful hero sections" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "match_parent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "top": 8, + "right": 0, + "bottom": 0, + "left": 0 + } + }, + "style": { + "textColor": "#FFFFFFDD", + "fontSize": 14, + "textAlign": "center", + "lineHeight": 20 + } + }, + { + "id": "hero-cta", + "type": "element", + "elementType": "button", + "bindings": { + "text": "Get Started" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "top": 12, + "right": 32, + "bottom": 12, + "left": 32 + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "textColor": "#FF5722", + "fontSize": 15, + "fontWeight": "bold", + "borderRadius": 24, + "lineHeight": 21 + } + } + ] + } + ] + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-120-wrap-content-constraints.json b/flutter-sample/assets/test-configs/test-120-wrap-content-constraints.json new file mode 100644 index 0000000..634cc03 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-120-wrap-content-constraints.json @@ -0,0 +1,922 @@ +{ + "theme": { + "id": "test-120", + "defaultStyle": { + "textColor": "#000000", + "fontSize": 14, + "fontWeight": "normal", + "lineHeight": 20 + } + }, + "variables": { + "testName": "WRAP_CONTENT with Constraints Test", + "testDescription": "Demonstrates WRAP_CONTENT with constraints and edge cases including long text, multiple children, and nested containers" + }, + "root": { + "id": "root-container", + "type": "container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 600, + "unit": "dp" + }, + "padding": { + "all": 0 + }, + "arrangement": { + "spacing": 8, + "strategy": "spaced", + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FAFAFA" + }, + "children": [ + { + "id": "section-long-text", + "type": "container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 12 + }, + "arrangement": { + "spacing": 8, + "strategy": "spaced", + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#E3F2FD", + "borderRadius": 8, + "borderWidth": 1, + "borderColor": "#2196F3" + }, + "children": [ + { + "id": "label-long-text", + "type": "element", + "elementType": "text", + "bindings": { + "text": "Long Text: WRAP_CONTENT with width constraint" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 0 + } + }, + "style": { + "textColor": "#1976D2", + "fontSize": 12, + "fontWeight": "bold", + "lineHeight": 17 + } + }, + { + "id": "long-text-content", + "type": "element", + "elementType": "text", + "bindings": { + "text": "This is a very long text that demonstrates how WRAP_CONTENT behaves when the content is longer than expected. The container wraps to fit all the text while respecting the width constraint from the parent. This is useful for dynamic content like user comments, product descriptions, or article excerpts." + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "match_parent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 8 + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "textColor": "#212121", + "fontSize": 13, + "borderRadius": 4, + "lineHeight": 18 + } + } + ] + }, + { + "id": "section-many-children", + "type": "container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 12 + }, + "arrangement": { + "spacing": 6, + "strategy": "spaced", + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FFF3E0", + "borderRadius": 8, + "borderWidth": 1, + "borderColor": "#FF9800" + }, + "children": [ + { + "id": "label-many-children", + "type": "element", + "elementType": "text", + "bindings": { + "text": "Multiple Children: WRAP_CONTENT expands to fit all" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 0 + } + }, + "style": { + "textColor": "#F57C00", + "fontSize": 12, + "fontWeight": "bold", + "lineHeight": 17 + } + }, + { + "id": "child-1", + "type": "element", + "elementType": "text", + "bindings": { + "text": "Child 1" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "match_parent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 8 + } + }, + "style": { + "backgroundColor": "#FFA726", + "textColor": "#FFFFFF", + "fontSize": 13, + "borderRadius": 4, + "lineHeight": 18 + } + }, + { + "id": "child-2", + "type": "element", + "elementType": "text", + "bindings": { + "text": "Child 2" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "match_parent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 8 + } + }, + "style": { + "backgroundColor": "#FFB74D", + "textColor": "#000000", + "fontSize": 13, + "borderRadius": 4, + "lineHeight": 18 + } + }, + { + "id": "child-3", + "type": "element", + "elementType": "text", + "bindings": { + "text": "Child 3" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "match_parent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 8 + } + }, + "style": { + "backgroundColor": "#FFCC80", + "textColor": "#000000", + "fontSize": 13, + "borderRadius": 4, + "lineHeight": 18 + } + }, + { + "id": "child-4", + "type": "element", + "elementType": "text", + "bindings": { + "text": "Child 4" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "match_parent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 8 + } + }, + "style": { + "backgroundColor": "#FFE0B2", + "textColor": "#000000", + "fontSize": 13, + "borderRadius": 4, + "lineHeight": 18 + } + } + ] + }, + { + "id": "section-nested-wrap", + "type": "container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 12 + }, + "arrangement": { + "spacing": 8, + "strategy": "spaced", + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#F3E5F5", + "borderRadius": 8, + "borderWidth": 1, + "borderColor": "#9C27B0" + }, + "children": [ + { + "id": "label-nested", + "type": "element", + "elementType": "text", + "bindings": { + "text": "Nested Containers: WRAP_CONTENT cascades properly" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 0 + } + }, + "style": { + "textColor": "#7B1FA2", + "fontSize": 12, + "fontWeight": "bold", + "lineHeight": 17 + } + }, + { + "id": "nested-level-1", + "type": "container", + "containerType": "vertical", + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "match_parent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 8 + }, + "arrangement": { + "spacing": 6, + "strategy": "spaced", + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#CE93D8AA", + "borderRadius": 4 + }, + "children": [ + { + "id": "nested-label-1", + "type": "element", + "elementType": "text", + "bindings": { + "text": "Level 1 (WRAP)" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "match_parent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 0 + } + }, + "style": { + "textColor": "#4A148C", + "fontSize": 11, + "fontWeight": "bold", + "lineHeight": 15 + } + }, + { + "id": "nested-level-2", + "type": "container", + "containerType": "vertical", + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "match_parent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 8 + }, + "arrangement": { + "spacing": 4, + "strategy": "spaced", + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#BA68C8AA", + "borderRadius": 4 + }, + "children": [ + { + "id": "nested-label-2", + "type": "element", + "elementType": "text", + "bindings": { + "text": "Level 2 (WRAP)" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "match_parent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 0 + } + }, + "style": { + "textColor": "#4A148C", + "fontSize": 10, + "fontWeight": "bold", + "lineHeight": 14 + } + }, + { + "id": "nested-level-3", + "type": "container", + "containerType": "horizontal", + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "match_parent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 6 + }, + "arrangement": { + "spacing": 6, + "strategy": "spaced", + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#AB47BCAA", + "borderRadius": 4 + }, + "children": [ + { + "id": "deep-child-1", + "type": "element", + "elementType": "text", + "bindings": { + "text": "A" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 6 + } + }, + "style": { + "backgroundColor": "#9C27B0", + "textColor": "#FFFFFF", + "fontSize": 12, + "fontWeight": "bold", + "borderRadius": 4, + "lineHeight": 17 + } + }, + { + "id": "deep-child-2", + "type": "element", + "elementType": "text", + "bindings": { + "text": "B" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 6 + } + }, + "style": { + "backgroundColor": "#8E24AA", + "textColor": "#FFFFFF", + "fontSize": 12, + "fontWeight": "bold", + "borderRadius": 4, + "lineHeight": 17 + } + }, + { + "id": "deep-child-3", + "type": "element", + "elementType": "text", + "bindings": { + "text": "C" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 6 + } + }, + "style": { + "backgroundColor": "#7B1FA2", + "textColor": "#FFFFFF", + "fontSize": 12, + "fontWeight": "bold", + "borderRadius": 4, + "lineHeight": 17 + } + } + ] + } + ] + } + ] + } + ] + }, + { + "id": "section-horizontal-wrap", + "type": "container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 12 + }, + "arrangement": { + "spacing": 8, + "strategy": "spaced", + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#E8F5E9", + "borderRadius": 8, + "borderWidth": 1, + "borderColor": "#4CAF50" + }, + "children": [ + { + "id": "label-horizontal-wrap", + "type": "element", + "elementType": "text", + "bindings": { + "text": "Horizontal WRAP: Container wraps to fit content width" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 0 + } + }, + "style": { + "textColor": "#388E3C", + "fontSize": 12, + "fontWeight": "bold", + "lineHeight": 17 + } + }, + { + "id": "horizontal-wrap-container", + "type": "container", + "containerType": "horizontal", + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 8 + }, + "arrangement": { + "spacing": 8, + "strategy": "spaced", + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#66BB6AAA", + "borderRadius": 4 + }, + "children": [ + { + "id": "horiz-item-1", + "type": "element", + "elementType": "text", + "bindings": { + "text": "Tag 1" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "top": 6, + "right": 12, + "bottom": 6, + "left": 12 + } + }, + "style": { + "backgroundColor": "#4CAF50", + "textColor": "#FFFFFF", + "fontSize": 12, + "borderRadius": 12, + "lineHeight": 17 + } + }, + { + "id": "horiz-item-2", + "type": "element", + "elementType": "text", + "bindings": { + "text": "Tag 2" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "top": 6, + "right": 12, + "bottom": 6, + "left": 12 + } + }, + "style": { + "backgroundColor": "#66BB6A", + "textColor": "#FFFFFF", + "fontSize": 12, + "borderRadius": 12, + "lineHeight": 17 + } + }, + { + "id": "horiz-item-3", + "type": "element", + "elementType": "text", + "bindings": { + "text": "Tag 3" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "top": 6, + "right": 12, + "bottom": 6, + "left": 12 + } + }, + "style": { + "backgroundColor": "#81C784", + "textColor": "#FFFFFF", + "fontSize": 12, + "borderRadius": 12, + "lineHeight": 17 + } + } + ] + } + ] + }, + { + "id": "section-edge-case", + "type": "container", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 12 + }, + "arrangement": { + "spacing": 8, + "strategy": "spaced", + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FFEBEE", + "borderRadius": 8, + "borderWidth": 1, + "borderColor": "#F44336" + }, + "children": [ + { + "id": "label-edge-case", + "type": "element", + "elementType": "text", + "bindings": { + "text": "Edge Case: Empty content with padding" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 0 + } + }, + "style": { + "textColor": "#D32F2F", + "fontSize": 12, + "fontWeight": "bold", + "lineHeight": 17 + } + }, + { + "id": "empty-with-padding", + "type": "element", + "elementType": "text", + "bindings": { + "text": "" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 20 + } + }, + "style": { + "backgroundColor": "#EF5350", + "borderRadius": 4 + } + }, + { + "id": "edge-note", + "type": "element", + "elementType": "text", + "bindings": { + "text": "Even with empty text, padding still affects WRAP_CONTENT size" + }, + "layout": { + "width": { + "value": 0, + "unit": "dp", + "special": "match_parent" + }, + "height": { + "value": 0, + "unit": "dp", + "special": "wrap_content" + }, + "padding": { + "all": 0 + } + }, + "style": { + "textColor": "#C62828", + "fontSize": 11, + "lineHeight": 15 + } + } + ] + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-121-16x9-ar-image-text-button.json b/flutter-sample/assets/test-configs/test-121-16x9-ar-image-text-button.json new file mode 100644 index 0000000..06bc79d --- /dev/null +++ b/flutter-sample/assets/test-configs/test-121-16x9-ar-image-text-button.json @@ -0,0 +1,103 @@ +{ + "theme": { + "id": "test-121" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 1.778 + }, + "style": { + "backgroundColor": "#1A1A2E" + }, + "children": [ + { + "type": "element", + "id": "bg_image", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-1.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "offset": { + "x": 0, + "y": 0, + "unit": "percent" + } + }, + "imageConfig": { + "fit": "crop" + } + }, + { + "type": "element", + "id": "headline", + "elementType": "text", + "bindings": { + "text": "HEADLINE TITLE" + }, + "layout": { + "width": { + "value": 80, + "unit": "percent" + }, + "offset": { + "x": 10, + "y": 65, + "unit": "percent" + } + }, + "style": { + "fontSize": 22, + "fontWeight": "bold", + "lineHeight": 28, + "textColor": "#FFFFFF" + } + }, + { + "type": "element", + "id": "cta_button", + "elementType": "button", + "bindings": { + "text": "Get Started" + }, + "layout": { + "width": { + "value": 50, + "unit": "percent" + }, + "height": { + "value": 12, + "unit": "percent" + }, + "offset": { + "x": 25, + "y": 82, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#FF6B6B", + "textColor": "#FFFFFF", + "fontSize": 14, + "lineHeight": 20, + "borderRadius": 8 + } + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-122-1x1-ar-image-badge-rounded.json b/flutter-sample/assets/test-configs/test-122-1x1-ar-image-badge-rounded.json new file mode 100644 index 0000000..0cbc487 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-122-1x1-ar-image-badge-rounded.json @@ -0,0 +1,77 @@ +{ + "theme": { + "id": "test-122" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 1.0 + }, + "style": { + "backgroundColor": "#F0F0F0", + "borderRadius": 12 + }, + "children": [ + { + "type": "element", + "id": "main_image", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-50.jpg" + }, + "layout": { + "width": { + "value": 70, + "unit": "percent" + }, + "height": { + "value": 70, + "unit": "percent" + }, + "offset": { + "x": 15, + "y": 15, + "unit": "percent" + } + }, + "imageConfig": { + "fit": "crop" + } + }, + { + "type": "element", + "id": "badge", + "elementType": "text", + "bindings": { + "text": "NEW" + }, + "layout": { + "width": { + "value": 20, + "unit": "percent" + }, + "offset": { + "x": 5, + "y": 5, + "unit": "percent" + } + }, + "style": { + "fontSize": 10, + "fontWeight": "bold", + "lineHeight": 14, + "textColor": "#FFFFFF", + "backgroundColor": "#E53935", + "borderRadius": 4, + "textAlign": "center" + } + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-123-9x16-ar-video-caption.json b/flutter-sample/assets/test-configs/test-123-9x16-ar-video-caption.json new file mode 100644 index 0000000..d2e8196 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-123-9x16-ar-video-caption.json @@ -0,0 +1,75 @@ +{ + "theme": { + "id": "test-123" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 0.5625 + }, + "style": { + "backgroundColor": "#000000" + }, + "children": [ + { + "type": "element", + "id": "video", + "elementType": "video", + "bindings": { + "url": "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "offset": { + "x": 0, + "y": 0, + "unit": "percent" + } + }, + "autoPlay": true, + "muted": true, + "loop": true, + "showControls": false, + "showFullscreen": false + }, + { + "type": "element", + "id": "caption", + "elementType": "text", + "bindings": { + "text": "Big Buck Bunny — Classic Animation" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "offset": { + "x": 0, + "y": 88, + "unit": "percent" + } + }, + "style": { + "fontSize": 13, + "lineHeight": 18, + "textColor": "#FFFFFF", + "backgroundColor": "#88000000" + } + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-124-4x3-ar-text-weights.json b/flutter-sample/assets/test-configs/test-124-4x3-ar-text-weights.json new file mode 100644 index 0000000..44c7be0 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-124-4x3-ar-text-weights.json @@ -0,0 +1,122 @@ +{ + "theme": { + "id": "test-124" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 1.333 + }, + "style": { + "backgroundColor": "#1A1A2E" + }, + "children": [ + { + "type": "element", + "id": "text_light", + "elementType": "text", + "bindings": { + "text": "Light Weight Text Sample" + }, + "layout": { + "width": { + "value": 90, + "unit": "percent" + }, + "offset": { + "x": 5, + "y": 10, + "unit": "percent" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "light", + "lineHeight": 24, + "textColor": "#FFFFFF" + } + }, + { + "type": "element", + "id": "text_normal", + "elementType": "text", + "bindings": { + "text": "Normal Weight Text Sample" + }, + "layout": { + "width": { + "value": 90, + "unit": "percent" + }, + "offset": { + "x": 5, + "y": 35, + "unit": "percent" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "normal", + "lineHeight": 24, + "textColor": "#FFFFFF" + } + }, + { + "type": "element", + "id": "text_medium", + "elementType": "text", + "bindings": { + "text": "Medium Weight Text Sample" + }, + "layout": { + "width": { + "value": 90, + "unit": "percent" + }, + "offset": { + "x": 5, + "y": 58, + "unit": "percent" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "medium", + "lineHeight": 24, + "textColor": "#DDDDDD" + } + }, + { + "type": "element", + "id": "text_bold", + "elementType": "text", + "bindings": { + "text": "Bold Weight Text Sample" + }, + "layout": { + "width": { + "value": 90, + "unit": "percent" + }, + "offset": { + "x": 5, + "y": 78, + "unit": "percent" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "lineHeight": 24, + "textColor": "#FFFFFF" + } + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-125-2x1-ar-image-split-button.json b/flutter-sample/assets/test-configs/test-125-2x1-ar-image-split-button.json new file mode 100644 index 0000000..739af3e --- /dev/null +++ b/flutter-sample/assets/test-configs/test-125-2x1-ar-image-split-button.json @@ -0,0 +1,103 @@ +{ + "theme": { + "id": "test-125" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 2.0 + }, + "style": { + "backgroundColor": "#F5F5F5" + }, + "children": [ + { + "type": "element", + "id": "left_image", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-100.jpg" + }, + "layout": { + "width": { + "value": 50, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "offset": { + "x": 0, + "y": 0, + "unit": "percent" + } + }, + "imageConfig": { + "fit": "crop" + } + }, + { + "type": "element", + "id": "featured_title", + "elementType": "text", + "bindings": { + "text": "Featured Item" + }, + "layout": { + "width": { + "value": 40, + "unit": "percent" + }, + "offset": { + "x": 55, + "y": 20, + "unit": "percent" + } + }, + "style": { + "fontSize": 20, + "fontWeight": "bold", + "lineHeight": 26, + "textColor": "#1A1A2E" + } + }, + { + "type": "element", + "id": "shop_button", + "elementType": "button", + "bindings": { + "text": "Shop Now" + }, + "layout": { + "width": { + "value": 35, + "unit": "percent" + }, + "height": { + "value": 18, + "unit": "percent" + }, + "offset": { + "x": 58, + "y": 60, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#2196F3", + "textColor": "#FFFFFF", + "fontSize": 14, + "lineHeight": 20, + "borderRadius": 8 + } + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-126-text-font-weights.json b/flutter-sample/assets/test-configs/test-126-text-font-weights.json new file mode 100644 index 0000000..f95449c --- /dev/null +++ b/flutter-sample/assets/test-configs/test-126-text-font-weights.json @@ -0,0 +1,122 @@ +{ + "theme": { + "id": "test-126" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 1.778 + }, + "style": { + "backgroundColor": "#1A1A2E" + }, + "children": [ + { + "type": "element", + "id": "text_light", + "elementType": "text", + "bindings": { + "text": "Light: 300 weight text" + }, + "layout": { + "width": { + "value": 90, + "unit": "percent" + }, + "offset": { + "x": 5, + "y": 10, + "unit": "percent" + } + }, + "style": { + "fontSize": 16, + "fontWeight": "light", + "lineHeight": 22, + "textColor": "#CCCCCC" + } + }, + { + "type": "element", + "id": "text_normal", + "elementType": "text", + "bindings": { + "text": "Normal: 400 weight text" + }, + "layout": { + "width": { + "value": 90, + "unit": "percent" + }, + "offset": { + "x": 5, + "y": 30, + "unit": "percent" + } + }, + "style": { + "fontSize": 16, + "fontWeight": "normal", + "lineHeight": 22, + "textColor": "#CCCCCC" + } + }, + { + "type": "element", + "id": "text_medium", + "elementType": "text", + "bindings": { + "text": "Medium: 500 weight text" + }, + "layout": { + "width": { + "value": 90, + "unit": "percent" + }, + "offset": { + "x": 5, + "y": 55, + "unit": "percent" + } + }, + "style": { + "fontSize": 16, + "fontWeight": "medium", + "lineHeight": 22, + "textColor": "#FFFFFF" + } + }, + { + "type": "element", + "id": "text_bold", + "elementType": "text", + "bindings": { + "text": "Bold: 700 weight text" + }, + "layout": { + "width": { + "value": 90, + "unit": "percent" + }, + "offset": { + "x": 5, + "y": 75, + "unit": "percent" + } + }, + "style": { + "fontSize": 16, + "fontWeight": "bold", + "lineHeight": 22, + "textColor": "#FFFFFF" + } + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-127-text-font-sizes.json b/flutter-sample/assets/test-configs/test-127-text-font-sizes.json new file mode 100644 index 0000000..c775b08 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-127-text-font-sizes.json @@ -0,0 +1,118 @@ +{ + "theme": { + "id": "test-127" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 1.778 + }, + "style": { + "backgroundColor": "#1A1A2E" + }, + "children": [ + { + "type": "element", + "id": "text_small", + "elementType": "text", + "bindings": { + "text": "12sp small text" + }, + "layout": { + "width": { + "value": 90, + "unit": "percent" + }, + "offset": { + "x": 5, + "y": 5, + "unit": "percent" + } + }, + "style": { + "fontSize": 12, + "lineHeight": 17, + "textColor": "#CCCCCC" + } + }, + { + "type": "element", + "id": "text_body", + "elementType": "text", + "bindings": { + "text": "16sp body text" + }, + "layout": { + "width": { + "value": 90, + "unit": "percent" + }, + "offset": { + "x": 5, + "y": 25, + "unit": "percent" + } + }, + "style": { + "fontSize": 16, + "lineHeight": 22, + "textColor": "#FFFFFF" + } + }, + { + "type": "element", + "id": "text_subtitle", + "elementType": "text", + "bindings": { + "text": "24sp subtitle" + }, + "layout": { + "width": { + "value": 90, + "unit": "percent" + }, + "offset": { + "x": 5, + "y": 50, + "unit": "percent" + } + }, + "style": { + "fontSize": 24, + "lineHeight": 32, + "textColor": "#FFFFFF" + } + }, + { + "type": "element", + "id": "text_headline", + "elementType": "text", + "bindings": { + "text": "32sp headline" + }, + "layout": { + "width": { + "value": 90, + "unit": "percent" + }, + "offset": { + "x": 5, + "y": 75, + "unit": "percent" + } + }, + "style": { + "fontSize": 32, + "lineHeight": 42, + "textColor": "#FFFFFF" + } + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-128-text-alignment.json b/flutter-sample/assets/test-configs/test-128-text-alignment.json new file mode 100644 index 0000000..9710ae9 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-128-text-alignment.json @@ -0,0 +1,97 @@ +{ + "theme": { + "id": "test-128" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 1.778 + }, + "style": { + "backgroundColor": "#1A1A2E" + }, + "children": [ + { + "type": "element", + "id": "text_left", + "elementType": "text", + "bindings": { + "text": "Left aligned text block with multiple words" + }, + "layout": { + "width": { + "value": 80, + "unit": "percent" + }, + "offset": { + "x": 10, + "y": 15, + "unit": "percent" + } + }, + "style": { + "fontSize": 16, + "lineHeight": 22, + "textColor": "#FFFFFF", + "textAlign": "left" + } + }, + { + "type": "element", + "id": "text_center", + "elementType": "text", + "bindings": { + "text": "Center aligned text block with multiple words" + }, + "layout": { + "width": { + "value": 80, + "unit": "percent" + }, + "offset": { + "x": 10, + "y": 45, + "unit": "percent" + } + }, + "style": { + "fontSize": 16, + "lineHeight": 22, + "textColor": "#FFFFFF", + "textAlign": "center" + } + }, + { + "type": "element", + "id": "text_right", + "elementType": "text", + "bindings": { + "text": "Right aligned text block with multiple words" + }, + "layout": { + "width": { + "value": 80, + "unit": "percent" + }, + "offset": { + "x": 10, + "y": 72, + "unit": "percent" + } + }, + "style": { + "fontSize": 16, + "lineHeight": 22, + "textColor": "#FFFFFF", + "textAlign": "right" + } + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-129-text-decoration-italic.json b/flutter-sample/assets/test-configs/test-129-text-decoration-italic.json new file mode 100644 index 0000000..333c14b --- /dev/null +++ b/flutter-sample/assets/test-configs/test-129-text-decoration-italic.json @@ -0,0 +1,97 @@ +{ + "theme": { + "id": "test-129" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 1.778 + }, + "style": { + "backgroundColor": "#1A1A2E" + }, + "children": [ + { + "type": "element", + "id": "text_underline", + "elementType": "text", + "bindings": { + "text": "Underlined text decoration" + }, + "layout": { + "width": { + "value": 80, + "unit": "percent" + }, + "offset": { + "x": 10, + "y": 20, + "unit": "percent" + } + }, + "style": { + "fontSize": 18, + "lineHeight": 24, + "textColor": "#FFFFFF", + "textDecoration": "underline" + } + }, + { + "type": "element", + "id": "text_strikethrough", + "elementType": "text", + "bindings": { + "text": "Strikethrough text decoration" + }, + "layout": { + "width": { + "value": 80, + "unit": "percent" + }, + "offset": { + "x": 10, + "y": 45, + "unit": "percent" + } + }, + "style": { + "fontSize": 18, + "lineHeight": 24, + "textColor": "#CCCCCC", + "textDecoration": "strikethrough" + } + }, + { + "type": "element", + "id": "text_italic", + "elementType": "text", + "bindings": { + "text": "Italic font style text" + }, + "layout": { + "width": { + "value": 80, + "unit": "percent" + }, + "offset": { + "x": 10, + "y": 70, + "unit": "percent" + } + }, + "style": { + "fontSize": 18, + "lineHeight": 24, + "textColor": "#FFFFFF", + "fontStyle": "italic" + } + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-130-text-maxlines-overflow.json b/flutter-sample/assets/test-configs/test-130-text-maxlines-overflow.json new file mode 100644 index 0000000..3177729 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-130-text-maxlines-overflow.json @@ -0,0 +1,48 @@ +{ + "theme": { + "id": "test-130" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 1.778 + }, + "style": { + "backgroundColor": "#1A1A2E" + }, + "children": [ + { + "type": "element", + "id": "clamped_text", + "elementType": "text", + "bindings": { + "text": "This is a very long text that should be clamped at exactly two lines using the maxLines and overflow ellipsis configuration. There is more text here that won't be shown." + }, + "layout": { + "width": { + "value": 80, + "unit": "percent" + }, + "offset": { + "x": 10, + "y": 30, + "unit": "percent" + } + }, + "style": { + "fontSize": 16, + "lineHeight": 22, + "textColor": "#FFFFFF", + "maxLines": 2, + "overflow": "ellipsis" + } + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-131-text-gradient.json b/flutter-sample/assets/test-configs/test-131-text-gradient.json new file mode 100644 index 0000000..7771c3c --- /dev/null +++ b/flutter-sample/assets/test-configs/test-131-text-gradient.json @@ -0,0 +1,93 @@ +{ + "theme": { + "id": "test-131" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 1.778 + }, + "style": { + "backgroundColor": "#1A1A2E" + }, + "children": [ + { + "type": "element", + "id": "gradient_headline", + "elementType": "text", + "bindings": { + "text": "GRADIENT HEADLINE" + }, + "layout": { + "width": { + "value": 90, + "unit": "percent" + }, + "offset": { + "x": 5, + "y": 25, + "unit": "percent" + } + }, + "style": { + "fontSize": 28, + "fontWeight": "bold", + "lineHeight": 36, + "textGradient": { + "type": "linear", + "angle": 90, + "colors": [ + "#FF6B6B", + "#4ECDC4" + ], + "stops": [ + 0.0, + 1.0 + ] + } + } + }, + { + "type": "element", + "id": "gradient_subtitle", + "elementType": "text", + "bindings": { + "text": "gradient subtitle text" + }, + "layout": { + "width": { + "value": 90, + "unit": "percent" + }, + "offset": { + "x": 5, + "y": 60, + "unit": "percent" + } + }, + "style": { + "fontSize": 18, + "lineHeight": 24, + "textGradient": { + "type": "linear", + "angle": 45, + "colors": [ + "#A8EDEA", + "#FED6E3" + ], + "stops": [ + 0.0, + 1.0 + ] + } + } + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-132-image-fit-crop-contain.json b/flutter-sample/assets/test-configs/test-132-image-fit-crop-contain.json new file mode 100644 index 0000000..26ff52b --- /dev/null +++ b/flutter-sample/assets/test-configs/test-132-image-fit-crop-contain.json @@ -0,0 +1,124 @@ +{ + "theme": { + "id": "test-132" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 1.778 + }, + "style": { + "backgroundColor": "#333333" + }, + "children": [ + { + "type": "element", + "id": "image_crop", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-200.jpg" + }, + "layout": { + "width": { + "value": 48, + "unit": "percent" + }, + "height": { + "value": 90, + "unit": "percent" + }, + "offset": { + "x": 1, + "y": 5, + "unit": "percent" + } + }, + "imageConfig": { + "fit": "crop" + } + }, + { + "type": "element", + "id": "image_contain", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-200.jpg" + }, + "layout": { + "width": { + "value": 48, + "unit": "percent" + }, + "height": { + "value": 90, + "unit": "percent" + }, + "offset": { + "x": 51, + "y": 5, + "unit": "percent" + } + }, + "imageConfig": { + "fit": "contain" + } + }, + { + "type": "element", + "id": "label_crop", + "elementType": "text", + "bindings": { + "text": "CROP" + }, + "layout": { + "width": { + "value": 20, + "unit": "percent" + }, + "offset": { + "x": 5, + "y": 92, + "unit": "percent" + } + }, + "style": { + "fontSize": 12, + "lineHeight": 16, + "textColor": "#FFFFFF", + "textAlign": "center" + } + }, + { + "type": "element", + "id": "label_contain", + "elementType": "text", + "bindings": { + "text": "CONTAIN" + }, + "layout": { + "width": { + "value": 20, + "unit": "percent" + }, + "offset": { + "x": 55, + "y": 92, + "unit": "percent" + } + }, + "style": { + "fontSize": 12, + "lineHeight": 16, + "textColor": "#FFFFFF", + "textAlign": "center" + } + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-133-image-gif-rounded.json b/flutter-sample/assets/test-configs/test-133-image-gif-rounded.json new file mode 100644 index 0000000..99d2dcf --- /dev/null +++ b/flutter-sample/assets/test-configs/test-133-image-gif-rounded.json @@ -0,0 +1,75 @@ +{ + "theme": { + "id": "test-133" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 1.0 + }, + "style": { + "backgroundColor": "#F5F5F5", + "borderRadius": 12 + }, + "children": [ + { + "type": "element", + "id": "gif_image", + "elementType": "image", + "bindings": { + "url": "https://media.giphy.com/media/l0MYt5jPR6QX5pnqM/giphy.gif" + }, + "layout": { + "width": { + "value": 90, + "unit": "percent" + }, + "height": { + "value": 75, + "unit": "percent" + }, + "offset": { + "x": 5, + "y": 5, + "unit": "percent" + } + }, + "imageConfig": { + "animated": true + } + }, + { + "type": "element", + "id": "caption", + "elementType": "text", + "bindings": { + "text": "Animated GIF Demo" + }, + "layout": { + "width": { + "value": 90, + "unit": "percent" + }, + "offset": { + "x": 5, + "y": 82, + "unit": "percent" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "lineHeight": 20, + "textColor": "#333333", + "textAlign": "center" + } + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-134-image-border-radius.json b/flutter-sample/assets/test-configs/test-134-image-border-radius.json new file mode 100644 index 0000000..5002deb --- /dev/null +++ b/flutter-sample/assets/test-configs/test-134-image-border-radius.json @@ -0,0 +1,210 @@ +{ + "theme": { + "id": "test-134" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "vertical", + "layout": { + "width": { "value": 100, "unit": "percent" }, + "height": { "special": "wrap_content" }, + "padding": { "top": 16, "bottom": 16, "start": 16, "end": 16, "unit": "dp" } + }, + "style": { + "backgroundColor": "#F0F0F0" + }, + "arrangement": { "strategy": "spaced", "spacing": 20, "spacingUnit": "dp" }, + "children": [ + { + "type": "element", + "id": "section_label_raw", + "elementType": "text", + "bindings": { "text": "Raw dp borderRadius (backward compat)" }, + "layout": { "width": { "special": "match_parent" } }, + "style": { + "fontSize": 12, + "fontWeight": "bold", + "lineHeight": 16, + "textColor": "#333333" + } + }, + { + "type": "container", + "id": "row_raw", + "containerType": "horizontal", + "layout": { + "width": { "special": "match_parent" }, + "height": { "special": "wrap_content" } + }, + "arrangement": { "strategy": "spaced", "spacing": 16, "spacingUnit": "dp" }, + "children": [ + { + "type": "container", + "id": "rounded_frame", + "containerType": "box", + "layout": { + "width": { "value": 130, "unit": "dp" }, + "height": { "value": 100, "unit": "dp" } + }, + "style": { + "borderRadius": 20, + "backgroundColor": "#FFFFFF" + }, + "children": [ + { + "type": "element", + "id": "rounded_image", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-106.jpg" + }, + "layout": { + "width": { "value": 100, "unit": "percent" }, + "height": { "value": 100, "unit": "percent" } + } + } + ] + }, + { + "type": "element", + "id": "label_raw", + "elementType": "text", + "bindings": { "text": "borderRadius: 20 (dp)" }, + "layout": { + "width": { "value": 120, "unit": "dp" }, + "height": { "special": "wrap_content" } + }, + "style": { + "fontSize": 11, + "fontWeight": "bold", + "lineHeight": 15, + "textColor": "#FFFFFF", + "backgroundColor": "#333333AA", + "borderRadius": 4 + } + } + ] + }, + { + "type": "element", + "id": "section_label_percent", + "elementType": "text", + "bindings": { "text": "Percent-based borderRadius" }, + "layout": { "width": { "special": "match_parent" } }, + "style": { + "fontSize": 12, + "fontWeight": "bold", + "lineHeight": 16, + "textColor": "#333333" + } + }, + { + "type": "container", + "id": "row_percent", + "containerType": "horizontal", + "layout": { + "width": { "special": "match_parent" }, + "height": { "special": "wrap_content" } + }, + "arrangement": { "strategy": "spaced", "spacing": 24, "spacingUnit": "dp" }, + "children": [ + { + "type": "container", + "id": "pill_frame", + "containerType": "box", + "layout": { + "width": { "value": 140, "unit": "dp" }, + "height": { "value": 60, "unit": "dp" } + }, + "style": { + "borderRadius": { "value": 50, "unit": "percent" }, + "backgroundColor": "#4A90E2" + }, + "children": [ + { + "type": "element", + "id": "pill_label", + "elementType": "text", + "bindings": { "text": "Pill Shape\n50% radius" }, + "layout": { + "width": { "value": 100, "unit": "percent" }, + "height": { "value": 100, "unit": "percent" } + }, + "style": { + "fontSize": 12, + "lineHeight": 16, + "textColor": "#FFFFFF", + "textAlign": "center" + } + } + ] + }, + { + "type": "container", + "id": "circle_frame", + "containerType": "box", + "layout": { + "width": { "value": 80, "unit": "dp" }, + "height": { "value": 80, "unit": "dp" } + }, + "style": { + "borderRadius": { "value": 100, "unit": "percent" }, + "backgroundColor": "#E24A4A" + }, + "children": [ + { + "type": "element", + "id": "circle_image", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-106.jpg" + }, + "layout": { + "width": { "value": 100, "unit": "percent" }, + "height": { "value": 100, "unit": "percent" } + } + } + ] + }, + { + "type": "container", + "id": "circle_label_frame", + "containerType": "vertical", + "layout": { + "width": { "special": "wrap_content" }, + "height": { "special": "wrap_content" } + }, + "arrangement": { "strategy": "spaced", "spacing": 4, "spacingUnit": "dp" }, + "children": [ + { + "type": "element", + "id": "pill_desc", + "elementType": "text", + "bindings": { "text": "50% = pill" }, + "layout": { "width": { "special": "wrap_content" } }, + "style": { + "fontSize": 11, + "lineHeight": 15, + "textColor": "#555555" + } + }, + { + "type": "element", + "id": "circle_desc", + "elementType": "text", + "bindings": { "text": "100% = circle" }, + "layout": { "width": { "special": "wrap_content" } }, + "style": { + "fontSize": 11, + "lineHeight": 15, + "textColor": "#555555" + } + } + ] + } + ] + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-135-images-z-order.json b/flutter-sample/assets/test-configs/test-135-images-z-order.json new file mode 100644 index 0000000..6298170 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-135-images-z-order.json @@ -0,0 +1,74 @@ +{ + "theme": { + "id": "test-135" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 1.778 + }, + "style": { + "backgroundColor": "#EEEEEE" + }, + "children": [ + { + "type": "element", + "id": "image_back", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-107.jpg" + }, + "layout": { + "width": { + "value": 60, + "unit": "percent" + }, + "height": { + "value": 60, + "unit": "percent" + }, + "offset": { + "x": 5, + "y": 5, + "unit": "percent" + } + }, + "imageConfig": { + "fit": "crop" + } + }, + { + "type": "element", + "id": "image_front", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-108.jpg" + }, + "layout": { + "width": { + "value": 60, + "unit": "percent" + }, + "height": { + "value": 60, + "unit": "percent" + }, + "offset": { + "x": 20, + "y": 25, + "unit": "percent" + } + }, + "imageConfig": { + "fit": "crop" + } + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-136-video-autoplay-muted.json b/flutter-sample/assets/test-configs/test-136-video-autoplay-muted.json new file mode 100644 index 0000000..04583b1 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-136-video-autoplay-muted.json @@ -0,0 +1,50 @@ +{ + "theme": { + "id": "test-136" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 1.778 + }, + "style": { + "backgroundColor": "#000000" + }, + "children": [ + { + "type": "element", + "id": "video", + "elementType": "video", + "bindings": { + "url": "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "offset": { + "x": 0, + "y": 0, + "unit": "percent" + } + }, + "autoPlay": true, + "muted": true, + "loop": true, + "showControls": false, + "showFullscreen": false + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-137-video-with-controls.json b/flutter-sample/assets/test-configs/test-137-video-with-controls.json new file mode 100644 index 0000000..89c5716 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-137-video-with-controls.json @@ -0,0 +1,76 @@ +{ + "theme": { + "id": "test-137" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 1.778 + }, + "style": { + "backgroundColor": "#000000" + }, + "children": [ + { + "type": "element", + "id": "video", + "elementType": "video", + "bindings": { + "url": "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "offset": { + "x": 0, + "y": 0, + "unit": "percent" + } + }, + "autoPlay": false, + "muted": false, + "loop": false, + "showControls": true, + "showFullscreen": false + }, + { + "type": "element", + "id": "video_title", + "elementType": "text", + "bindings": { + "text": "Big Buck Bunny" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "offset": { + "x": 0, + "y": 85, + "unit": "percent" + } + }, + "style": { + "fontSize": 14, + "lineHeight": 20, + "textColor": "#FFFFFF", + "backgroundColor": "#88000000", + "textAlign": "center" + } + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-138-9x16-video-button.json b/flutter-sample/assets/test-configs/test-138-9x16-video-button.json new file mode 100644 index 0000000..c5f932d --- /dev/null +++ b/flutter-sample/assets/test-configs/test-138-9x16-video-button.json @@ -0,0 +1,80 @@ +{ + "theme": { + "id": "test-138" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 0.5625 + }, + "style": { + "backgroundColor": "#000000" + }, + "children": [ + { + "type": "element", + "id": "video", + "elementType": "video", + "bindings": { + "url": "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 80, + "unit": "percent" + }, + "offset": { + "x": 0, + "y": 0, + "unit": "percent" + } + }, + "autoPlay": true, + "muted": true, + "loop": true, + "showControls": false, + "showFullscreen": false + }, + { + "type": "element", + "id": "watch_button", + "elementType": "button", + "bindings": { + "text": "Watch Full Video" + }, + "layout": { + "width": { + "value": 60, + "unit": "percent" + }, + "height": { + "value": 8, + "unit": "percent" + }, + "offset": { + "x": 20, + "y": 83, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#FF6B6B", + "textColor": "#FFFFFF", + "fontSize": 14, + "lineHeight": 20, + "borderRadius": 8 + } + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-139-button-centered.json b/flutter-sample/assets/test-configs/test-139-button-centered.json new file mode 100644 index 0000000..950becc --- /dev/null +++ b/flutter-sample/assets/test-configs/test-139-button-centered.json @@ -0,0 +1,52 @@ +{ + "theme": { + "id": "test-139" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 1.778 + }, + "style": { + "backgroundColor": "#1A1A2E" + }, + "children": [ + { + "type": "element", + "id": "cta_button", + "elementType": "button", + "bindings": { + "text": "Get Started" + }, + "layout": { + "width": { + "value": 60, + "unit": "percent" + }, + "height": { + "value": 15, + "unit": "percent" + }, + "offset": { + "x": 20, + "y": 42, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#2196F3", + "textColor": "#FFFFFF", + "fontSize": 16, + "lineHeight": 22, + "borderRadius": 8 + } + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-140-button-primary-secondary.json b/flutter-sample/assets/test-configs/test-140-button-primary-secondary.json new file mode 100644 index 0000000..bd6e17e --- /dev/null +++ b/flutter-sample/assets/test-configs/test-140-button-primary-secondary.json @@ -0,0 +1,84 @@ +{ + "theme": { + "id": "test-140" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 1.778 + }, + "style": { + "backgroundColor": "#F5F5F5" + }, + "children": [ + { + "type": "element", + "id": "primary_button", + "elementType": "button", + "bindings": { + "text": "Primary Action" + }, + "layout": { + "width": { + "value": 60, + "unit": "percent" + }, + "height": { + "value": 14, + "unit": "percent" + }, + "offset": { + "x": 20, + "y": 35, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#2196F3", + "textColor": "#FFFFFF", + "fontSize": 15, + "lineHeight": 21, + "borderRadius": 8 + } + }, + { + "type": "element", + "id": "secondary_button", + "elementType": "button", + "bindings": { + "text": "Secondary Action" + }, + "layout": { + "width": { + "value": 60, + "unit": "percent" + }, + "height": { + "value": 14, + "unit": "percent" + }, + "offset": { + "x": 20, + "y": 55, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "textColor": "#2196F3", + "fontSize": 15, + "lineHeight": 21, + "borderRadius": 8, + "borderWidth": 2, + "borderColor": "#2196F3" + } + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-141-button-size-variants.json b/flutter-sample/assets/test-configs/test-141-button-size-variants.json new file mode 100644 index 0000000..942fa23 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-141-button-size-variants.json @@ -0,0 +1,112 @@ +{ + "theme": { + "id": "test-141" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 1.0 + }, + "style": { + "backgroundColor": "#EEEEEE" + }, + "children": [ + { + "type": "element", + "id": "small_button", + "elementType": "button", + "bindings": { + "text": "Small" + }, + "layout": { + "width": { + "value": 30, + "unit": "percent" + }, + "height": { + "value": 10, + "unit": "percent" + }, + "offset": { + "x": 35, + "y": 20, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#9C27B0", + "textColor": "#FFFFFF", + "fontSize": 12, + "lineHeight": 17, + "borderRadius": 6 + } + }, + { + "type": "element", + "id": "medium_button", + "elementType": "button", + "bindings": { + "text": "Medium Button" + }, + "layout": { + "width": { + "value": 50, + "unit": "percent" + }, + "height": { + "value": 12, + "unit": "percent" + }, + "offset": { + "x": 25, + "y": 44, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#E91E63", + "textColor": "#FFFFFF", + "fontSize": 14, + "lineHeight": 20, + "borderRadius": 8 + } + }, + { + "type": "element", + "id": "large_button", + "elementType": "button", + "bindings": { + "text": "Large Action Button" + }, + "layout": { + "width": { + "value": 70, + "unit": "percent" + }, + "height": { + "value": 14, + "unit": "percent" + }, + "offset": { + "x": 15, + "y": 65, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#F44336", + "textColor": "#FFFFFF", + "fontSize": 16, + "lineHeight": 22, + "borderRadius": 10 + } + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-142-cta-card.json b/flutter-sample/assets/test-configs/test-142-cta-card.json new file mode 100644 index 0000000..9d27266 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-142-cta-card.json @@ -0,0 +1,127 @@ +{ + "theme": { + "id": "test-142" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 1.778 + }, + "style": { + "backgroundColor": "#000000" + }, + "children": [ + { + "type": "element", + "id": "bg_image", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-109.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "offset": { + "x": 0, + "y": 0, + "unit": "percent" + } + }, + "imageConfig": { + "fit": "crop" + } + }, + { + "type": "element", + "id": "offer_headline", + "elementType": "text", + "bindings": { + "text": "Exclusive Offer" + }, + "layout": { + "width": { + "value": 80, + "unit": "percent" + }, + "offset": { + "x": 10, + "y": 55, + "unit": "percent" + } + }, + "style": { + "fontSize": 22, + "fontWeight": "bold", + "lineHeight": 28, + "textColor": "#FFFFFF" + } + }, + { + "type": "element", + "id": "offer_subtitle", + "elementType": "text", + "bindings": { + "text": "Up to 50% off today only" + }, + "layout": { + "width": { + "value": 80, + "unit": "percent" + }, + "offset": { + "x": 10, + "y": 70, + "unit": "percent" + } + }, + "style": { + "fontSize": 14, + "lineHeight": 20, + "textColor": "#EEEEEE" + } + }, + { + "type": "element", + "id": "claim_button", + "elementType": "button", + "bindings": { + "text": "Claim Now" + }, + "layout": { + "width": { + "value": 50, + "unit": "percent" + }, + "height": { + "value": 12, + "unit": "percent" + }, + "offset": { + "x": 25, + "y": 82, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#FF6B6B", + "textColor": "#FFFFFF", + "fontSize": 15, + "lineHeight": 21, + "borderRadius": 8 + } + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-143-button-rounded-text.json b/flutter-sample/assets/test-configs/test-143-button-rounded-text.json new file mode 100644 index 0000000..cf32093 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-143-button-rounded-text.json @@ -0,0 +1,78 @@ +{ + "theme": { + "id": "test-143" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 1.333 + }, + "style": { + "backgroundColor": "#F5F5F5" + }, + "children": [ + { + "type": "element", + "id": "demo_title", + "elementType": "text", + "bindings": { + "text": "Round Button Demo" + }, + "layout": { + "width": { + "value": 80, + "unit": "percent" + }, + "offset": { + "x": 10, + "y": 20, + "unit": "percent" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "lineHeight": 24, + "textColor": "#333333", + "textAlign": "center" + } + }, + { + "type": "element", + "id": "rounded_button", + "elementType": "button", + "bindings": { + "text": "Fully Rounded CTA" + }, + "layout": { + "width": { + "value": 70, + "unit": "percent" + }, + "height": { + "value": 16, + "unit": "percent" + }, + "offset": { + "x": 15, + "y": 55, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#4CAF50", + "textColor": "#FFFFFF", + "fontSize": 16, + "lineHeight": 22, + "borderRadius": 24 + } + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-144-rounded-box-text.json b/flutter-sample/assets/test-configs/test-144-rounded-box-text.json new file mode 100644 index 0000000..87a79dc --- /dev/null +++ b/flutter-sample/assets/test-configs/test-144-rounded-box-text.json @@ -0,0 +1,99 @@ +{ + "theme": { + "id": "test-144" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 1.778 + }, + "style": { + "backgroundColor": "#2196F3", + "borderRadius": 16 + }, + "children": [ + { + "type": "element", + "id": "top_label", + "elementType": "text", + "bindings": { + "text": "Top Label" + }, + "layout": { + "width": { + "value": 80, + "unit": "percent" + }, + "offset": { + "x": 10, + "y": 10, + "unit": "percent" + } + }, + "style": { + "fontSize": 16, + "lineHeight": 22, + "textColor": "#FFFFFF", + "textAlign": "center" + } + }, + { + "type": "element", + "id": "middle_content", + "elementType": "text", + "bindings": { + "text": "Middle Content" + }, + "layout": { + "width": { + "value": 80, + "unit": "percent" + }, + "offset": { + "x": 10, + "y": 42, + "unit": "percent" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "lineHeight": 24, + "textColor": "#FFFFFF", + "textAlign": "center" + } + }, + { + "type": "element", + "id": "bottom_note", + "elementType": "text", + "bindings": { + "text": "Bottom Note" + }, + "layout": { + "width": { + "value": 80, + "unit": "percent" + }, + "offset": { + "x": 10, + "y": 75, + "unit": "percent" + } + }, + "style": { + "fontSize": 13, + "lineHeight": 18, + "textColor": "#E3F2FD", + "textAlign": "center" + } + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-145-nested-rounded-boxes.json b/flutter-sample/assets/test-configs/test-145-nested-rounded-boxes.json new file mode 100644 index 0000000..f3e6b75 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-145-nested-rounded-boxes.json @@ -0,0 +1,75 @@ +{ + "theme": { + "id": "test-145" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 1.778 + }, + "style": { + "backgroundColor": "#333333", + "borderRadius": 24 + }, + "children": [ + { + "type": "container", + "id": "inner_box", + "containerType": "box", + "layout": { + "width": { + "value": 50, + "unit": "percent" + }, + "height": { + "value": 50, + "unit": "percent" + }, + "offset": { + "x": 25, + "y": 25, + "unit": "percent" + } + }, + "style": { + "borderRadius": 12, + "backgroundColor": "#FFFFFF" + }, + "children": [ + { + "type": "element", + "id": "inner_text", + "elementType": "text", + "bindings": { + "text": "Inner Box" + }, + "layout": { + "width": { + "value": 80, + "unit": "percent" + }, + "offset": { + "x": 10, + "y": 35, + "unit": "percent" + } + }, + "style": { + "fontSize": 16, + "fontWeight": "bold", + "lineHeight": 22, + "textColor": "#333333", + "textAlign": "center" + } + } + ] + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-146-image-overlay-rounded.json b/flutter-sample/assets/test-configs/test-146-image-overlay-rounded.json new file mode 100644 index 0000000..7db149d --- /dev/null +++ b/flutter-sample/assets/test-configs/test-146-image-overlay-rounded.json @@ -0,0 +1,100 @@ +{ + "theme": { + "id": "test-146" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 1.778 + }, + "style": { + "backgroundColor": "#000000" + }, + "children": [ + { + "type": "element", + "id": "bg_image", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-110.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "offset": { + "x": 0, + "y": 0, + "unit": "percent" + } + }, + "imageConfig": { + "fit": "crop" + } + }, + { + "type": "container", + "id": "overlay_box", + "containerType": "box", + "layout": { + "width": { + "value": 60, + "unit": "percent" + }, + "height": { + "value": 40, + "unit": "percent" + }, + "offset": { + "x": 20, + "y": 55, + "unit": "percent" + } + }, + "style": { + "borderRadius": 12, + "backgroundColor": "#CC000000" + }, + "children": [ + { + "type": "element", + "id": "overlay_caption", + "elementType": "text", + "bindings": { + "text": "Overlay Caption" + }, + "layout": { + "width": { + "value": 80, + "unit": "percent" + }, + "offset": { + "x": 10, + "y": 35, + "unit": "percent" + } + }, + "style": { + "fontSize": 16, + "fontWeight": "bold", + "lineHeight": 22, + "textColor": "#FFFFFF", + "textAlign": "center" + } + } + ] + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-147-hero-banner-complex.json b/flutter-sample/assets/test-configs/test-147-hero-banner-complex.json new file mode 100644 index 0000000..a786863 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-147-hero-banner-complex.json @@ -0,0 +1,127 @@ +{ + "theme": { + "id": "test-147" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 1.778 + }, + "style": { + "backgroundColor": "#000000" + }, + "children": [ + { + "type": "element", + "id": "hero_image", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-111.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "offset": { + "x": 0, + "y": 0, + "unit": "percent" + } + }, + "imageConfig": { + "fit": "crop" + } + }, + { + "type": "element", + "id": "hero_headline", + "elementType": "text", + "bindings": { + "text": "DISCOVER YOUR WORLD" + }, + "layout": { + "width": { + "value": 80, + "unit": "percent" + }, + "offset": { + "x": 10, + "y": 50, + "unit": "percent" + } + }, + "style": { + "fontSize": 22, + "fontWeight": "bold", + "lineHeight": 28, + "textColor": "#FFFFFF" + } + }, + { + "type": "element", + "id": "hero_subtitle", + "elementType": "text", + "bindings": { + "text": "Explore amazing destinations" + }, + "layout": { + "width": { + "value": 80, + "unit": "percent" + }, + "offset": { + "x": 10, + "y": 65, + "unit": "percent" + } + }, + "style": { + "fontSize": 14, + "lineHeight": 20, + "textColor": "#EEEEEE" + } + }, + { + "type": "element", + "id": "explore_button", + "elementType": "button", + "bindings": { + "text": "Explore Now" + }, + "layout": { + "width": { + "value": 40, + "unit": "percent" + }, + "height": { + "value": 12, + "unit": "percent" + }, + "offset": { + "x": 30, + "y": 80, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#FF6B6B", + "textColor": "#FFFFFF", + "fontSize": 14, + "lineHeight": 20, + "borderRadius": 8 + } + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-148-product-card-complex.json b/flutter-sample/assets/test-configs/test-148-product-card-complex.json new file mode 100644 index 0000000..bebd26c --- /dev/null +++ b/flutter-sample/assets/test-configs/test-148-product-card-complex.json @@ -0,0 +1,130 @@ +{ + "theme": { + "id": "test-148" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 1.333 + }, + "style": { + "backgroundColor": "#FFFFFF" + }, + "children": [ + { + "type": "element", + "id": "product_image", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-112.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 50, + "unit": "percent" + }, + "offset": { + "x": 0, + "y": 0, + "unit": "percent" + } + }, + "imageConfig": { + "fit": "crop" + } + }, + { + "type": "element", + "id": "price_badge", + "elementType": "text", + "bindings": { + "text": "$99.99" + }, + "layout": { + "width": { + "value": 28, + "unit": "percent" + }, + "offset": { + "x": 65, + "y": 5, + "unit": "percent" + } + }, + "style": { + "fontSize": 13, + "fontWeight": "bold", + "lineHeight": 18, + "textColor": "#FFFFFF", + "backgroundColor": "#E53935", + "borderRadius": 4, + "textAlign": "center" + } + }, + { + "type": "element", + "id": "rating", + "elementType": "text", + "bindings": { + "text": "★★★★☆ 4.2 (128 reviews)" + }, + "layout": { + "width": { + "value": 80, + "unit": "percent" + }, + "offset": { + "x": 5, + "y": 55, + "unit": "percent" + } + }, + "style": { + "fontSize": 13, + "lineHeight": 18, + "textColor": "#F57C00" + } + }, + { + "type": "element", + "id": "add_to_cart_button", + "elementType": "button", + "bindings": { + "text": "Add to Cart" + }, + "layout": { + "width": { + "value": 80, + "unit": "percent" + }, + "height": { + "value": 13, + "unit": "percent" + }, + "offset": { + "x": 10, + "y": 75, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#2196F3", + "textColor": "#FFFFFF", + "fontSize": 14, + "lineHeight": 20, + "borderRadius": 8 + } + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-149-notification-card.json b/flutter-sample/assets/test-configs/test-149-notification-card.json new file mode 100644 index 0000000..8c05068 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-149-notification-card.json @@ -0,0 +1,128 @@ +{ + "theme": { + "id": "test-149" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 1.778 + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 12 + }, + "children": [ + { + "type": "element", + "id": "avatar", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-1.jpg" + }, + "layout": { + "width": { + "value": 15, + "unit": "percent" + }, + "height": { + "value": 40, + "unit": "percent" + }, + "offset": { + "x": 5, + "y": 30, + "unit": "percent" + } + }, + "imageConfig": { + "fit": "crop" + } + }, + { + "type": "element", + "id": "notification_title", + "elementType": "text", + "bindings": { + "text": "New Message" + }, + "layout": { + "width": { + "value": 50, + "unit": "percent" + }, + "offset": { + "x": 25, + "y": 30, + "unit": "percent" + } + }, + "style": { + "fontSize": 15, + "fontWeight": "bold", + "lineHeight": 21, + "textColor": "#1A1A2E" + } + }, + { + "type": "element", + "id": "notification_body", + "elementType": "text", + "bindings": { + "text": "Hey! Just checking in..." + }, + "layout": { + "width": { + "value": 50, + "unit": "percent" + }, + "offset": { + "x": 25, + "y": 52, + "unit": "percent" + } + }, + "style": { + "fontSize": 13, + "lineHeight": 18, + "textColor": "#666666" + } + }, + { + "type": "element", + "id": "dismiss_button", + "elementType": "button", + "bindings": { + "text": "Dismiss" + }, + "layout": { + "width": { + "value": 18, + "unit": "percent" + }, + "height": { + "value": 20, + "unit": "percent" + }, + "offset": { + "x": 75, + "y": 38, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#EEEEEE", + "textColor": "#666666", + "fontSize": 12, + "lineHeight": 17, + "borderRadius": 6 + } + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-150-dashboard-widget.json b/flutter-sample/assets/test-configs/test-150-dashboard-widget.json new file mode 100644 index 0000000..90aee16 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-150-dashboard-widget.json @@ -0,0 +1,179 @@ +{ + "theme": { + "id": "test-150" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 1.0 + }, + "style": { + "backgroundColor": "#000000" + }, + "children": [ + { + "type": "element", + "id": "bg_image", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-113.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "offset": { + "x": 0, + "y": 0, + "unit": "percent" + } + }, + "imageConfig": { + "fit": "crop" + } + }, + { + "type": "container", + "id": "dark_overlay", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "offset": { + "x": 0, + "y": 0, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#88000000" + }, + "children": [] + }, + { + "type": "element", + "id": "dashboard_label", + "elementType": "text", + "bindings": { + "text": "DASHBOARD" + }, + "layout": { + "width": { + "value": 80, + "unit": "percent" + }, + "offset": { + "x": 10, + "y": 10, + "unit": "percent" + } + }, + "style": { + "fontSize": 11, + "fontWeight": "bold", + "lineHeight": 15, + "textColor": "#AAAAAA", + "textAlign": "center" + } + }, + { + "type": "element", + "id": "metric_value", + "elementType": "text", + "bindings": { + "text": "142" + }, + "layout": { + "width": { + "value": 80, + "unit": "percent" + }, + "offset": { + "x": 10, + "y": 35, + "unit": "percent" + } + }, + "style": { + "fontSize": 42, + "fontWeight": "bold", + "lineHeight": 52, + "textColor": "#FFFFFF", + "textAlign": "center" + } + }, + { + "type": "element", + "id": "metric_label", + "elementType": "text", + "bindings": { + "text": "Active Users" + }, + "layout": { + "width": { + "value": 80, + "unit": "percent" + }, + "offset": { + "x": 10, + "y": 62, + "unit": "percent" + } + }, + "style": { + "fontSize": 14, + "lineHeight": 20, + "textColor": "#CCCCCC", + "textAlign": "center" + } + }, + { + "type": "element", + "id": "report_button", + "elementType": "button", + "bindings": { + "text": "View Report" + }, + "layout": { + "width": { + "value": 50, + "unit": "percent" + }, + "height": { + "value": 10, + "unit": "percent" + }, + "offset": { + "x": 25, + "y": 80, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#2196F3", + "textColor": "#FFFFFF", + "fontSize": 13, + "lineHeight": 18, + "borderRadius": 6 + } + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-151-video-player-card.json b/flutter-sample/assets/test-configs/test-151-video-player-card.json new file mode 100644 index 0000000..748c591 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-151-video-player-card.json @@ -0,0 +1,129 @@ +{ + "theme": { + "id": "test-151" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 1.778 + }, + "style": { + "backgroundColor": "#000000" + }, + "children": [ + { + "type": "element", + "id": "video", + "elementType": "video", + "bindings": { + "url": "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "offset": { + "x": 0, + "y": 0, + "unit": "percent" + } + }, + "autoPlay": true, + "muted": true, + "loop": true, + "showControls": false, + "showFullscreen": false + }, + { + "type": "container", + "id": "info_bar", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 15, + "unit": "percent" + }, + "offset": { + "x": 0, + "y": 85, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#CC000000" + }, + "children": [ + { + "type": "element", + "id": "video_title", + "elementType": "text", + "bindings": { + "text": "Big Buck Bunny — 2008 Blender Film" + }, + "layout": { + "width": { + "value": 70, + "unit": "percent" + }, + "offset": { + "x": 5, + "y": 20, + "unit": "percent" + } + }, + "style": { + "fontSize": 13, + "lineHeight": 18, + "textColor": "#FFFFFF" + } + } + ] + }, + { + "type": "element", + "id": "share_button", + "elementType": "button", + "bindings": { + "text": "Share" + }, + "layout": { + "width": { + "value": 15, + "unit": "percent" + }, + "height": { + "value": 12, + "unit": "percent" + }, + "offset": { + "x": 82, + "y": 87, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#FF6B6B", + "textColor": "#FFFFFF", + "fontSize": 12, + "lineHeight": 17, + "borderRadius": 6 + } + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-152-text-corners.json b/flutter-sample/assets/test-configs/test-152-text-corners.json new file mode 100644 index 0000000..e775fb1 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-152-text-corners.json @@ -0,0 +1,134 @@ +{ + "theme": { + "id": "test-152" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 1.778 + }, + "style": { + "backgroundColor": "#F5F5F5" + }, + "children": [ + { + "type": "element", + "id": "corner_tl", + "elementType": "text", + "bindings": { + "text": "TL" + }, + "layout": { + "width": { + "value": 15, + "unit": "percent" + }, + "offset": { + "x": 2, + "y": 2, + "unit": "percent" + } + }, + "style": { + "fontSize": 12, + "fontWeight": "bold", + "lineHeight": 17, + "textColor": "#E53935", + "backgroundColor": "#FFCCCC", + "borderRadius": 4, + "textAlign": "center" + } + }, + { + "type": "element", + "id": "corner_tr", + "elementType": "text", + "bindings": { + "text": "TR" + }, + "layout": { + "width": { + "value": 15, + "unit": "percent" + }, + "offset": { + "x": 80, + "y": 2, + "unit": "percent" + } + }, + "style": { + "fontSize": 12, + "fontWeight": "bold", + "lineHeight": 17, + "textColor": "#1565C0", + "backgroundColor": "#BBDEFB", + "borderRadius": 4, + "textAlign": "center" + } + }, + { + "type": "element", + "id": "corner_bl", + "elementType": "text", + "bindings": { + "text": "BL" + }, + "layout": { + "width": { + "value": 15, + "unit": "percent" + }, + "offset": { + "x": 2, + "y": 85, + "unit": "percent" + } + }, + "style": { + "fontSize": 12, + "fontWeight": "bold", + "lineHeight": 17, + "textColor": "#2E7D32", + "backgroundColor": "#C8E6C9", + "borderRadius": 4, + "textAlign": "center" + } + }, + { + "type": "element", + "id": "corner_br", + "elementType": "text", + "bindings": { + "text": "BR" + }, + "layout": { + "width": { + "value": 15, + "unit": "percent" + }, + "offset": { + "x": 75, + "y": 85, + "unit": "percent" + } + }, + "style": { + "fontSize": 12, + "fontWeight": "bold", + "lineHeight": 17, + "textColor": "#E65100", + "backgroundColor": "#FFE0B2", + "borderRadius": 4, + "textAlign": "center" + } + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-153-image-clipped.json b/flutter-sample/assets/test-configs/test-153-image-clipped.json new file mode 100644 index 0000000..cc44150 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-153-image-clipped.json @@ -0,0 +1,48 @@ +{ + "theme": { + "id": "test-153" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 1.778 + }, + "style": { + "backgroundColor": "#DDDDDD" + }, + "children": [ + { + "type": "element", + "id": "clipped_image", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-1.jpg" + }, + "layout": { + "width": { + "value": 60, + "unit": "percent" + }, + "height": { + "value": 60, + "unit": "percent" + }, + "offset": { + "x": 40, + "y": 40, + "unit": "percent" + } + }, + "imageConfig": { + "fit": "crop" + } + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-154-nested-box-deep.json b/flutter-sample/assets/test-configs/test-154-nested-box-deep.json new file mode 100644 index 0000000..7adf4c6 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-154-nested-box-deep.json @@ -0,0 +1,100 @@ +{ + "theme": { + "id": "test-154" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 1.778 + }, + "style": { + "backgroundColor": "#1A1A2E" + }, + "children": [ + { + "type": "container", + "id": "level1_box", + "containerType": "box", + "layout": { + "width": { + "value": 50, + "unit": "percent" + }, + "height": { + "value": 50, + "unit": "percent" + }, + "offset": { + "x": 25, + "y": 25, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#FFFFFF", + "borderRadius": 8 + }, + "children": [ + { + "type": "container", + "id": "level2_box", + "containerType": "box", + "layout": { + "width": { + "value": 50, + "unit": "percent" + }, + "height": { + "value": 50, + "unit": "percent" + }, + "offset": { + "x": 25, + "y": 25, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#2196F3", + "borderRadius": 6 + }, + "children": [ + { + "type": "element", + "id": "deep_text", + "elementType": "text", + "bindings": { + "text": "Deep!" + }, + "layout": { + "width": { + "value": 80, + "unit": "percent" + }, + "offset": { + "x": 10, + "y": 35, + "unit": "percent" + } + }, + "style": { + "fontSize": 12, + "fontWeight": "bold", + "lineHeight": 17, + "textColor": "#FFFFFF", + "textAlign": "center" + } + } + ] + } + ] + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-155-all-element-types.json b/flutter-sample/assets/test-configs/test-155-all-element-types.json new file mode 100644 index 0000000..a7b1efd --- /dev/null +++ b/flutter-sample/assets/test-configs/test-155-all-element-types.json @@ -0,0 +1,141 @@ +{ + "theme": { + "id": "test-155", + "defaultStyle": { + "textColor": "#FFFFFF", + "fontSize": 14, + "lineHeight": 20 + } + }, + "variables": { + "testName": "All Element Types in 16:9 BOX" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 1.778 + }, + "style": { + "backgroundColor": "#000000" + }, + "children": [ + { + "type": "element", + "id": "bg-image", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-114.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "offset": { + "x": 0, + "y": 0, + "unit": "percent" + } + }, + "imageConfig": { + "fit": "crop" + } + }, + { + "type": "element", + "id": "label", + "elementType": "text", + "bindings": { + "text": "All Element Types" + }, + "layout": { + "width": { + "value": 60, + "unit": "percent" + }, + "offset": { + "x": 5, + "y": 5, + "unit": "percent" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "lineHeight": 20, + "textColor": "#FFFFFF", + "backgroundColor": "#88000000", + "borderRadius": 4 + } + }, + { + "type": "element", + "id": "video-inset", + "elementType": "video", + "bindings": { + "url": "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4" + }, + "layout": { + "width": { + "value": 40, + "unit": "percent" + }, + "height": { + "value": 40, + "unit": "percent" + }, + "offset": { + "x": 5, + "y": 30, + "unit": "percent" + } + }, + "autoPlay": true, + "loop": true, + "muted": true, + "showControls": false, + "showFullscreen": false + }, + { + "type": "element", + "id": "action-button", + "elementType": "button", + "bindings": { + "text": "Action" + }, + "layout": { + "width": { + "value": 35, + "unit": "percent" + }, + "height": { + "value": 12, + "unit": "percent" + }, + "offset": { + "x": 55, + "y": 38, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#FF6B6B", + "textColor": "#FFFFFF", + "fontSize": 14, + "lineHeight": 20, + "borderRadius": 8 + } + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-156-button-backgrounds.json b/flutter-sample/assets/test-configs/test-156-button-backgrounds.json new file mode 100644 index 0000000..6138b64 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-156-button-backgrounds.json @@ -0,0 +1,318 @@ +{ + "theme": { + "id": "test-156" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "vertical", + "layout": { + "width": { + "special": "match_parent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "all": 16, + "unit": "dp" + }, + "arrangement": { + "strategy": "spaced", + "spacing": 12, + "spacingUnit": "dp" + } + }, + "style": { + "backgroundColor": "#FFFFFF" + }, + "children": [ + { + "type": "element", + "id": "btn_solid_blue", + "elementType": "button", + "bindings": { + "text": "Solid Blue" + }, + "layout": { + "width": { + "value": 90, + "unit": "percent" + }, + "height": { + "value": 48, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#2196F3", + "textColor": "#FFFFFF", + "fontSize": 16, + "lineHeight": 22, + "fontWeight": "bold", + "borderRadius": 8 + } + }, + { + "type": "element", + "id": "btn_solid_red", + "elementType": "button", + "bindings": { + "text": "Vibrant Red" + }, + "layout": { + "width": { + "value": 90, + "unit": "percent" + }, + "height": { + "value": 48, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#E53935", + "textColor": "#FFFFFF", + "fontSize": 16, + "lineHeight": 22, + "fontWeight": "bold", + "borderRadius": 8 + } + }, + { + "type": "element", + "id": "btn_outlined", + "elementType": "button", + "bindings": { + "text": "Outlined" + }, + "layout": { + "width": { + "value": 90, + "unit": "percent" + }, + "height": { + "value": 48, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#00FFFFFF", + "textColor": "#2196F3", + "fontSize": 16, + "lineHeight": 22, + "fontWeight": "bold", + "borderRadius": 8, + "borderWidth": 2, + "borderColor": "#2196F3" + } + }, + { + "type": "element", + "id": "btn_gradient_diagonal", + "elementType": "button", + "bindings": { + "text": "Gradient Diagonal" + }, + "layout": { + "width": { + "value": 90, + "unit": "percent" + }, + "height": { + "value": 48, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#00000000", + "textColor": "#FFFFFF", + "fontSize": 16, + "lineHeight": 22, + "fontWeight": "bold", + "borderRadius": 8, + "background": { + "type": "linear_gradient", + "angle": 135, + "colors": [ + "#7B1FA2", + "#E91E63" + ], + "stops": [ + 0.0, + 1.0 + ] + } + } + }, + { + "type": "element", + "id": "btn_gradient_horizontal", + "elementType": "button", + "bindings": { + "text": "Gradient Horizontal" + }, + "layout": { + "width": { + "value": 90, + "unit": "percent" + }, + "height": { + "value": 48, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#00000000", + "textColor": "#FFFFFF", + "fontSize": 16, + "lineHeight": 22, + "fontWeight": "bold", + "borderRadius": 8, + "background": { + "type": "linear_gradient", + "angle": 90, + "colors": [ + "#1565C0", + "#42A5F5" + ], + "stops": [ + 0.0, + 1.0 + ] + } + } + }, + { + "type": "element", + "id": "btn_dark", + "elementType": "button", + "bindings": { + "text": "Dark Fill" + }, + "layout": { + "width": { + "value": 90, + "unit": "percent" + }, + "height": { + "value": 48, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#212121", + "textColor": "#FFFFFF", + "fontSize": 16, + "lineHeight": 22, + "fontWeight": "bold", + "borderRadius": 8 + } + }, + { + "type": "element", + "id": "btn_pill", + "elementType": "button", + "bindings": { + "text": "Rounded Pill" + }, + "layout": { + "width": { + "value": 90, + "unit": "percent" + }, + "height": { + "value": 48, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#43A047", + "textColor": "#FFFFFF", + "fontSize": 16, + "lineHeight": 22, + "fontWeight": "bold", + "borderRadius": 50 + } + }, + { + "type": "element", + "id": "btn_ghost", + "elementType": "button", + "bindings": { + "text": "Ghost / Muted" + }, + "layout": { + "width": { + "value": 90, + "unit": "percent" + }, + "height": { + "value": 48, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#EEEEEE", + "textColor": "#424242", + "fontSize": 16, + "lineHeight": 22, + "fontWeight": "bold", + "borderRadius": 8 + } + }, + { + "type": "element", + "id": "btn_warning", + "elementType": "button", + "bindings": { + "text": "Warning Orange" + }, + "layout": { + "width": { + "value": 90, + "unit": "percent" + }, + "height": { + "value": 48, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF6F00", + "textColor": "#FFFFFF", + "fontSize": 16, + "lineHeight": 22, + "fontWeight": "bold", + "borderRadius": 8 + } + }, + { + "type": "element", + "id": "btn_teal", + "elementType": "button", + "bindings": { + "text": "Success Teal" + }, + "layout": { + "width": { + "value": 90, + "unit": "percent" + }, + "height": { + "value": 48, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#00897B", + "textColor": "#FFFFFF", + "fontSize": 16, + "lineHeight": 22, + "fontWeight": "bold", + "borderRadius": 8 + } + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-157-gallery-box-freeflow-indicators-navbtns.json b/flutter-sample/assets/test-configs/test-157-gallery-box-freeflow-indicators-navbtns.json new file mode 100644 index 0000000..820b2f7 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-157-gallery-box-freeflow-indicators-navbtns.json @@ -0,0 +1,814 @@ +{ + "theme": { + "id": "gallery-157" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "vertical", + "layout": { + "width": { + "special": "match_parent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "all": 12, + "unit": "dp" + } + }, + "children": [ + { + "type": "container", + "id": "outer-card", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 1.15 + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 20, + "shadowColor": "#40000000", + "shadowRadius": 16, + "shadowOffsetX": 0, + "shadowOffsetY": 6 + }, + "children": [ + { + "type": "container", + "id": "card-content", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "children": [ + { + "type": "container", + "id": "title-section", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 22, + "unit": "percent" + }, + "padding": { + "left": 16, + "right": 16, + "top": 14, + "bottom": 10, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF2563EB" + }, + "children": [ + { + "type": "container", + "id": "title-content", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "arrangement": { + "spacing": 6, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "category-tag", + "elementType": "text", + "bindings": { + "text": "LIFESTYLE" + }, + "style": { + "fontSize": 11, + "fontWeight": "bold", + "textColor": "#CCFFFFFF", + "lineHeight": 16 + } + }, + { + "type": "element", + "id": "big-title", + "elementType": "text", + "bindings": { + "text": "Summer Picks" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 22, + "fontWeight": "bold", + "textColor": "#FFFFFFFF", + "lineHeight": 30 + } + } + ] + }, + { + "type": "element", + "id": "see-all", + "elementType": "text", + "bindings": { + "text": "All →" + }, + "layout": { + "width": { + "special": "wrap_content" + }, + "height": { + "special": "wrap_content" + }, + "offset": { + "x": 76, + "y": 10, + "unit": "percent" + } + }, + "style": { + "fontSize": 13, + "textColor": "#DDFFFFFF", + "lineHeight": 18, + "maxLines": 1 + } + } + ] + }, + { + "type": "container", + "id": "gallery-wrapper", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 58, + "unit": "percent" + } + }, + "children": [ + { + "type": "container", + "id": "gallery", + "containerType": "gallery", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "galleryConfig": { + "mode": "free_flow", + "orientation": "horizontal", + "spacing": 10, + "showIndicators": true + }, + "children": [ + { + "type": "container", + "id": "item-1", + "containerType": "vertical", + "layout": { + "width": { + "value": 45, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 14, + "shadowColor": "#1A000000", + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 3 + }, + "children": [ + { + "type": "element", + "id": "item-1-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-31.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 65, + "unit": "percent" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-1-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 35, + "unit": "percent" + }, + "padding": { + "left": 10, + "right": 10, + "top": 8, + "bottom": 8, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 4, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-1-title", + "elementType": "text", + "bindings": { + "text": "Floral Dress" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 20, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-1-subtitle", + "elementType": "text", + "bindings": { + "text": "Breezy & light" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 12, + "textColor": "#FF6B7280", + "lineHeight": 17, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-1-btn", + "elementType": "button", + "bindings": { + "text": "Shop Now" + }, + "layout": { + "width": { + "value": 88, + "unit": "percent" + }, + "height": { + "value": 30, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF2563EB", + "textColor": "#FFFFFFFF", + "fontSize": 12, + "fontWeight": "bold", + "borderRadius": 8 + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-2", + "containerType": "vertical", + "layout": { + "width": { + "value": 45, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 14, + "shadowColor": "#1A000000", + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 3 + }, + "children": [ + { + "type": "element", + "id": "item-2-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-32.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 65, + "unit": "percent" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-2-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 35, + "unit": "percent" + }, + "padding": { + "left": 10, + "right": 10, + "top": 8, + "bottom": 8, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 4, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-2-title", + "elementType": "text", + "bindings": { + "text": "Linen Shorts" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 20, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-2-subtitle", + "elementType": "text", + "bindings": { + "text": "Easy comfort" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 12, + "textColor": "#FF6B7280", + "lineHeight": 17, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-2-btn", + "elementType": "button", + "bindings": { + "text": "Shop Now" + }, + "layout": { + "width": { + "value": 88, + "unit": "percent" + }, + "height": { + "value": 30, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF2563EB", + "textColor": "#FFFFFFFF", + "fontSize": 12, + "fontWeight": "bold", + "borderRadius": 8 + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-3", + "containerType": "vertical", + "layout": { + "width": { + "value": 45, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 14, + "shadowColor": "#1A000000", + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 3 + }, + "children": [ + { + "type": "element", + "id": "item-3-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-33.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 65, + "unit": "percent" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-3-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 35, + "unit": "percent" + }, + "padding": { + "left": 10, + "right": 10, + "top": 8, + "bottom": 8, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 4, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-3-title", + "elementType": "text", + "bindings": { + "text": "Straw Hat" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 20, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-3-subtitle", + "elementType": "text", + "bindings": { + "text": "Beach vibes" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 12, + "textColor": "#FF6B7280", + "lineHeight": 17, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-3-btn", + "elementType": "button", + "bindings": { + "text": "Shop Now" + }, + "layout": { + "width": { + "value": 88, + "unit": "percent" + }, + "height": { + "value": 30, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF2563EB", + "textColor": "#FFFFFFFF", + "fontSize": 12, + "fontWeight": "bold", + "borderRadius": 8 + } + } + ] + } + ] + } + ] + }, + { + "type": "container", + "id": "nav-overlay", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "children": [ + { + "type": "element", + "id": "nav-prev", + "elementType": "button", + "bindings": { + "text": "‹" + }, + "layout": { + "width": { + "value": 11, + "unit": "percent" + }, + "height": { + "value": 44, + "unit": "dp" + }, + "offset": { + "x": 2, + "y": 38, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#CC000000", + "textColor": "#FFFFFFFF", + "fontSize": 22, + "borderRadius": 22 + } + }, + { + "type": "element", + "id": "nav-next", + "elementType": "button", + "bindings": { + "text": "›" + }, + "layout": { + "width": { + "value": 11, + "unit": "percent" + }, + "height": { + "value": 44, + "unit": "dp" + }, + "offset": { + "x": 87, + "y": 38, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#CC000000", + "textColor": "#FFFFFFFF", + "fontSize": 22, + "borderRadius": 22 + } + } + ] + } + ] + }, + { + "type": "container", + "id": "footer", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 20, + "unit": "percent" + }, + "padding": { + "left": 16, + "right": 12, + "unit": "dp" + } + }, + "arrangement": { + "strategy": "space_between" + }, + "style": { + "backgroundColor": "#FFFAFAFA" + }, + "children": [ + { + "type": "element", + "id": "footer-info", + "elementType": "text", + "bindings": { + "text": "Free shipping on ₹499+" + }, + "layout": { + "width": { + "value": 55, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#FF6B7280", + "lineHeight": 22, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "footer-cta", + "elementType": "button", + "bindings": { + "text": "Shop Now" + }, + "layout": { + "width": { + "value": 38, + "unit": "percent" + }, + "height": { + "value": 44, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF2563EB", + "textColor": "#FFFFFFFF", + "fontSize": 14, + "fontWeight": "bold", + "borderRadius": 18 + } + } + ] + } + ] + } + ] + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-158-gallery-box-freeflow-indicators-only.json b/flutter-sample/assets/test-configs/test-158-gallery-box-freeflow-indicators-only.json new file mode 100644 index 0000000..490bca7 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-158-gallery-box-freeflow-indicators-only.json @@ -0,0 +1,665 @@ +{ + "theme": { + "id": "gallery-158" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "vertical", + "layout": { + "width": { + "special": "match_parent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "all": 12, + "unit": "dp" + } + }, + "children": [ + { + "type": "container", + "id": "outer-card", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 1.15 + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 20, + "shadowColor": "#40000000", + "shadowRadius": 16, + "shadowOffsetX": 0, + "shadowOffsetY": 6 + }, + "children": [ + { + "type": "container", + "id": "card-content", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "children": [ + { + "type": "container", + "id": "title-section", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 22, + "unit": "percent" + }, + "padding": { + "left": 16, + "right": 16, + "top": 14, + "bottom": 10, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF7C3AED" + }, + "children": [ + { + "type": "container", + "id": "title-content", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "arrangement": { + "spacing": 6, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "category-tag", + "elementType": "text", + "bindings": { + "text": "HOME DECOR" + }, + "style": { + "fontSize": 11, + "fontWeight": "bold", + "textColor": "#CCFFFFFF", + "lineHeight": 16 + } + }, + { + "type": "element", + "id": "big-title", + "elementType": "text", + "bindings": { + "text": "Living Spaces" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 22, + "fontWeight": "bold", + "textColor": "#FFFFFFFF", + "lineHeight": 30 + } + } + ] + }, + { + "type": "element", + "id": "see-all", + "elementType": "text", + "bindings": { + "text": "All →" + }, + "layout": { + "width": { + "special": "wrap_content" + }, + "height": { + "special": "wrap_content" + }, + "offset": { + "x": 76, + "y": 10, + "unit": "percent" + } + }, + "style": { + "fontSize": 13, + "textColor": "#DDFFFFFF", + "lineHeight": 18, + "maxLines": 1 + } + } + ] + }, + { + "type": "container", + "id": "gallery-wrapper", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 58, + "unit": "percent" + } + }, + "children": [ + { + "type": "container", + "id": "gallery", + "containerType": "gallery", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "galleryConfig": { + "mode": "free_flow", + "orientation": "horizontal", + "spacing": 10, + "showIndicators": true + }, + "children": [ + { + "type": "container", + "id": "item-1", + "containerType": "vertical", + "layout": { + "width": { + "value": 45, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 14, + "shadowColor": "#1A000000", + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 3 + }, + "children": [ + { + "type": "element", + "id": "item-1-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-49.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 65, + "unit": "percent" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-1-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 35, + "unit": "percent" + }, + "padding": { + "left": 10, + "right": 10, + "top": 8, + "bottom": 8, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 4, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-1-title", + "elementType": "text", + "bindings": { + "text": "Boho Cushion" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 20, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-1-subtitle", + "elementType": "text", + "bindings": { + "text": "Earthy tones" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 12, + "textColor": "#FF6B7280", + "lineHeight": 17, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-2", + "containerType": "vertical", + "layout": { + "width": { + "value": 45, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 14, + "shadowColor": "#1A000000", + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 3 + }, + "children": [ + { + "type": "element", + "id": "item-2-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-50.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 65, + "unit": "percent" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-2-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 35, + "unit": "percent" + }, + "padding": { + "left": 10, + "right": 10, + "top": 8, + "bottom": 8, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 4, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-2-title", + "elementType": "text", + "bindings": { + "text": "Woven Lamp" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 20, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-2-subtitle", + "elementType": "text", + "bindings": { + "text": "Warm glow" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 12, + "textColor": "#FF6B7280", + "lineHeight": 17, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-3", + "containerType": "vertical", + "layout": { + "width": { + "value": 45, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 14, + "shadowColor": "#1A000000", + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 3 + }, + "children": [ + { + "type": "element", + "id": "item-3-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-51.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 65, + "unit": "percent" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-3-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 35, + "unit": "percent" + }, + "padding": { + "left": 10, + "right": 10, + "top": 8, + "bottom": 8, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 4, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-3-title", + "elementType": "text", + "bindings": { + "text": "Ceramic Pot" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 20, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-3-subtitle", + "elementType": "text", + "bindings": { + "text": "Minimalist" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 12, + "textColor": "#FF6B7280", + "lineHeight": 17, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "container", + "id": "footer", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 20, + "unit": "percent" + }, + "padding": { + "left": 16, + "right": 12, + "unit": "dp" + } + }, + "arrangement": { + "strategy": "space_between" + }, + "style": { + "backgroundColor": "#FFFAFAFA" + }, + "children": [ + { + "type": "element", + "id": "footer-info", + "elementType": "text", + "bindings": { + "text": "★ 4.9 · 240+ designs" + }, + "layout": { + "width": { + "value": 55, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#FF6B7280", + "lineHeight": 22, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "footer-cta", + "elementType": "button", + "bindings": { + "text": "Explore" + }, + "layout": { + "width": { + "value": 38, + "unit": "percent" + }, + "height": { + "value": 44, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF7C3AED", + "textColor": "#FFFFFFFF", + "fontSize": 14, + "fontWeight": "bold", + "borderRadius": 18 + } + } + ] + } + ] + } + ] + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-159-gallery-box-freeflow-navbtns-only.json b/flutter-sample/assets/test-configs/test-159-gallery-box-freeflow-navbtns-only.json new file mode 100644 index 0000000..5d85b0f --- /dev/null +++ b/flutter-sample/assets/test-configs/test-159-gallery-box-freeflow-navbtns-only.json @@ -0,0 +1,742 @@ +{ + "theme": { + "id": "gallery-159" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "vertical", + "layout": { + "width": { + "special": "match_parent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "all": 12, + "unit": "dp" + } + }, + "children": [ + { + "type": "container", + "id": "outer-card", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 1.15 + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 20, + "shadowColor": "#40000000", + "shadowRadius": 16, + "shadowOffsetX": 0, + "shadowOffsetY": 6 + }, + "children": [ + { + "type": "container", + "id": "card-content", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "children": [ + { + "type": "container", + "id": "title-section", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 22, + "unit": "percent" + }, + "padding": { + "left": 16, + "right": 16, + "top": 14, + "bottom": 10, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FFE11D48" + }, + "children": [ + { + "type": "container", + "id": "title-content", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "arrangement": { + "spacing": 6, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "category-tag", + "elementType": "text", + "bindings": { + "text": "TRENDING" + }, + "style": { + "fontSize": 11, + "fontWeight": "bold", + "textColor": "#CCFFFFFF", + "lineHeight": 16 + } + }, + { + "type": "element", + "id": "big-title", + "elementType": "text", + "bindings": { + "text": "What's Hot Now" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 22, + "fontWeight": "bold", + "textColor": "#FFFFFFFF", + "lineHeight": 30 + } + } + ] + }, + { + "type": "element", + "id": "see-all", + "elementType": "text", + "bindings": { + "text": "All →" + }, + "layout": { + "width": { + "special": "wrap_content" + }, + "height": { + "special": "wrap_content" + }, + "offset": { + "x": 76, + "y": 10, + "unit": "percent" + } + }, + "style": { + "fontSize": 13, + "textColor": "#DDFFFFFF", + "lineHeight": 18, + "maxLines": 1 + } + } + ] + }, + { + "type": "container", + "id": "gallery-wrapper", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 58, + "unit": "percent" + } + }, + "children": [ + { + "type": "container", + "id": "gallery", + "containerType": "gallery", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "galleryConfig": { + "mode": "free_flow", + "orientation": "horizontal", + "spacing": 10, + "showIndicators": false + }, + "children": [ + { + "type": "container", + "id": "item-1", + "containerType": "vertical", + "layout": { + "width": { + "value": 50, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 14, + "shadowColor": "#1A000000", + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 3 + }, + "children": [ + { + "type": "element", + "id": "item-1-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-52.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 65, + "unit": "percent" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-1-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 35, + "unit": "percent" + }, + "padding": { + "left": 10, + "right": 10, + "top": 8, + "bottom": 8, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 4, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-1-title", + "elementType": "text", + "bindings": { + "text": "Vintage Tee" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 20, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-1-btn", + "elementType": "button", + "bindings": { + "text": "See More" + }, + "layout": { + "width": { + "value": 88, + "unit": "percent" + }, + "height": { + "value": 30, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FFE11D48", + "textColor": "#FFFFFFFF", + "fontSize": 12, + "fontWeight": "bold", + "borderRadius": 8 + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-2", + "containerType": "vertical", + "layout": { + "width": { + "value": 50, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 14, + "shadowColor": "#1A000000", + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 3 + }, + "children": [ + { + "type": "element", + "id": "item-2-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-53.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 65, + "unit": "percent" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-2-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 35, + "unit": "percent" + }, + "padding": { + "left": 10, + "right": 10, + "top": 8, + "bottom": 8, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 4, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-2-title", + "elementType": "text", + "bindings": { + "text": "Cargo Pants" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 20, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-2-btn", + "elementType": "button", + "bindings": { + "text": "See More" + }, + "layout": { + "width": { + "value": 88, + "unit": "percent" + }, + "height": { + "value": 30, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FFE11D48", + "textColor": "#FFFFFFFF", + "fontSize": 12, + "fontWeight": "bold", + "borderRadius": 8 + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-3", + "containerType": "vertical", + "layout": { + "width": { + "value": 50, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 14, + "shadowColor": "#1A000000", + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 3 + }, + "children": [ + { + "type": "element", + "id": "item-3-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-54.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 65, + "unit": "percent" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-3-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 35, + "unit": "percent" + }, + "padding": { + "left": 10, + "right": 10, + "top": 8, + "bottom": 8, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 4, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-3-title", + "elementType": "text", + "bindings": { + "text": "Sneakers" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 20, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-3-btn", + "elementType": "button", + "bindings": { + "text": "See More" + }, + "layout": { + "width": { + "value": 88, + "unit": "percent" + }, + "height": { + "value": 30, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FFE11D48", + "textColor": "#FFFFFFFF", + "fontSize": 12, + "fontWeight": "bold", + "borderRadius": 8 + } + } + ] + } + ] + } + ] + }, + { + "type": "container", + "id": "nav-overlay", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "children": [ + { + "type": "element", + "id": "nav-prev", + "elementType": "button", + "bindings": { + "text": "‹" + }, + "layout": { + "width": { + "value": 11, + "unit": "percent" + }, + "height": { + "value": 44, + "unit": "dp" + }, + "offset": { + "x": 2, + "y": 38, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#CC000000", + "textColor": "#FFFFFFFF", + "fontSize": 22, + "borderRadius": 22 + } + }, + { + "type": "element", + "id": "nav-next", + "elementType": "button", + "bindings": { + "text": "›" + }, + "layout": { + "width": { + "value": 11, + "unit": "percent" + }, + "height": { + "value": 44, + "unit": "dp" + }, + "offset": { + "x": 87, + "y": 38, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#CC000000", + "textColor": "#FFFFFFFF", + "fontSize": 22, + "borderRadius": 22 + } + } + ] + } + ] + }, + { + "type": "container", + "id": "footer", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 20, + "unit": "percent" + }, + "padding": { + "left": 16, + "right": 12, + "unit": "dp" + } + }, + "arrangement": { + "strategy": "space_between" + }, + "style": { + "backgroundColor": "#FFFAFAFA" + }, + "children": [ + { + "type": "element", + "id": "footer-info", + "elementType": "text", + "bindings": { + "text": "Trending this week" + }, + "layout": { + "width": { + "value": 55, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#FF6B7280", + "lineHeight": 22, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "footer-cta", + "elementType": "button", + "bindings": { + "text": "See More" + }, + "layout": { + "width": { + "value": 38, + "unit": "percent" + }, + "height": { + "value": 44, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FFE11D48", + "textColor": "#FFFFFFFF", + "fontSize": 14, + "fontWeight": "bold", + "borderRadius": 18 + } + } + ] + } + ] + } + ] + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-160-gallery-box-freeflow-minimal.json b/flutter-sample/assets/test-configs/test-160-gallery-box-freeflow-minimal.json new file mode 100644 index 0000000..9ea6bae --- /dev/null +++ b/flutter-sample/assets/test-configs/test-160-gallery-box-freeflow-minimal.json @@ -0,0 +1,593 @@ +{ + "theme": { + "id": "gallery-160" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "vertical", + "layout": { + "width": { + "special": "match_parent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "all": 12, + "unit": "dp" + } + }, + "children": [ + { + "type": "container", + "id": "outer-card", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 1.15 + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 20, + "shadowColor": "#40000000", + "shadowRadius": 16, + "shadowOffsetX": 0, + "shadowOffsetY": 6 + }, + "children": [ + { + "type": "container", + "id": "card-content", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "children": [ + { + "type": "container", + "id": "title-section", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 22, + "unit": "percent" + }, + "padding": { + "left": 16, + "right": 16, + "top": 14, + "bottom": 10, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF0D9488" + }, + "children": [ + { + "type": "container", + "id": "title-content", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "arrangement": { + "spacing": 6, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "category-tag", + "elementType": "text", + "bindings": { + "text": "ECO PICKS" + }, + "style": { + "fontSize": 11, + "fontWeight": "bold", + "textColor": "#CCFFFFFF", + "lineHeight": 16 + } + }, + { + "type": "element", + "id": "big-title", + "elementType": "text", + "bindings": { + "text": "Go Green" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 22, + "fontWeight": "bold", + "textColor": "#FFFFFFFF", + "lineHeight": 30 + } + } + ] + }, + { + "type": "element", + "id": "see-all", + "elementType": "text", + "bindings": { + "text": "All →" + }, + "layout": { + "width": { + "special": "wrap_content" + }, + "height": { + "special": "wrap_content" + }, + "offset": { + "x": 76, + "y": 10, + "unit": "percent" + } + }, + "style": { + "fontSize": 13, + "textColor": "#DDFFFFFF", + "lineHeight": 18, + "maxLines": 1 + } + } + ] + }, + { + "type": "container", + "id": "gallery-wrapper", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 58, + "unit": "percent" + } + }, + "children": [ + { + "type": "container", + "id": "gallery", + "containerType": "gallery", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "galleryConfig": { + "mode": "free_flow", + "orientation": "horizontal", + "spacing": 10, + "showIndicators": false + }, + "children": [ + { + "type": "container", + "id": "item-1", + "containerType": "vertical", + "layout": { + "width": { + "value": 50, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 14, + "shadowColor": "#1A000000", + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 3 + }, + "children": [ + { + "type": "element", + "id": "item-1-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-31.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 65, + "unit": "percent" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-1-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 35, + "unit": "percent" + }, + "padding": { + "left": 10, + "right": 10, + "top": 8, + "bottom": 8, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 4, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-1-title", + "elementType": "text", + "bindings": { + "text": "Bamboo Cup" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 20, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-2", + "containerType": "vertical", + "layout": { + "width": { + "value": 50, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 14, + "shadowColor": "#1A000000", + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 3 + }, + "children": [ + { + "type": "element", + "id": "item-2-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-49.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 65, + "unit": "percent" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-2-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 35, + "unit": "percent" + }, + "padding": { + "left": 10, + "right": 10, + "top": 8, + "bottom": 8, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 4, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-2-title", + "elementType": "text", + "bindings": { + "text": "Hemp Bag" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 20, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-3", + "containerType": "vertical", + "layout": { + "width": { + "value": 50, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 14, + "shadowColor": "#1A000000", + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 3 + }, + "children": [ + { + "type": "element", + "id": "item-3-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-52.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 65, + "unit": "percent" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-3-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 35, + "unit": "percent" + }, + "padding": { + "left": 10, + "right": 10, + "top": 8, + "bottom": 8, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 4, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-3-title", + "elementType": "text", + "bindings": { + "text": "Organic Tee" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 20, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "container", + "id": "footer", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 20, + "unit": "percent" + }, + "padding": { + "left": 16, + "right": 12, + "unit": "dp" + } + }, + "arrangement": { + "strategy": "space_between" + }, + "style": { + "backgroundColor": "#FFFAFAFA" + }, + "children": [ + { + "type": "element", + "id": "footer-info", + "elementType": "text", + "bindings": { + "text": "Sustainably sourced" + }, + "layout": { + "width": { + "value": 55, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#FF6B7280", + "lineHeight": 22, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "footer-cta", + "elementType": "button", + "bindings": { + "text": "Go Green" + }, + "layout": { + "width": { + "value": 38, + "unit": "percent" + }, + "height": { + "value": 44, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF0D9488", + "textColor": "#FFFFFFFF", + "fontSize": 14, + "fontWeight": "bold", + "borderRadius": 18 + } + } + ] + } + ] + } + ] + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-161-gallery-box-freeflow-tall-images.json b/flutter-sample/assets/test-configs/test-161-gallery-box-freeflow-tall-images.json new file mode 100644 index 0000000..ebc26a9 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-161-gallery-box-freeflow-tall-images.json @@ -0,0 +1,667 @@ +{ + "theme": { + "id": "gallery-161" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "vertical", + "layout": { + "width": { + "special": "match_parent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "all": 12, + "unit": "dp" + } + }, + "children": [ + { + "type": "container", + "id": "outer-card", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 1.2 + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 20, + "shadowColor": "#40000000", + "shadowRadius": 16, + "shadowOffsetX": 0, + "shadowOffsetY": 6 + }, + "children": [ + { + "type": "container", + "id": "card-content", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "children": [ + { + "type": "container", + "id": "title-section", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 22, + "unit": "percent" + }, + "padding": { + "left": 16, + "right": 16, + "top": 14, + "bottom": 10, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FFD97706" + }, + "children": [ + { + "type": "container", + "id": "title-content", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "arrangement": { + "spacing": 6, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "category-tag", + "elementType": "text", + "bindings": { + "text": "FLASH SALE" + }, + "style": { + "fontSize": 11, + "fontWeight": "bold", + "textColor": "#CCFFFFFF", + "lineHeight": 16 + } + }, + { + "type": "element", + "id": "big-title", + "elementType": "text", + "bindings": { + "text": "Today's Deals" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 22, + "fontWeight": "bold", + "textColor": "#FFFFFFFF", + "lineHeight": 30 + } + } + ] + }, + { + "type": "element", + "id": "see-all", + "elementType": "text", + "bindings": { + "text": "All →" + }, + "layout": { + "width": { + "special": "wrap_content" + }, + "height": { + "special": "wrap_content" + }, + "offset": { + "x": 76, + "y": 10, + "unit": "percent" + } + }, + "style": { + "fontSize": 13, + "textColor": "#DDFFFFFF", + "lineHeight": 18, + "maxLines": 1 + } + } + ] + }, + { + "type": "container", + "id": "gallery-wrapper", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 58, + "unit": "percent" + } + }, + "children": [ + { + "type": "container", + "id": "gallery", + "containerType": "gallery", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "galleryConfig": { + "mode": "free_flow", + "orientation": "horizontal", + "spacing": 10, + "showIndicators": true + }, + "children": [ + { + "type": "container", + "id": "item-1", + "containerType": "vertical", + "layout": { + "width": { + "value": 40, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 14, + "shadowColor": "#1A000000", + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 3 + }, + "children": [ + { + "type": "element", + "id": "item-1-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-33.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 70, + "unit": "percent" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-1-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 30, + "unit": "percent" + }, + "padding": { + "left": 10, + "right": 10, + "top": 8, + "bottom": 8, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 4, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-1-title", + "elementType": "text", + "bindings": { + "text": "Kurta Set" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 20, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-2", + "containerType": "vertical", + "layout": { + "width": { + "value": 40, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 14, + "shadowColor": "#1A000000", + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 3 + }, + "children": [ + { + "type": "element", + "id": "item-2-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-50.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 70, + "unit": "percent" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-2-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 30, + "unit": "percent" + }, + "padding": { + "left": 10, + "right": 10, + "top": 8, + "bottom": 8, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 4, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-2-title", + "elementType": "text", + "bindings": { + "text": "Block Print" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 20, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-3", + "containerType": "vertical", + "layout": { + "width": { + "value": 40, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 14, + "shadowColor": "#1A000000", + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 3 + }, + "children": [ + { + "type": "element", + "id": "item-3-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-53.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 70, + "unit": "percent" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-3-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 30, + "unit": "percent" + }, + "padding": { + "left": 10, + "right": 10, + "top": 8, + "bottom": 8, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 4, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-3-title", + "elementType": "text", + "bindings": { + "text": "Silk Scarf" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 20, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + } + ] + }, + { + "type": "container", + "id": "nav-overlay", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "children": [ + { + "type": "element", + "id": "nav-prev", + "elementType": "button", + "bindings": { + "text": "‹" + }, + "layout": { + "width": { + "value": 11, + "unit": "percent" + }, + "height": { + "value": 44, + "unit": "dp" + }, + "offset": { + "x": 2, + "y": 38, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#CC000000", + "textColor": "#FFFFFFFF", + "fontSize": 22, + "borderRadius": 22 + } + }, + { + "type": "element", + "id": "nav-next", + "elementType": "button", + "bindings": { + "text": "›" + }, + "layout": { + "width": { + "value": 11, + "unit": "percent" + }, + "height": { + "value": 44, + "unit": "dp" + }, + "offset": { + "x": 87, + "y": 38, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#CC000000", + "textColor": "#FFFFFFFF", + "fontSize": 22, + "borderRadius": 22 + } + } + ] + } + ] + }, + { + "type": "container", + "id": "footer", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 20, + "unit": "percent" + }, + "padding": { + "left": 16, + "right": 12, + "unit": "dp" + } + }, + "arrangement": { + "strategy": "space_between" + }, + "style": { + "backgroundColor": "#FFFAFAFA" + }, + "children": [ + { + "type": "element", + "id": "footer-info", + "elementType": "text", + "bindings": { + "text": "Ends in 2h 30m" + }, + "layout": { + "width": { + "value": 55, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#FF6B7280", + "lineHeight": 22, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "footer-cta", + "elementType": "button", + "bindings": { + "text": "Get Deal" + }, + "layout": { + "width": { + "value": 38, + "unit": "percent" + }, + "height": { + "value": 44, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FFD97706", + "textColor": "#FFFFFFFF", + "fontSize": 14, + "fontWeight": "bold", + "borderRadius": 18 + } + } + ] + } + ] + } + ] + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-162-gallery-box-freeflow-video-items.json b/flutter-sample/assets/test-configs/test-162-gallery-box-freeflow-video-items.json new file mode 100644 index 0000000..4c29b9a --- /dev/null +++ b/flutter-sample/assets/test-configs/test-162-gallery-box-freeflow-video-items.json @@ -0,0 +1,736 @@ +{ + "theme": { + "id": "gallery-162" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "vertical", + "layout": { + "width": { + "special": "match_parent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "all": 12, + "unit": "dp" + } + }, + "children": [ + { + "type": "container", + "id": "outer-card", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 1.15 + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 20, + "shadowColor": "#40000000", + "shadowRadius": 16, + "shadowOffsetX": 0, + "shadowOffsetY": 6 + }, + "children": [ + { + "type": "container", + "id": "card-content", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "children": [ + { + "type": "container", + "id": "title-section", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 22, + "unit": "percent" + }, + "padding": { + "left": 16, + "right": 16, + "top": 14, + "bottom": 10, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF4338CA" + }, + "children": [ + { + "type": "container", + "id": "title-content", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "arrangement": { + "spacing": 6, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "category-tag", + "elementType": "text", + "bindings": { + "text": "PREMIUM" + }, + "style": { + "fontSize": 11, + "fontWeight": "bold", + "textColor": "#CCFFFFFF", + "lineHeight": 16 + } + }, + { + "type": "element", + "id": "big-title", + "elementType": "text", + "bindings": { + "text": "Editor's Choice" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 22, + "fontWeight": "bold", + "textColor": "#FFFFFFFF", + "lineHeight": 30 + } + } + ] + }, + { + "type": "element", + "id": "see-all", + "elementType": "text", + "bindings": { + "text": "All →" + }, + "layout": { + "width": { + "special": "wrap_content" + }, + "height": { + "special": "wrap_content" + }, + "offset": { + "x": 76, + "y": 10, + "unit": "percent" + } + }, + "style": { + "fontSize": 13, + "textColor": "#DDFFFFFF", + "lineHeight": 18, + "maxLines": 1 + } + } + ] + }, + { + "type": "container", + "id": "gallery-wrapper", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 58, + "unit": "percent" + } + }, + "children": [ + { + "type": "container", + "id": "gallery", + "containerType": "gallery", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "galleryConfig": { + "mode": "free_flow", + "orientation": "horizontal", + "spacing": 10, + "showIndicators": false + }, + "children": [ + { + "type": "container", + "id": "item-1", + "containerType": "vertical", + "layout": { + "width": { + "value": 45, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 14, + "shadowColor": "#1A000000", + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 3 + }, + "children": [ + { + "type": "element", + "id": "item-1-video", + "elementType": "video", + "bindings": { + "url": "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4", + "autoPlay": "false", + "loop": "false", + "muted": "true", + "showControls": "true" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 65, + "unit": "percent" + } + } + }, + { + "type": "container", + "id": "item-1-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 35, + "unit": "percent" + }, + "padding": { + "left": 10, + "right": 10, + "top": 8, + "bottom": 8, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 4, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-1-title", + "elementType": "text", + "bindings": { + "text": "Campaign 2026" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 20, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-1-subtitle", + "elementType": "text", + "bindings": { + "text": "3 min · HD" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 12, + "textColor": "#FF6B7280", + "lineHeight": 17, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-2", + "containerType": "vertical", + "layout": { + "width": { + "value": 45, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 14, + "shadowColor": "#1A000000", + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 3 + }, + "children": [ + { + "type": "element", + "id": "item-2-video", + "elementType": "video", + "bindings": { + "url": "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4", + "autoPlay": "false", + "loop": "false", + "muted": "true", + "showControls": "true" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 65, + "unit": "percent" + } + } + }, + { + "type": "container", + "id": "item-2-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 35, + "unit": "percent" + }, + "padding": { + "left": 10, + "right": 10, + "top": 8, + "bottom": 8, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 4, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-2-title", + "elementType": "text", + "bindings": { + "text": "Behind Scenes" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 20, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-2-subtitle", + "elementType": "text", + "bindings": { + "text": "5 min · HD" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 12, + "textColor": "#FF6B7280", + "lineHeight": 17, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-3", + "containerType": "vertical", + "layout": { + "width": { + "value": 45, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 14, + "shadowColor": "#1A000000", + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 3 + }, + "children": [ + { + "type": "element", + "id": "item-3-video", + "elementType": "video", + "bindings": { + "url": "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4", + "autoPlay": "false", + "loop": "false", + "muted": "true", + "showControls": "true" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 65, + "unit": "percent" + } + } + }, + { + "type": "container", + "id": "item-3-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 35, + "unit": "percent" + }, + "padding": { + "left": 10, + "right": 10, + "top": 8, + "bottom": 8, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 4, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-3-title", + "elementType": "text", + "bindings": { + "text": "Lookbook" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 20, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-3-subtitle", + "elementType": "text", + "bindings": { + "text": "2 min · 4K" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 12, + "textColor": "#FF6B7280", + "lineHeight": 17, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + } + ] + }, + { + "type": "container", + "id": "nav-overlay", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "children": [ + { + "type": "element", + "id": "nav-prev", + "elementType": "button", + "bindings": { + "text": "‹" + }, + "layout": { + "width": { + "value": 11, + "unit": "percent" + }, + "height": { + "value": 44, + "unit": "dp" + }, + "offset": { + "x": 2, + "y": 38, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#CC000000", + "textColor": "#FFFFFFFF", + "fontSize": 22, + "borderRadius": 22 + } + }, + { + "type": "element", + "id": "nav-next", + "elementType": "button", + "bindings": { + "text": "›" + }, + "layout": { + "width": { + "value": 11, + "unit": "percent" + }, + "height": { + "value": 44, + "unit": "dp" + }, + "offset": { + "x": 87, + "y": 38, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#CC000000", + "textColor": "#FFFFFFFF", + "fontSize": 22, + "borderRadius": 22 + } + } + ] + } + ] + }, + { + "type": "container", + "id": "footer", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 20, + "unit": "percent" + }, + "padding": { + "left": 16, + "right": 12, + "unit": "dp" + } + }, + "arrangement": { + "strategy": "space_between" + }, + "style": { + "backgroundColor": "#FFFAFAFA" + }, + "children": [ + { + "type": "element", + "id": "footer-info", + "elementType": "text", + "bindings": { + "text": "Handpicked for you" + }, + "layout": { + "width": { + "value": 55, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#FF6B7280", + "lineHeight": 22, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "footer-cta", + "elementType": "button", + "bindings": { + "text": "View All" + }, + "layout": { + "width": { + "value": 38, + "unit": "percent" + }, + "height": { + "value": 44, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF4338CA", + "textColor": "#FFFFFFFF", + "fontSize": 14, + "fontWeight": "bold", + "borderRadius": 18 + } + } + ] + } + ] + } + ] + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-163-gallery-box-freeflow-button-items.json b/flutter-sample/assets/test-configs/test-163-gallery-box-freeflow-button-items.json new file mode 100644 index 0000000..bb770a1 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-163-gallery-box-freeflow-button-items.json @@ -0,0 +1,890 @@ +{ + "theme": { + "id": "gallery-163" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "vertical", + "layout": { + "width": { + "special": "match_parent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "all": 12, + "unit": "dp" + } + }, + "children": [ + { + "type": "container", + "id": "outer-card", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 1.15 + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 20, + "shadowColor": "#40000000", + "shadowRadius": 16, + "shadowOffsetX": 0, + "shadowOffsetY": 6 + }, + "children": [ + { + "type": "container", + "id": "card-content", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "children": [ + { + "type": "container", + "id": "title-section", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 22, + "unit": "percent" + }, + "padding": { + "left": 16, + "right": 16, + "top": 14, + "bottom": 10, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FFDB2777" + }, + "children": [ + { + "type": "container", + "id": "title-content", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "arrangement": { + "spacing": 6, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "category-tag", + "elementType": "text", + "bindings": { + "text": "BEAUTY" + }, + "style": { + "fontSize": 11, + "fontWeight": "bold", + "textColor": "#CCFFFFFF", + "lineHeight": 16 + } + }, + { + "type": "element", + "id": "big-title", + "elementType": "text", + "bindings": { + "text": "Glow Essentials" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 22, + "fontWeight": "bold", + "textColor": "#FFFFFFFF", + "lineHeight": 30 + } + } + ] + }, + { + "type": "element", + "id": "see-all", + "elementType": "text", + "bindings": { + "text": "All →" + }, + "layout": { + "width": { + "special": "wrap_content" + }, + "height": { + "special": "wrap_content" + }, + "offset": { + "x": 76, + "y": 10, + "unit": "percent" + } + }, + "style": { + "fontSize": 13, + "textColor": "#DDFFFFFF", + "lineHeight": 18, + "maxLines": 1 + } + } + ] + }, + { + "type": "container", + "id": "gallery-wrapper", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 58, + "unit": "percent" + } + }, + "children": [ + { + "type": "container", + "id": "gallery", + "containerType": "gallery", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "galleryConfig": { + "mode": "free_flow", + "orientation": "horizontal", + "spacing": 10, + "showIndicators": true + }, + "children": [ + { + "type": "container", + "id": "item-1", + "containerType": "vertical", + "layout": { + "width": { + "value": 40, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 14, + "shadowColor": "#1A000000", + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 3 + }, + "children": [ + { + "type": "element", + "id": "item-1-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-31.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 65, + "unit": "percent" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-1-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 35, + "unit": "percent" + }, + "padding": { + "left": 10, + "right": 10, + "top": 8, + "bottom": 8, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 4, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-1-title", + "elementType": "text", + "bindings": { + "text": "Lip Gloss" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 20, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-1-subtitle", + "elementType": "text", + "bindings": { + "text": "Sheer finish" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 12, + "textColor": "#FF6B7280", + "lineHeight": 17, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-1-btn", + "elementType": "button", + "bindings": { + "text": "Shop Now" + }, + "layout": { + "width": { + "value": 88, + "unit": "percent" + }, + "height": { + "value": 30, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FFDB2777", + "textColor": "#FFFFFFFF", + "fontSize": 12, + "fontWeight": "bold", + "borderRadius": 8 + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-2", + "containerType": "vertical", + "layout": { + "width": { + "value": 40, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 14, + "shadowColor": "#1A000000", + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 3 + }, + "children": [ + { + "type": "element", + "id": "item-2-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-32.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 65, + "unit": "percent" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-2-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 35, + "unit": "percent" + }, + "padding": { + "left": 10, + "right": 10, + "top": 8, + "bottom": 8, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 4, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-2-title", + "elementType": "text", + "bindings": { + "text": "Eye Serum" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 20, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-2-subtitle", + "elementType": "text", + "bindings": { + "text": "Anti-aging" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 12, + "textColor": "#FF6B7280", + "lineHeight": 17, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-2-btn", + "elementType": "button", + "bindings": { + "text": "Shop Now" + }, + "layout": { + "width": { + "value": 88, + "unit": "percent" + }, + "height": { + "value": 30, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FFDB2777", + "textColor": "#FFFFFFFF", + "fontSize": 12, + "fontWeight": "bold", + "borderRadius": 8 + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-3", + "containerType": "vertical", + "layout": { + "width": { + "value": 40, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 14, + "shadowColor": "#1A000000", + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 3 + }, + "children": [ + { + "type": "element", + "id": "item-3-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-51.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 65, + "unit": "percent" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-3-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 35, + "unit": "percent" + }, + "padding": { + "left": 10, + "right": 10, + "top": 8, + "bottom": 8, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 4, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-3-title", + "elementType": "text", + "bindings": { + "text": "Face Mist" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 20, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-3-subtitle", + "elementType": "text", + "bindings": { + "text": "Hydrating" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 12, + "textColor": "#FF6B7280", + "lineHeight": 17, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-3-btn", + "elementType": "button", + "bindings": { + "text": "Shop Now" + }, + "layout": { + "width": { + "value": 88, + "unit": "percent" + }, + "height": { + "value": 30, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FFDB2777", + "textColor": "#FFFFFFFF", + "fontSize": 12, + "fontWeight": "bold", + "borderRadius": 8 + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-4", + "containerType": "vertical", + "layout": { + "width": { + "value": 40, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 14, + "shadowColor": "#1A000000", + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 3 + }, + "children": [ + { + "type": "element", + "id": "item-4-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-54.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 65, + "unit": "percent" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-4-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 35, + "unit": "percent" + }, + "padding": { + "left": 10, + "right": 10, + "top": 8, + "bottom": 8, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 4, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-4-title", + "elementType": "text", + "bindings": { + "text": "SPF 50" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 20, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-4-subtitle", + "elementType": "text", + "bindings": { + "text": "Daily shield" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 12, + "textColor": "#FF6B7280", + "lineHeight": 17, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-4-btn", + "elementType": "button", + "bindings": { + "text": "Shop Now" + }, + "layout": { + "width": { + "value": 88, + "unit": "percent" + }, + "height": { + "value": 30, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FFDB2777", + "textColor": "#FFFFFFFF", + "fontSize": 12, + "fontWeight": "bold", + "borderRadius": 8 + } + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "container", + "id": "footer", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 20, + "unit": "percent" + }, + "padding": { + "left": 16, + "right": 12, + "unit": "dp" + } + }, + "arrangement": { + "strategy": "space_between" + }, + "style": { + "backgroundColor": "#FFFAFAFA" + }, + "children": [ + { + "type": "element", + "id": "footer-info", + "elementType": "text", + "bindings": { + "text": "New arrivals daily" + }, + "layout": { + "width": { + "value": 55, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#FF6B7280", + "lineHeight": 22, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "footer-cta", + "elementType": "button", + "bindings": { + "text": "Shop Now" + }, + "layout": { + "width": { + "value": 38, + "unit": "percent" + }, + "height": { + "value": 44, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FFDB2777", + "textColor": "#FFFFFFFF", + "fontSize": 14, + "fontWeight": "bold", + "borderRadius": 18 + } + } + ] + } + ] + } + ] + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-164-gallery-box-freeflow-5items.json b/flutter-sample/assets/test-configs/test-164-gallery-box-freeflow-5items.json new file mode 100644 index 0000000..fdf88fa --- /dev/null +++ b/flutter-sample/assets/test-configs/test-164-gallery-box-freeflow-5items.json @@ -0,0 +1,795 @@ +{ + "theme": { + "id": "gallery-164" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "vertical", + "layout": { + "width": { + "special": "match_parent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "all": 12, + "unit": "dp" + } + }, + "children": [ + { + "type": "container", + "id": "outer-card", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 1.15 + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 20, + "shadowColor": "#40000000", + "shadowRadius": 16, + "shadowOffsetX": 0, + "shadowOffsetY": 6 + }, + "children": [ + { + "type": "container", + "id": "card-content", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "children": [ + { + "type": "container", + "id": "title-section", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 22, + "unit": "percent" + }, + "padding": { + "left": 16, + "right": 16, + "top": 14, + "bottom": 10, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF059669" + }, + "children": [ + { + "type": "container", + "id": "title-content", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "arrangement": { + "spacing": 6, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "category-tag", + "elementType": "text", + "bindings": { + "text": "SPORTS" + }, + "style": { + "fontSize": 11, + "fontWeight": "bold", + "textColor": "#CCFFFFFF", + "lineHeight": 16 + } + }, + { + "type": "element", + "id": "big-title", + "elementType": "text", + "bindings": { + "text": "Get Active" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 22, + "fontWeight": "bold", + "textColor": "#FFFFFFFF", + "lineHeight": 30 + } + } + ] + }, + { + "type": "element", + "id": "see-all", + "elementType": "text", + "bindings": { + "text": "All →" + }, + "layout": { + "width": { + "special": "wrap_content" + }, + "height": { + "special": "wrap_content" + }, + "offset": { + "x": 76, + "y": 10, + "unit": "percent" + } + }, + "style": { + "fontSize": 13, + "textColor": "#DDFFFFFF", + "lineHeight": 18, + "maxLines": 1 + } + } + ] + }, + { + "type": "container", + "id": "gallery-wrapper", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 58, + "unit": "percent" + } + }, + "children": [ + { + "type": "container", + "id": "gallery", + "containerType": "gallery", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "galleryConfig": { + "mode": "free_flow", + "orientation": "horizontal", + "spacing": 10, + "showIndicators": false + }, + "children": [ + { + "type": "container", + "id": "item-1", + "containerType": "vertical", + "layout": { + "width": { + "value": 50, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 14, + "shadowColor": "#1A000000", + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 3 + }, + "children": [ + { + "type": "element", + "id": "item-1-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-33.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 65, + "unit": "percent" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-1-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 35, + "unit": "percent" + }, + "padding": { + "left": 10, + "right": 10, + "top": 8, + "bottom": 8, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 4, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-1-title", + "elementType": "text", + "bindings": { + "text": "Running Shoe" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 20, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-2", + "containerType": "vertical", + "layout": { + "width": { + "value": 50, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 14, + "shadowColor": "#1A000000", + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 3 + }, + "children": [ + { + "type": "element", + "id": "item-2-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-49.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 65, + "unit": "percent" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-2-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 35, + "unit": "percent" + }, + "padding": { + "left": 10, + "right": 10, + "top": 8, + "bottom": 8, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 4, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-2-title", + "elementType": "text", + "bindings": { + "text": "Yoga Mat" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 20, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-3", + "containerType": "vertical", + "layout": { + "width": { + "value": 50, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 14, + "shadowColor": "#1A000000", + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 3 + }, + "children": [ + { + "type": "element", + "id": "item-3-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-50.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 65, + "unit": "percent" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-3-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 35, + "unit": "percent" + }, + "padding": { + "left": 10, + "right": 10, + "top": 8, + "bottom": 8, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 4, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-3-title", + "elementType": "text", + "bindings": { + "text": "Water Bottle" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 20, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-4", + "containerType": "vertical", + "layout": { + "width": { + "value": 50, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 14, + "shadowColor": "#1A000000", + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 3 + }, + "children": [ + { + "type": "element", + "id": "item-4-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-52.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 65, + "unit": "percent" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-4-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 35, + "unit": "percent" + }, + "padding": { + "left": 10, + "right": 10, + "top": 8, + "bottom": 8, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 4, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-4-title", + "elementType": "text", + "bindings": { + "text": "Gym Gloves" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 20, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-5", + "containerType": "vertical", + "layout": { + "width": { + "value": 50, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 14, + "shadowColor": "#1A000000", + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 3 + }, + "children": [ + { + "type": "element", + "id": "item-5-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-53.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 65, + "unit": "percent" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-5-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 35, + "unit": "percent" + }, + "padding": { + "left": 10, + "right": 10, + "top": 8, + "bottom": 8, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 4, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-5-title", + "elementType": "text", + "bindings": { + "text": "Track Pants" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 20, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "container", + "id": "footer", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 20, + "unit": "percent" + }, + "padding": { + "left": 16, + "right": 12, + "unit": "dp" + } + }, + "arrangement": { + "strategy": "space_between" + }, + "style": { + "backgroundColor": "#FFFAFAFA" + }, + "children": [ + { + "type": "element", + "id": "footer-info", + "elementType": "text", + "bindings": { + "text": "Members get 20% off" + }, + "layout": { + "width": { + "value": 55, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#FF6B7280", + "lineHeight": 22, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "footer-cta", + "elementType": "button", + "bindings": { + "text": "Join Now" + }, + "layout": { + "width": { + "value": 38, + "unit": "percent" + }, + "height": { + "value": 44, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF059669", + "textColor": "#FFFFFFFF", + "fontSize": 14, + "fontWeight": "bold", + "borderRadius": 18 + } + } + ] + } + ] + } + ] + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-165-gallery-box-grid2col-indicators-navbtns.json b/flutter-sample/assets/test-configs/test-165-gallery-box-grid2col-indicators-navbtns.json new file mode 100644 index 0000000..66e31ef --- /dev/null +++ b/flutter-sample/assets/test-configs/test-165-gallery-box-grid2col-indicators-navbtns.json @@ -0,0 +1,837 @@ +{ + "theme": { + "id": "gallery-165" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "vertical", + "layout": { + "width": { + "special": "match_parent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "all": 12, + "unit": "dp" + } + }, + "children": [ + { + "type": "container", + "id": "outer-card", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 0.85 + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 20, + "shadowColor": "#40000000", + "shadowRadius": 16, + "shadowOffsetX": 0, + "shadowOffsetY": 6 + }, + "children": [ + { + "type": "container", + "id": "card-content", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "children": [ + { + "type": "container", + "id": "title-section", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 22, + "unit": "percent" + }, + "padding": { + "left": 16, + "right": 16, + "top": 14, + "bottom": 10, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF1E40AF" + }, + "children": [ + { + "type": "container", + "id": "title-content", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "arrangement": { + "spacing": 6, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "category-tag", + "elementType": "text", + "bindings": { + "text": "FASHION" + }, + "style": { + "fontSize": 11, + "fontWeight": "bold", + "textColor": "#CCFFFFFF", + "lineHeight": 16 + } + }, + { + "type": "element", + "id": "big-title", + "elementType": "text", + "bindings": { + "text": "Style Grid" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 22, + "fontWeight": "bold", + "textColor": "#FFFFFFFF", + "lineHeight": 30 + } + } + ] + }, + { + "type": "element", + "id": "see-all", + "elementType": "text", + "bindings": { + "text": "All →" + }, + "layout": { + "width": { + "special": "wrap_content" + }, + "height": { + "special": "wrap_content" + }, + "offset": { + "x": 76, + "y": 10, + "unit": "percent" + } + }, + "style": { + "fontSize": 13, + "textColor": "#DDFFFFFF", + "lineHeight": 18, + "maxLines": 1 + } + } + ] + }, + { + "type": "container", + "id": "gallery-wrapper", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 58, + "unit": "percent" + } + }, + "children": [ + { + "type": "container", + "id": "gallery", + "containerType": "gallery", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "galleryConfig": { + "mode": "free_flow_grid", + "orientation": "horizontal", + "columns": 2, + "spacing": 8, + "showIndicators": true + }, + "children": [ + { + "type": "container", + "id": "item-1", + "containerType": "vertical", + "layout": { + "height": { + "special": "wrap_content" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 10, + "borderWidth": 1, + "borderColor": "#FFF0F0F0" + }, + "children": [ + { + "type": "element", + "id": "item-1-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-31.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 110, + "unit": "dp" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-1-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "left": 8, + "right": 8, + "top": 8, + "bottom": 10, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 3, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-1-title", + "elementType": "text", + "bindings": { + "text": "Maxi Dress" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 13, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 19, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-1-subtitle", + "elementType": "text", + "bindings": { + "text": "Flowy fit" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 11, + "textColor": "#FF6B7280", + "lineHeight": 16, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-2", + "containerType": "vertical", + "layout": { + "height": { + "special": "wrap_content" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 10, + "borderWidth": 1, + "borderColor": "#FFF0F0F0" + }, + "children": [ + { + "type": "element", + "id": "item-2-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-32.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 110, + "unit": "dp" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-2-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "left": 8, + "right": 8, + "top": 8, + "bottom": 10, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 3, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-2-title", + "elementType": "text", + "bindings": { + "text": "Linen Set" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 13, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 19, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-2-subtitle", + "elementType": "text", + "bindings": { + "text": "Smart casual" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 11, + "textColor": "#FF6B7280", + "lineHeight": 16, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-3", + "containerType": "vertical", + "layout": { + "height": { + "special": "wrap_content" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 10, + "borderWidth": 1, + "borderColor": "#FFF0F0F0" + }, + "children": [ + { + "type": "element", + "id": "item-3-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-51.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 110, + "unit": "dp" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-3-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "left": 8, + "right": 8, + "top": 8, + "bottom": 10, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 3, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-3-title", + "elementType": "text", + "bindings": { + "text": "Silk Blouse" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 13, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 19, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-3-subtitle", + "elementType": "text", + "bindings": { + "text": "Elegant" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 11, + "textColor": "#FF6B7280", + "lineHeight": 16, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-4", + "containerType": "vertical", + "layout": { + "height": { + "special": "wrap_content" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 10, + "borderWidth": 1, + "borderColor": "#FFF0F0F0" + }, + "children": [ + { + "type": "element", + "id": "item-4-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-52.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 110, + "unit": "dp" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-4-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "left": 8, + "right": 8, + "top": 8, + "bottom": 10, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 3, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-4-title", + "elementType": "text", + "bindings": { + "text": "Wide Pants" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 13, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 19, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-4-subtitle", + "elementType": "text", + "bindings": { + "text": "Comfy" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 11, + "textColor": "#FF6B7280", + "lineHeight": 16, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + } + ] + }, + { + "type": "container", + "id": "nav-overlay", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "children": [ + { + "type": "element", + "id": "nav-prev", + "elementType": "button", + "bindings": { + "text": "‹" + }, + "layout": { + "width": { + "value": 11, + "unit": "percent" + }, + "height": { + "value": 44, + "unit": "dp" + }, + "offset": { + "x": 2, + "y": 38, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#CC000000", + "textColor": "#FFFFFFFF", + "fontSize": 22, + "borderRadius": 22 + } + }, + { + "type": "element", + "id": "nav-next", + "elementType": "button", + "bindings": { + "text": "›" + }, + "layout": { + "width": { + "value": 11, + "unit": "percent" + }, + "height": { + "value": 44, + "unit": "dp" + }, + "offset": { + "x": 87, + "y": 38, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#CC000000", + "textColor": "#FFFFFFFF", + "fontSize": 22, + "borderRadius": 22 + } + } + ] + } + ] + }, + { + "type": "container", + "id": "footer", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 20, + "unit": "percent" + }, + "padding": { + "left": 16, + "right": 12, + "unit": "dp" + } + }, + "arrangement": { + "strategy": "space_between" + }, + "style": { + "backgroundColor": "#FFFAFAFA" + }, + "children": [ + { + "type": "element", + "id": "footer-info", + "elementType": "text", + "bindings": { + "text": "120 items available" + }, + "layout": { + "width": { + "value": 55, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#FF6B7280", + "lineHeight": 22, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "footer-cta", + "elementType": "button", + "bindings": { + "text": "Browse" + }, + "layout": { + "width": { + "value": 38, + "unit": "percent" + }, + "height": { + "value": 44, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF1E40AF", + "textColor": "#FFFFFFFF", + "fontSize": 14, + "fontWeight": "bold", + "borderRadius": 18 + } + } + ] + } + ] + } + ] + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-166-gallery-box-grid2col-indicators-only.json b/flutter-sample/assets/test-configs/test-166-gallery-box-grid2col-indicators-only.json new file mode 100644 index 0000000..e674ed2 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-166-gallery-box-grid2col-indicators-only.json @@ -0,0 +1,763 @@ +{ + "theme": { + "id": "gallery-166" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "vertical", + "layout": { + "width": { + "special": "match_parent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "all": 12, + "unit": "dp" + } + }, + "children": [ + { + "type": "container", + "id": "outer-card", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 0.85 + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 20, + "shadowColor": "#40000000", + "shadowRadius": 16, + "shadowOffsetX": 0, + "shadowOffsetY": 6 + }, + "children": [ + { + "type": "container", + "id": "card-content", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "children": [ + { + "type": "container", + "id": "title-section", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 22, + "unit": "percent" + }, + "padding": { + "left": 16, + "right": 16, + "top": 14, + "bottom": 10, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF334155" + }, + "children": [ + { + "type": "container", + "id": "title-content", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "arrangement": { + "spacing": 6, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "category-tag", + "elementType": "text", + "bindings": { + "text": "FOOTWEAR" + }, + "style": { + "fontSize": 11, + "fontWeight": "bold", + "textColor": "#CCFFFFFF", + "lineHeight": 16 + } + }, + { + "type": "element", + "id": "big-title", + "elementType": "text", + "bindings": { + "text": "Step Up" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 22, + "fontWeight": "bold", + "textColor": "#FFFFFFFF", + "lineHeight": 30 + } + } + ] + }, + { + "type": "element", + "id": "see-all", + "elementType": "text", + "bindings": { + "text": "All →" + }, + "layout": { + "width": { + "special": "wrap_content" + }, + "height": { + "special": "wrap_content" + }, + "offset": { + "x": 76, + "y": 10, + "unit": "percent" + } + }, + "style": { + "fontSize": 13, + "textColor": "#DDFFFFFF", + "lineHeight": 18, + "maxLines": 1 + } + } + ] + }, + { + "type": "container", + "id": "gallery-wrapper", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 58, + "unit": "percent" + } + }, + "children": [ + { + "type": "container", + "id": "gallery", + "containerType": "gallery", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "galleryConfig": { + "mode": "free_flow_grid", + "orientation": "horizontal", + "columns": 2, + "spacing": 8, + "showIndicators": true + }, + "children": [ + { + "type": "container", + "id": "item-1", + "containerType": "vertical", + "layout": { + "height": { + "special": "wrap_content" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 10, + "borderWidth": 1, + "borderColor": "#FFF0F0F0" + }, + "children": [ + { + "type": "element", + "id": "item-1-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-33.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 110, + "unit": "dp" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-1-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "left": 8, + "right": 8, + "top": 8, + "bottom": 10, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 3, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-1-title", + "elementType": "text", + "bindings": { + "text": "Sneakers" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 13, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 19, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-1-subtitle", + "elementType": "text", + "bindings": { + "text": "Street style" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 11, + "textColor": "#FF6B7280", + "lineHeight": 16, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-2", + "containerType": "vertical", + "layout": { + "height": { + "special": "wrap_content" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 10, + "borderWidth": 1, + "borderColor": "#FFF0F0F0" + }, + "children": [ + { + "type": "element", + "id": "item-2-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-49.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 110, + "unit": "dp" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-2-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "left": 8, + "right": 8, + "top": 8, + "bottom": 10, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 3, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-2-title", + "elementType": "text", + "bindings": { + "text": "Loafers" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 13, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 19, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-2-subtitle", + "elementType": "text", + "bindings": { + "text": "Office look" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 11, + "textColor": "#FF6B7280", + "lineHeight": 16, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-3", + "containerType": "vertical", + "layout": { + "height": { + "special": "wrap_content" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 10, + "borderWidth": 1, + "borderColor": "#FFF0F0F0" + }, + "children": [ + { + "type": "element", + "id": "item-3-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-50.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 110, + "unit": "dp" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-3-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "left": 8, + "right": 8, + "top": 8, + "bottom": 10, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 3, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-3-title", + "elementType": "text", + "bindings": { + "text": "Ankle Boot" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 13, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 19, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-3-subtitle", + "elementType": "text", + "bindings": { + "text": "Fall ready" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 11, + "textColor": "#FF6B7280", + "lineHeight": 16, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-4", + "containerType": "vertical", + "layout": { + "height": { + "special": "wrap_content" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 10, + "borderWidth": 1, + "borderColor": "#FFF0F0F0" + }, + "children": [ + { + "type": "element", + "id": "item-4-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-53.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 110, + "unit": "dp" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-4-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "left": 8, + "right": 8, + "top": 8, + "bottom": 10, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 3, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-4-title", + "elementType": "text", + "bindings": { + "text": "Slide" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 13, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 19, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-4-subtitle", + "elementType": "text", + "bindings": { + "text": "Pool side" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 11, + "textColor": "#FF6B7280", + "lineHeight": 16, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "container", + "id": "footer", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 20, + "unit": "percent" + }, + "padding": { + "left": 16, + "right": 12, + "unit": "dp" + } + }, + "arrangement": { + "strategy": "space_between" + }, + "style": { + "backgroundColor": "#FFFAFAFA" + }, + "children": [ + { + "type": "element", + "id": "footer-info", + "elementType": "text", + "bindings": { + "text": "Sizes 36–48 in stock" + }, + "layout": { + "width": { + "value": 55, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#FF6B7280", + "lineHeight": 22, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "footer-cta", + "elementType": "button", + "bindings": { + "text": "Find Size" + }, + "layout": { + "width": { + "value": 38, + "unit": "percent" + }, + "height": { + "value": 44, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF334155", + "textColor": "#FFFFFFFF", + "fontSize": 14, + "fontWeight": "bold", + "borderRadius": 18 + } + } + ] + } + ] + } + ] + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-167-gallery-box-grid2col-navbtns-only.json b/flutter-sample/assets/test-configs/test-167-gallery-box-grid2col-navbtns-only.json new file mode 100644 index 0000000..322647a --- /dev/null +++ b/flutter-sample/assets/test-configs/test-167-gallery-box-grid2col-navbtns-only.json @@ -0,0 +1,741 @@ +{ + "theme": { + "id": "gallery-167" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "vertical", + "layout": { + "width": { + "special": "match_parent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "all": 12, + "unit": "dp" + } + }, + "children": [ + { + "type": "container", + "id": "outer-card", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 0.85 + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 20, + "shadowColor": "#40000000", + "shadowRadius": 16, + "shadowOffsetX": 0, + "shadowOffsetY": 6 + }, + "children": [ + { + "type": "container", + "id": "card-content", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "children": [ + { + "type": "container", + "id": "title-section", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 22, + "unit": "percent" + }, + "padding": { + "left": 16, + "right": 16, + "top": 14, + "bottom": 10, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF6D28D9" + }, + "children": [ + { + "type": "container", + "id": "title-content", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "arrangement": { + "spacing": 6, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "category-tag", + "elementType": "text", + "bindings": { + "text": "GADGETS" + }, + "style": { + "fontSize": 11, + "fontWeight": "bold", + "textColor": "#CCFFFFFF", + "lineHeight": 16 + } + }, + { + "type": "element", + "id": "big-title", + "elementType": "text", + "bindings": { + "text": "Tech Finds" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 22, + "fontWeight": "bold", + "textColor": "#FFFFFFFF", + "lineHeight": 30 + } + } + ] + }, + { + "type": "element", + "id": "see-all", + "elementType": "text", + "bindings": { + "text": "All →" + }, + "layout": { + "width": { + "special": "wrap_content" + }, + "height": { + "special": "wrap_content" + }, + "offset": { + "x": 76, + "y": 10, + "unit": "percent" + } + }, + "style": { + "fontSize": 13, + "textColor": "#DDFFFFFF", + "lineHeight": 18, + "maxLines": 1 + } + } + ] + }, + { + "type": "container", + "id": "gallery-wrapper", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 58, + "unit": "percent" + } + }, + "children": [ + { + "type": "container", + "id": "gallery", + "containerType": "gallery", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "galleryConfig": { + "mode": "free_flow_grid", + "orientation": "horizontal", + "columns": 2, + "spacing": 8, + "showIndicators": false + }, + "children": [ + { + "type": "container", + "id": "item-1", + "containerType": "vertical", + "layout": { + "height": { + "special": "wrap_content" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 10, + "borderWidth": 1, + "borderColor": "#FFF0F0F0" + }, + "children": [ + { + "type": "element", + "id": "item-1-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-54.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 110, + "unit": "dp" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-1-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "left": 8, + "right": 8, + "top": 8, + "bottom": 10, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 3, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-1-title", + "elementType": "text", + "bindings": { + "text": "Smart Watch" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 13, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 19, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-2", + "containerType": "vertical", + "layout": { + "height": { + "special": "wrap_content" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 10, + "borderWidth": 1, + "borderColor": "#FFF0F0F0" + }, + "children": [ + { + "type": "element", + "id": "item-2-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-31.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 110, + "unit": "dp" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-2-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "left": 8, + "right": 8, + "top": 8, + "bottom": 10, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 3, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-2-title", + "elementType": "text", + "bindings": { + "text": "Earbuds Pro" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 13, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 19, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-3", + "containerType": "vertical", + "layout": { + "height": { + "special": "wrap_content" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 10, + "borderWidth": 1, + "borderColor": "#FFF0F0F0" + }, + "children": [ + { + "type": "element", + "id": "item-3-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-32.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 110, + "unit": "dp" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-3-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "left": 8, + "right": 8, + "top": 8, + "bottom": 10, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 3, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-3-title", + "elementType": "text", + "bindings": { + "text": "USB Hub" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 13, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 19, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-4", + "containerType": "vertical", + "layout": { + "height": { + "special": "wrap_content" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 10, + "borderWidth": 1, + "borderColor": "#FFF0F0F0" + }, + "children": [ + { + "type": "element", + "id": "item-4-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-33.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 110, + "unit": "dp" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-4-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "left": 8, + "right": 8, + "top": 8, + "bottom": 10, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 3, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-4-title", + "elementType": "text", + "bindings": { + "text": "LED Strip" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 13, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 19, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + } + ] + }, + { + "type": "container", + "id": "nav-overlay", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "children": [ + { + "type": "element", + "id": "nav-prev", + "elementType": "button", + "bindings": { + "text": "‹" + }, + "layout": { + "width": { + "value": 11, + "unit": "percent" + }, + "height": { + "value": 44, + "unit": "dp" + }, + "offset": { + "x": 2, + "y": 38, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#CC000000", + "textColor": "#FFFFFFFF", + "fontSize": 22, + "borderRadius": 22 + } + }, + { + "type": "element", + "id": "nav-next", + "elementType": "button", + "bindings": { + "text": "›" + }, + "layout": { + "width": { + "value": 11, + "unit": "percent" + }, + "height": { + "value": 44, + "unit": "dp" + }, + "offset": { + "x": 87, + "y": 38, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#CC000000", + "textColor": "#FFFFFFFF", + "fontSize": 22, + "borderRadius": 22 + } + } + ] + } + ] + }, + { + "type": "container", + "id": "footer", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 20, + "unit": "percent" + }, + "padding": { + "left": 16, + "right": 12, + "unit": "dp" + } + }, + "arrangement": { + "strategy": "space_between" + }, + "style": { + "backgroundColor": "#FFFAFAFA" + }, + "children": [ + { + "type": "element", + "id": "footer-info", + "elementType": "text", + "bindings": { + "text": "Free 2-day delivery" + }, + "layout": { + "width": { + "value": 55, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#FF6B7280", + "lineHeight": 22, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "footer-cta", + "elementType": "button", + "bindings": { + "text": "Shop Now" + }, + "layout": { + "width": { + "value": 38, + "unit": "percent" + }, + "height": { + "value": 44, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF6D28D9", + "textColor": "#FFFFFFFF", + "fontSize": 14, + "fontWeight": "bold", + "borderRadius": 18 + } + } + ] + } + ] + } + ] + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-168-gallery-box-grid2col-minimal.json b/flutter-sample/assets/test-configs/test-168-gallery-box-grid2col-minimal.json new file mode 100644 index 0000000..97ae7c0 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-168-gallery-box-grid2col-minimal.json @@ -0,0 +1,763 @@ +{ + "theme": { + "id": "gallery-168" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "vertical", + "layout": { + "width": { + "special": "match_parent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "all": 12, + "unit": "dp" + } + }, + "children": [ + { + "type": "container", + "id": "outer-card", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 0.85 + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 20, + "shadowColor": "#40000000", + "shadowRadius": 16, + "shadowOffsetX": 0, + "shadowOffsetY": 6 + }, + "children": [ + { + "type": "container", + "id": "card-content", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "children": [ + { + "type": "container", + "id": "title-section", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 22, + "unit": "percent" + }, + "padding": { + "left": 16, + "right": 16, + "top": 14, + "bottom": 10, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF047857" + }, + "children": [ + { + "type": "container", + "id": "title-content", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "arrangement": { + "spacing": 6, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "category-tag", + "elementType": "text", + "bindings": { + "text": "DAILY USE" + }, + "style": { + "fontSize": 11, + "fontWeight": "bold", + "textColor": "#CCFFFFFF", + "lineHeight": 16 + } + }, + { + "type": "element", + "id": "big-title", + "elementType": "text", + "bindings": { + "text": "Essentials" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 22, + "fontWeight": "bold", + "textColor": "#FFFFFFFF", + "lineHeight": 30 + } + } + ] + }, + { + "type": "element", + "id": "see-all", + "elementType": "text", + "bindings": { + "text": "All →" + }, + "layout": { + "width": { + "special": "wrap_content" + }, + "height": { + "special": "wrap_content" + }, + "offset": { + "x": 76, + "y": 10, + "unit": "percent" + } + }, + "style": { + "fontSize": 13, + "textColor": "#DDFFFFFF", + "lineHeight": 18, + "maxLines": 1 + } + } + ] + }, + { + "type": "container", + "id": "gallery-wrapper", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 58, + "unit": "percent" + } + }, + "children": [ + { + "type": "container", + "id": "gallery", + "containerType": "gallery", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "galleryConfig": { + "mode": "free_flow_grid", + "orientation": "horizontal", + "columns": 2, + "spacing": 8, + "showIndicators": false + }, + "children": [ + { + "type": "container", + "id": "item-1", + "containerType": "vertical", + "layout": { + "height": { + "special": "wrap_content" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 10, + "borderWidth": 1, + "borderColor": "#FFF0F0F0" + }, + "children": [ + { + "type": "element", + "id": "item-1-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-49.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 110, + "unit": "dp" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-1-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "left": 8, + "right": 8, + "top": 8, + "bottom": 10, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 3, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-1-title", + "elementType": "text", + "bindings": { + "text": "Basic Tee" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 13, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 19, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-1-subtitle", + "elementType": "text", + "bindings": { + "text": "Pack of 3" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 11, + "textColor": "#FF6B7280", + "lineHeight": 16, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-2", + "containerType": "vertical", + "layout": { + "height": { + "special": "wrap_content" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 10, + "borderWidth": 1, + "borderColor": "#FFF0F0F0" + }, + "children": [ + { + "type": "element", + "id": "item-2-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-50.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 110, + "unit": "dp" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-2-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "left": 8, + "right": 8, + "top": 8, + "bottom": 10, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 3, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-2-title", + "elementType": "text", + "bindings": { + "text": "Slim Chinos" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 13, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 19, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-2-subtitle", + "elementType": "text", + "bindings": { + "text": "Stretch fit" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 11, + "textColor": "#FF6B7280", + "lineHeight": 16, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-3", + "containerType": "vertical", + "layout": { + "height": { + "special": "wrap_content" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 10, + "borderWidth": 1, + "borderColor": "#FFF0F0F0" + }, + "children": [ + { + "type": "element", + "id": "item-3-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-51.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 110, + "unit": "dp" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-3-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "left": 8, + "right": 8, + "top": 8, + "bottom": 10, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 3, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-3-title", + "elementType": "text", + "bindings": { + "text": "Canvas Bag" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 13, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 19, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-3-subtitle", + "elementType": "text", + "bindings": { + "text": "Everyday carry" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 11, + "textColor": "#FF6B7280", + "lineHeight": 16, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-4", + "containerType": "vertical", + "layout": { + "height": { + "special": "wrap_content" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 10, + "borderWidth": 1, + "borderColor": "#FFF0F0F0" + }, + "children": [ + { + "type": "element", + "id": "item-4-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-52.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 110, + "unit": "dp" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-4-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "left": 8, + "right": 8, + "top": 8, + "bottom": 10, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 3, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-4-title", + "elementType": "text", + "bindings": { + "text": "White Socks" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 13, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 19, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-4-subtitle", + "elementType": "text", + "bindings": { + "text": "12-pair set" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 11, + "textColor": "#FF6B7280", + "lineHeight": 16, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "container", + "id": "footer", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 20, + "unit": "percent" + }, + "padding": { + "left": 16, + "right": 12, + "unit": "dp" + } + }, + "arrangement": { + "strategy": "space_between" + }, + "style": { + "backgroundColor": "#FFFAFAFA" + }, + "children": [ + { + "type": "element", + "id": "footer-info", + "elementType": "text", + "bindings": { + "text": "Save up to 30%" + }, + "layout": { + "width": { + "value": 55, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#FF6B7280", + "lineHeight": 22, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "footer-cta", + "elementType": "button", + "bindings": { + "text": "Save Now" + }, + "layout": { + "width": { + "value": 38, + "unit": "percent" + }, + "height": { + "value": 44, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF047857", + "textColor": "#FFFFFFFF", + "fontSize": 14, + "fontWeight": "bold", + "borderRadius": 18 + } + } + ] + } + ] + } + ] + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-169-gallery-box-grid3col-indicators.json b/flutter-sample/assets/test-configs/test-169-gallery-box-grid3col-indicators.json new file mode 100644 index 0000000..89ce374 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-169-gallery-box-grid3col-indicators.json @@ -0,0 +1,855 @@ +{ + "theme": { + "id": "gallery-169" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "vertical", + "layout": { + "width": { + "special": "match_parent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "all": 12, + "unit": "dp" + } + }, + "children": [ + { + "type": "container", + "id": "outer-card", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 0.85 + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 20, + "shadowColor": "#40000000", + "shadowRadius": 16, + "shadowOffsetX": 0, + "shadowOffsetY": 6 + }, + "children": [ + { + "type": "container", + "id": "card-content", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "children": [ + { + "type": "container", + "id": "title-section", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 22, + "unit": "percent" + }, + "padding": { + "left": 16, + "right": 16, + "top": 14, + "bottom": 10, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FFB45309" + }, + "children": [ + { + "type": "container", + "id": "title-content", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "arrangement": { + "spacing": 6, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "category-tag", + "elementType": "text", + "bindings": { + "text": "CURATED" + }, + "style": { + "fontSize": 11, + "fontWeight": "bold", + "textColor": "#CCFFFFFF", + "lineHeight": 16 + } + }, + { + "type": "element", + "id": "big-title", + "elementType": "text", + "bindings": { + "text": "Top Picks" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 22, + "fontWeight": "bold", + "textColor": "#FFFFFFFF", + "lineHeight": 30 + } + } + ] + }, + { + "type": "element", + "id": "see-all", + "elementType": "text", + "bindings": { + "text": "All →" + }, + "layout": { + "width": { + "special": "wrap_content" + }, + "height": { + "special": "wrap_content" + }, + "offset": { + "x": 76, + "y": 10, + "unit": "percent" + } + }, + "style": { + "fontSize": 13, + "textColor": "#DDFFFFFF", + "lineHeight": 18, + "maxLines": 1 + } + } + ] + }, + { + "type": "container", + "id": "gallery-wrapper", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 58, + "unit": "percent" + } + }, + "children": [ + { + "type": "container", + "id": "gallery", + "containerType": "gallery", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "galleryConfig": { + "mode": "free_flow_grid", + "orientation": "horizontal", + "columns": 3, + "spacing": 6, + "showIndicators": true + }, + "children": [ + { + "type": "container", + "id": "item-1", + "containerType": "vertical", + "layout": { + "height": { + "special": "wrap_content" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 10, + "borderWidth": 1, + "borderColor": "#FFF0F0F0" + }, + "children": [ + { + "type": "element", + "id": "item-1-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-53.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 80, + "unit": "dp" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-1-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "left": 8, + "right": 8, + "top": 8, + "bottom": 10, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 2, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-1-title", + "elementType": "text", + "bindings": { + "text": "Chair" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 13, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 19, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-2", + "containerType": "vertical", + "layout": { + "height": { + "special": "wrap_content" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 10, + "borderWidth": 1, + "borderColor": "#FFF0F0F0" + }, + "children": [ + { + "type": "element", + "id": "item-2-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-54.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 80, + "unit": "dp" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-2-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "left": 8, + "right": 8, + "top": 8, + "bottom": 10, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 2, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-2-title", + "elementType": "text", + "bindings": { + "text": "Table" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 13, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 19, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-3", + "containerType": "vertical", + "layout": { + "height": { + "special": "wrap_content" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 10, + "borderWidth": 1, + "borderColor": "#FFF0F0F0" + }, + "children": [ + { + "type": "element", + "id": "item-3-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-31.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 80, + "unit": "dp" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-3-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "left": 8, + "right": 8, + "top": 8, + "bottom": 10, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 2, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-3-title", + "elementType": "text", + "bindings": { + "text": "Lamp" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 13, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 19, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-4", + "containerType": "vertical", + "layout": { + "height": { + "special": "wrap_content" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 10, + "borderWidth": 1, + "borderColor": "#FFF0F0F0" + }, + "children": [ + { + "type": "element", + "id": "item-4-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-32.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 80, + "unit": "dp" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-4-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "left": 8, + "right": 8, + "top": 8, + "bottom": 10, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 2, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-4-title", + "elementType": "text", + "bindings": { + "text": "Rug" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 13, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 19, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-5", + "containerType": "vertical", + "layout": { + "height": { + "special": "wrap_content" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 10, + "borderWidth": 1, + "borderColor": "#FFF0F0F0" + }, + "children": [ + { + "type": "element", + "id": "item-5-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-33.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 80, + "unit": "dp" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-5-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "left": 8, + "right": 8, + "top": 8, + "bottom": 10, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 2, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-5-title", + "elementType": "text", + "bindings": { + "text": "Vase" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 13, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 19, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-6", + "containerType": "vertical", + "layout": { + "height": { + "special": "wrap_content" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 10, + "borderWidth": 1, + "borderColor": "#FFF0F0F0" + }, + "children": [ + { + "type": "element", + "id": "item-6-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-49.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 80, + "unit": "dp" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-6-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "left": 8, + "right": 8, + "top": 8, + "bottom": 10, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 2, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-6-title", + "elementType": "text", + "bindings": { + "text": "Mirror" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 13, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 19, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "container", + "id": "footer", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 20, + "unit": "percent" + }, + "padding": { + "left": 16, + "right": 12, + "unit": "dp" + } + }, + "arrangement": { + "strategy": "space_between" + }, + "style": { + "backgroundColor": "#FFFAFAFA" + }, + "children": [ + { + "type": "element", + "id": "footer-info", + "elementType": "text", + "bindings": { + "text": "★ 4.8 top rated" + }, + "layout": { + "width": { + "value": 55, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#FF6B7280", + "lineHeight": 22, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "footer-cta", + "elementType": "button", + "bindings": { + "text": "View All" + }, + "layout": { + "width": { + "value": 38, + "unit": "percent" + }, + "height": { + "value": 44, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FFB45309", + "textColor": "#FFFFFFFF", + "fontSize": 14, + "fontWeight": "bold", + "borderRadius": 18 + } + } + ] + } + ] + } + ] + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-170-gallery-box-grid3col-navbtns.json b/flutter-sample/assets/test-configs/test-170-gallery-box-grid3col-navbtns.json new file mode 100644 index 0000000..d7d5f78 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-170-gallery-box-grid3col-navbtns.json @@ -0,0 +1,929 @@ +{ + "theme": { + "id": "gallery-170" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "vertical", + "layout": { + "width": { + "special": "match_parent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "all": 12, + "unit": "dp" + } + }, + "children": [ + { + "type": "container", + "id": "outer-card", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 0.85 + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 20, + "shadowColor": "#40000000", + "shadowRadius": 16, + "shadowOffsetX": 0, + "shadowOffsetY": 6 + }, + "children": [ + { + "type": "container", + "id": "card-content", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "children": [ + { + "type": "container", + "id": "title-section", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 22, + "unit": "percent" + }, + "padding": { + "left": 16, + "right": 16, + "top": 14, + "bottom": 10, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF0E7490" + }, + "children": [ + { + "type": "container", + "id": "title-content", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "arrangement": { + "spacing": 6, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "category-tag", + "elementType": "text", + "bindings": { + "text": "NEW LAUNCH" + }, + "style": { + "fontSize": 11, + "fontWeight": "bold", + "textColor": "#CCFFFFFF", + "lineHeight": 16 + } + }, + { + "type": "element", + "id": "big-title", + "elementType": "text", + "bindings": { + "text": "Just Dropped" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 22, + "fontWeight": "bold", + "textColor": "#FFFFFFFF", + "lineHeight": 30 + } + } + ] + }, + { + "type": "element", + "id": "see-all", + "elementType": "text", + "bindings": { + "text": "All →" + }, + "layout": { + "width": { + "special": "wrap_content" + }, + "height": { + "special": "wrap_content" + }, + "offset": { + "x": 76, + "y": 10, + "unit": "percent" + } + }, + "style": { + "fontSize": 13, + "textColor": "#DDFFFFFF", + "lineHeight": 18, + "maxLines": 1 + } + } + ] + }, + { + "type": "container", + "id": "gallery-wrapper", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 58, + "unit": "percent" + } + }, + "children": [ + { + "type": "container", + "id": "gallery", + "containerType": "gallery", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "galleryConfig": { + "mode": "free_flow_grid", + "orientation": "horizontal", + "columns": 3, + "spacing": 6, + "showIndicators": false + }, + "children": [ + { + "type": "container", + "id": "item-1", + "containerType": "vertical", + "layout": { + "height": { + "special": "wrap_content" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 10, + "borderWidth": 1, + "borderColor": "#FFF0F0F0" + }, + "children": [ + { + "type": "element", + "id": "item-1-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-50.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 80, + "unit": "dp" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-1-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "left": 8, + "right": 8, + "top": 8, + "bottom": 10, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 2, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-1-title", + "elementType": "text", + "bindings": { + "text": "AirPods" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 13, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 19, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-2", + "containerType": "vertical", + "layout": { + "height": { + "special": "wrap_content" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 10, + "borderWidth": 1, + "borderColor": "#FFF0F0F0" + }, + "children": [ + { + "type": "element", + "id": "item-2-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-51.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 80, + "unit": "dp" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-2-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "left": 8, + "right": 8, + "top": 8, + "bottom": 10, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 2, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-2-title", + "elementType": "text", + "bindings": { + "text": "iPad" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 13, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 19, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-3", + "containerType": "vertical", + "layout": { + "height": { + "special": "wrap_content" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 10, + "borderWidth": 1, + "borderColor": "#FFF0F0F0" + }, + "children": [ + { + "type": "element", + "id": "item-3-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-52.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 80, + "unit": "dp" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-3-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "left": 8, + "right": 8, + "top": 8, + "bottom": 10, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 2, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-3-title", + "elementType": "text", + "bindings": { + "text": "Watch" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 13, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 19, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-4", + "containerType": "vertical", + "layout": { + "height": { + "special": "wrap_content" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 10, + "borderWidth": 1, + "borderColor": "#FFF0F0F0" + }, + "children": [ + { + "type": "element", + "id": "item-4-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-53.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 80, + "unit": "dp" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-4-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "left": 8, + "right": 8, + "top": 8, + "bottom": 10, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 2, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-4-title", + "elementType": "text", + "bindings": { + "text": "MacBook" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 13, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 19, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-5", + "containerType": "vertical", + "layout": { + "height": { + "special": "wrap_content" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 10, + "borderWidth": 1, + "borderColor": "#FFF0F0F0" + }, + "children": [ + { + "type": "element", + "id": "item-5-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-54.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 80, + "unit": "dp" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-5-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "left": 8, + "right": 8, + "top": 8, + "bottom": 10, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 2, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-5-title", + "elementType": "text", + "bindings": { + "text": "iPhone" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 13, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 19, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-6", + "containerType": "vertical", + "layout": { + "height": { + "special": "wrap_content" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 10, + "borderWidth": 1, + "borderColor": "#FFF0F0F0" + }, + "children": [ + { + "type": "element", + "id": "item-6-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-31.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 80, + "unit": "dp" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-6-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "left": 8, + "right": 8, + "top": 8, + "bottom": 10, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 2, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-6-title", + "elementType": "text", + "bindings": { + "text": "HomePod" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 13, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 19, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + } + ] + }, + { + "type": "container", + "id": "nav-overlay", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "children": [ + { + "type": "element", + "id": "nav-prev", + "elementType": "button", + "bindings": { + "text": "‹" + }, + "layout": { + "width": { + "value": 11, + "unit": "percent" + }, + "height": { + "value": 44, + "unit": "dp" + }, + "offset": { + "x": 2, + "y": 38, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#CC000000", + "textColor": "#FFFFFFFF", + "fontSize": 22, + "borderRadius": 22 + } + }, + { + "type": "element", + "id": "nav-next", + "elementType": "button", + "bindings": { + "text": "›" + }, + "layout": { + "width": { + "value": 11, + "unit": "percent" + }, + "height": { + "value": 44, + "unit": "dp" + }, + "offset": { + "x": 87, + "y": 38, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#CC000000", + "textColor": "#FFFFFFFF", + "fontSize": 22, + "borderRadius": 22 + } + } + ] + } + ] + }, + { + "type": "container", + "id": "footer", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 20, + "unit": "percent" + }, + "padding": { + "left": 16, + "right": 12, + "unit": "dp" + } + }, + "arrangement": { + "strategy": "space_between" + }, + "style": { + "backgroundColor": "#FFFAFAFA" + }, + "children": [ + { + "type": "element", + "id": "footer-info", + "elementType": "text", + "bindings": { + "text": "Ships today · 48h" + }, + "layout": { + "width": { + "value": 55, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#FF6B7280", + "lineHeight": 22, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "footer-cta", + "elementType": "button", + "bindings": { + "text": "Shop New" + }, + "layout": { + "width": { + "value": 38, + "unit": "percent" + }, + "height": { + "value": 44, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF0E7490", + "textColor": "#FFFFFFFF", + "fontSize": 14, + "fontWeight": "bold", + "borderRadius": 18 + } + } + ] + } + ] + } + ] + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-171-gallery-box-grid2col-video.json b/flutter-sample/assets/test-configs/test-171-gallery-box-grid2col-video.json new file mode 100644 index 0000000..11659e6 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-171-gallery-box-grid2col-video.json @@ -0,0 +1,833 @@ +{ + "theme": { + "id": "gallery-171" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "vertical", + "layout": { + "width": { + "special": "match_parent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "all": 12, + "unit": "dp" + } + }, + "children": [ + { + "type": "container", + "id": "outer-card", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 0.85 + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 20, + "shadowColor": "#40000000", + "shadowRadius": 16, + "shadowOffsetX": 0, + "shadowOffsetY": 6 + }, + "children": [ + { + "type": "container", + "id": "card-content", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "children": [ + { + "type": "container", + "id": "title-section", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 22, + "unit": "percent" + }, + "padding": { + "left": 16, + "right": 16, + "top": 14, + "bottom": 10, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FFB91C1C" + }, + "children": [ + { + "type": "container", + "id": "title-content", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "arrangement": { + "spacing": 6, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "category-tag", + "elementType": "text", + "bindings": { + "text": "FEATURED" + }, + "style": { + "fontSize": 11, + "fontWeight": "bold", + "textColor": "#CCFFFFFF", + "lineHeight": 16 + } + }, + { + "type": "element", + "id": "big-title", + "elementType": "text", + "bindings": { + "text": "Watch & Shop" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 22, + "fontWeight": "bold", + "textColor": "#FFFFFFFF", + "lineHeight": 30 + } + } + ] + }, + { + "type": "element", + "id": "see-all", + "elementType": "text", + "bindings": { + "text": "All →" + }, + "layout": { + "width": { + "special": "wrap_content" + }, + "height": { + "special": "wrap_content" + }, + "offset": { + "x": 76, + "y": 10, + "unit": "percent" + } + }, + "style": { + "fontSize": 13, + "textColor": "#DDFFFFFF", + "lineHeight": 18, + "maxLines": 1 + } + } + ] + }, + { + "type": "container", + "id": "gallery-wrapper", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 58, + "unit": "percent" + } + }, + "children": [ + { + "type": "container", + "id": "gallery", + "containerType": "gallery", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "galleryConfig": { + "mode": "free_flow_grid", + "orientation": "horizontal", + "columns": 2, + "spacing": 8, + "showIndicators": true + }, + "children": [ + { + "type": "container", + "id": "item-1", + "containerType": "vertical", + "layout": { + "height": { + "special": "wrap_content" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 10, + "borderWidth": 1, + "borderColor": "#FFF0F0F0" + }, + "children": [ + { + "type": "element", + "id": "item-1-video", + "elementType": "video", + "bindings": { + "url": "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4", + "autoPlay": "false", + "loop": "false", + "muted": "true", + "showControls": "true" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "dp" + } + } + }, + { + "type": "container", + "id": "item-1-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "left": 8, + "right": 8, + "top": 8, + "bottom": 10, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 3, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-1-title", + "elementType": "text", + "bindings": { + "text": "Summer Film" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 13, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 19, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-1-subtitle", + "elementType": "text", + "bindings": { + "text": "2 min · HD" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 11, + "textColor": "#FF6B7280", + "lineHeight": 16, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-2", + "containerType": "vertical", + "layout": { + "height": { + "special": "wrap_content" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 10, + "borderWidth": 1, + "borderColor": "#FFF0F0F0" + }, + "children": [ + { + "type": "element", + "id": "item-2-video", + "elementType": "video", + "bindings": { + "url": "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4", + "autoPlay": "false", + "loop": "false", + "muted": "true", + "showControls": "true" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "dp" + } + } + }, + { + "type": "container", + "id": "item-2-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "left": 8, + "right": 8, + "top": 8, + "bottom": 10, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 3, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-2-title", + "elementType": "text", + "bindings": { + "text": "Style Haul" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 13, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 19, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-2-subtitle", + "elementType": "text", + "bindings": { + "text": "5 min · HD" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 11, + "textColor": "#FF6B7280", + "lineHeight": 16, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-3", + "containerType": "vertical", + "layout": { + "height": { + "special": "wrap_content" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 10, + "borderWidth": 1, + "borderColor": "#FFF0F0F0" + }, + "children": [ + { + "type": "element", + "id": "item-3-video", + "elementType": "video", + "bindings": { + "url": "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4", + "autoPlay": "false", + "loop": "false", + "muted": "true", + "showControls": "true" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "dp" + } + } + }, + { + "type": "container", + "id": "item-3-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "left": 8, + "right": 8, + "top": 8, + "bottom": 10, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 3, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-3-title", + "elementType": "text", + "bindings": { + "text": "Room Tour" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 13, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 19, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-3-subtitle", + "elementType": "text", + "bindings": { + "text": "3 min · 4K" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 11, + "textColor": "#FF6B7280", + "lineHeight": 16, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-4", + "containerType": "vertical", + "layout": { + "height": { + "special": "wrap_content" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 10, + "borderWidth": 1, + "borderColor": "#FFF0F0F0" + }, + "children": [ + { + "type": "element", + "id": "item-4-video", + "elementType": "video", + "bindings": { + "url": "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4", + "autoPlay": "false", + "loop": "false", + "muted": "true", + "showControls": "true" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "dp" + } + } + }, + { + "type": "container", + "id": "item-4-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "left": 8, + "right": 8, + "top": 8, + "bottom": 10, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 3, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-4-title", + "elementType": "text", + "bindings": { + "text": "Brand Story" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 13, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 19, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-4-subtitle", + "elementType": "text", + "bindings": { + "text": "1 min · HD" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 11, + "textColor": "#FF6B7280", + "lineHeight": 16, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + } + ] + }, + { + "type": "container", + "id": "nav-overlay", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "children": [ + { + "type": "element", + "id": "nav-prev", + "elementType": "button", + "bindings": { + "text": "‹" + }, + "layout": { + "width": { + "value": 11, + "unit": "percent" + }, + "height": { + "value": 44, + "unit": "dp" + }, + "offset": { + "x": 2, + "y": 38, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#CC000000", + "textColor": "#FFFFFFFF", + "fontSize": 22, + "borderRadius": 22 + } + }, + { + "type": "element", + "id": "nav-next", + "elementType": "button", + "bindings": { + "text": "›" + }, + "layout": { + "width": { + "value": 11, + "unit": "percent" + }, + "height": { + "value": 44, + "unit": "dp" + }, + "offset": { + "x": 87, + "y": 38, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#CC000000", + "textColor": "#FFFFFFFF", + "fontSize": 22, + "borderRadius": 22 + } + } + ] + } + ] + }, + { + "type": "container", + "id": "footer", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 20, + "unit": "percent" + }, + "padding": { + "left": 16, + "right": 12, + "unit": "dp" + } + }, + "arrangement": { + "strategy": "space_between" + }, + "style": { + "backgroundColor": "#FFFAFAFA" + }, + "children": [ + { + "type": "element", + "id": "footer-info", + "elementType": "text", + "bindings": { + "text": "720p · 4K available" + }, + "layout": { + "width": { + "value": 55, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#FF6B7280", + "lineHeight": 22, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "footer-cta", + "elementType": "button", + "bindings": { + "text": "Watch Now" + }, + "layout": { + "width": { + "value": 38, + "unit": "percent" + }, + "height": { + "value": 44, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FFB91C1C", + "textColor": "#FFFFFFFF", + "fontSize": 14, + "fontWeight": "bold", + "borderRadius": 18 + } + } + ] + } + ] + } + ] + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-172-gallery-box-grid2col-vertical.json b/flutter-sample/assets/test-configs/test-172-gallery-box-grid2col-vertical.json new file mode 100644 index 0000000..807d8fa --- /dev/null +++ b/flutter-sample/assets/test-configs/test-172-gallery-box-grid2col-vertical.json @@ -0,0 +1,763 @@ +{ + "theme": { + "id": "gallery-172" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "vertical", + "layout": { + "width": { + "special": "match_parent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "all": 12, + "unit": "dp" + } + }, + "children": [ + { + "type": "container", + "id": "outer-card", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 0.85 + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 20, + "shadowColor": "#40000000", + "shadowRadius": 16, + "shadowOffsetX": 0, + "shadowOffsetY": 6 + }, + "children": [ + { + "type": "container", + "id": "card-content", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "children": [ + { + "type": "container", + "id": "title-section", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 22, + "unit": "percent" + }, + "padding": { + "left": 16, + "right": 16, + "top": 14, + "bottom": 10, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF92400E" + }, + "children": [ + { + "type": "container", + "id": "title-content", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "arrangement": { + "spacing": 6, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "category-tag", + "elementType": "text", + "bindings": { + "text": "COLLECTIONS" + }, + "style": { + "fontSize": 11, + "fontWeight": "bold", + "textColor": "#CCFFFFFF", + "lineHeight": 16 + } + }, + { + "type": "element", + "id": "big-title", + "elementType": "text", + "bindings": { + "text": "Browse All" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 22, + "fontWeight": "bold", + "textColor": "#FFFFFFFF", + "lineHeight": 30 + } + } + ] + }, + { + "type": "element", + "id": "see-all", + "elementType": "text", + "bindings": { + "text": "All →" + }, + "layout": { + "width": { + "special": "wrap_content" + }, + "height": { + "special": "wrap_content" + }, + "offset": { + "x": 76, + "y": 10, + "unit": "percent" + } + }, + "style": { + "fontSize": 13, + "textColor": "#DDFFFFFF", + "lineHeight": 18, + "maxLines": 1 + } + } + ] + }, + { + "type": "container", + "id": "gallery-wrapper", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 58, + "unit": "percent" + } + }, + "children": [ + { + "type": "container", + "id": "gallery", + "containerType": "gallery", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "galleryConfig": { + "mode": "free_flow_grid", + "orientation": "vertical", + "columns": 2, + "spacing": 8, + "showIndicators": false + }, + "children": [ + { + "type": "container", + "id": "item-1", + "containerType": "vertical", + "layout": { + "height": { + "special": "wrap_content" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 10, + "borderWidth": 1, + "borderColor": "#FFF0F0F0" + }, + "children": [ + { + "type": "element", + "id": "item-1-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-32.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 110, + "unit": "dp" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-1-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "left": 8, + "right": 8, + "top": 8, + "bottom": 10, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 3, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-1-title", + "elementType": "text", + "bindings": { + "text": "Spring Set" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 13, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 19, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-1-subtitle", + "elementType": "text", + "bindings": { + "text": "12 pieces" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 11, + "textColor": "#FF6B7280", + "lineHeight": 16, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-2", + "containerType": "vertical", + "layout": { + "height": { + "special": "wrap_content" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 10, + "borderWidth": 1, + "borderColor": "#FFF0F0F0" + }, + "children": [ + { + "type": "element", + "id": "item-2-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-33.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 110, + "unit": "dp" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-2-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "left": 8, + "right": 8, + "top": 8, + "bottom": 10, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 3, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-2-title", + "elementType": "text", + "bindings": { + "text": "Summer Set" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 13, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 19, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-2-subtitle", + "elementType": "text", + "bindings": { + "text": "8 pieces" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 11, + "textColor": "#FF6B7280", + "lineHeight": 16, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-3", + "containerType": "vertical", + "layout": { + "height": { + "special": "wrap_content" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 10, + "borderWidth": 1, + "borderColor": "#FFF0F0F0" + }, + "children": [ + { + "type": "element", + "id": "item-3-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-50.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 110, + "unit": "dp" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-3-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "left": 8, + "right": 8, + "top": 8, + "bottom": 10, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 3, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-3-title", + "elementType": "text", + "bindings": { + "text": "Fall Set" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 13, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 19, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-3-subtitle", + "elementType": "text", + "bindings": { + "text": "15 pieces" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 11, + "textColor": "#FF6B7280", + "lineHeight": 16, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-4", + "containerType": "vertical", + "layout": { + "height": { + "special": "wrap_content" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 10, + "borderWidth": 1, + "borderColor": "#FFF0F0F0" + }, + "children": [ + { + "type": "element", + "id": "item-4-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-51.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 110, + "unit": "dp" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-4-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "left": 8, + "right": 8, + "top": 8, + "bottom": 10, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 3, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-4-title", + "elementType": "text", + "bindings": { + "text": "Winter Set" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 13, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 19, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-4-subtitle", + "elementType": "text", + "bindings": { + "text": "10 pieces" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 11, + "textColor": "#FF6B7280", + "lineHeight": 16, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "container", + "id": "footer", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 20, + "unit": "percent" + }, + "padding": { + "left": 16, + "right": 12, + "unit": "dp" + } + }, + "arrangement": { + "strategy": "space_between" + }, + "style": { + "backgroundColor": "#FFFAFAFA" + }, + "children": [ + { + "type": "element", + "id": "footer-info", + "elementType": "text", + "bindings": { + "text": "Browse 240+ items" + }, + "layout": { + "width": { + "value": 55, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#FF6B7280", + "lineHeight": 22, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "footer-cta", + "elementType": "button", + "bindings": { + "text": "Browse" + }, + "layout": { + "width": { + "value": 38, + "unit": "percent" + }, + "height": { + "value": 44, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF92400E", + "textColor": "#FFFFFFFF", + "fontSize": 14, + "fontWeight": "bold", + "borderRadius": 18 + } + } + ] + } + ] + } + ] + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-172-video-fullscreen-openurl.json b/flutter-sample/assets/test-configs/test-172-video-fullscreen-openurl.json new file mode 100644 index 0000000..afc4c10 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-172-video-fullscreen-openurl.json @@ -0,0 +1,34 @@ +{ + "root": { + "type": "container", + "id": "root", + "containerType": "box", + "layout": { + "width": { "value": 100, "unit": "percent" }, + "aspectRatio": 1.778 + }, + "style": { + "backgroundColor": "#000000" + }, + "children": [ + { + "type": "element", + "id": "video", + "elementType": "video", + "bindings": { + "url": "https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8", + "openUrl": "https://peach.blender.org" + }, + "layout": { + "width": { "value": 100, "unit": "percent" }, + "height": { "value": 100, "unit": "percent" } + }, + "autoPlay": false, + "muted": false, + "loop": false, + "showControls": true, + "showFullscreen": true + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-173-gallery-box-snapping-indicators-navbtns.json b/flutter-sample/assets/test-configs/test-173-gallery-box-snapping-indicators-navbtns.json new file mode 100644 index 0000000..1e4839a --- /dev/null +++ b/flutter-sample/assets/test-configs/test-173-gallery-box-snapping-indicators-navbtns.json @@ -0,0 +1,819 @@ +{ + "theme": { + "id": "gallery-173" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "vertical", + "layout": { + "width": { + "special": "match_parent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "all": 12, + "unit": "dp" + } + }, + "children": [ + { + "type": "container", + "id": "outer-card", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 1.3 + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 20, + "shadowColor": "#40000000", + "shadowRadius": 16, + "shadowOffsetX": 0, + "shadowOffsetY": 6 + }, + "children": [ + { + "type": "container", + "id": "card-content", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "children": [ + { + "type": "container", + "id": "title-section", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 22, + "unit": "percent" + }, + "padding": { + "left": 16, + "right": 16, + "top": 14, + "bottom": 10, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF1D4ED8" + }, + "children": [ + { + "type": "container", + "id": "title-content", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "arrangement": { + "spacing": 6, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "category-tag", + "elementType": "text", + "bindings": { + "text": "SALE" + }, + "style": { + "fontSize": 11, + "fontWeight": "bold", + "textColor": "#CCFFFFFF", + "lineHeight": 16 + } + }, + { + "type": "element", + "id": "big-title", + "elementType": "text", + "bindings": { + "text": "Best Deals" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 22, + "fontWeight": "bold", + "textColor": "#FFFFFFFF", + "lineHeight": 30 + } + } + ] + }, + { + "type": "element", + "id": "see-all", + "elementType": "text", + "bindings": { + "text": "All →" + }, + "layout": { + "width": { + "special": "wrap_content" + }, + "height": { + "special": "wrap_content" + }, + "offset": { + "x": 76, + "y": 10, + "unit": "percent" + } + }, + "style": { + "fontSize": 13, + "textColor": "#DDFFFFFF", + "lineHeight": 18, + "maxLines": 1 + } + } + ] + }, + { + "type": "container", + "id": "gallery-wrapper", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 58, + "unit": "percent" + } + }, + "children": [ + { + "type": "container", + "id": "gallery", + "containerType": "gallery", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "galleryConfig": { + "mode": "snapping", + "orientation": "horizontal", + "snapBehavior": "center", + "peek": { + "before": 16, + "after": 16 + }, + "spacing": 12, + "showIndicators": true + }, + "children": [ + { + "type": "container", + "id": "item-1", + "containerType": "vertical", + "layout": { + "width": { + "value": 80, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 14, + "shadowColor": "#1A000000", + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 3 + }, + "children": [ + { + "type": "element", + "id": "item-1-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-52.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 65, + "unit": "percent" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-1-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 35, + "unit": "percent" + }, + "padding": { + "left": 12, + "right": 12, + "top": 10, + "bottom": 8, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 4, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-1-title", + "elementType": "text", + "bindings": { + "text": "Winter Jacket" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 15, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 22, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-1-subtitle", + "elementType": "text", + "bindings": { + "text": "40% off · ₹2,999" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 12, + "textColor": "#FF6B7280", + "lineHeight": 17, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-1-btn", + "elementType": "button", + "bindings": { + "text": "Grab Deal" + }, + "layout": { + "width": { + "value": 90, + "unit": "percent" + }, + "height": { + "value": 32, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF1D4ED8", + "textColor": "#FFFFFFFF", + "fontSize": 13, + "fontWeight": "bold", + "borderRadius": 10 + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-2", + "containerType": "vertical", + "layout": { + "width": { + "value": 80, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 14, + "shadowColor": "#1A000000", + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 3 + }, + "children": [ + { + "type": "element", + "id": "item-2-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-53.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 65, + "unit": "percent" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-2-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 35, + "unit": "percent" + }, + "padding": { + "left": 12, + "right": 12, + "top": 10, + "bottom": 8, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 4, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-2-title", + "elementType": "text", + "bindings": { + "text": "Wool Coat" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 15, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 22, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-2-subtitle", + "elementType": "text", + "bindings": { + "text": "35% off · ₹3,499" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 12, + "textColor": "#FF6B7280", + "lineHeight": 17, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-2-btn", + "elementType": "button", + "bindings": { + "text": "Grab Deal" + }, + "layout": { + "width": { + "value": 90, + "unit": "percent" + }, + "height": { + "value": 32, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF1D4ED8", + "textColor": "#FFFFFFFF", + "fontSize": 13, + "fontWeight": "bold", + "borderRadius": 10 + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-3", + "containerType": "vertical", + "layout": { + "width": { + "value": 80, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 14, + "shadowColor": "#1A000000", + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 3 + }, + "children": [ + { + "type": "element", + "id": "item-3-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-54.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 65, + "unit": "percent" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-3-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 35, + "unit": "percent" + }, + "padding": { + "left": 12, + "right": 12, + "top": 10, + "bottom": 8, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 4, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-3-title", + "elementType": "text", + "bindings": { + "text": "Puffer Vest" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 15, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 22, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-3-subtitle", + "elementType": "text", + "bindings": { + "text": "50% off · ₹1,799" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 12, + "textColor": "#FF6B7280", + "lineHeight": 17, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-3-btn", + "elementType": "button", + "bindings": { + "text": "Grab Deal" + }, + "layout": { + "width": { + "value": 90, + "unit": "percent" + }, + "height": { + "value": 32, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF1D4ED8", + "textColor": "#FFFFFFFF", + "fontSize": 13, + "fontWeight": "bold", + "borderRadius": 10 + } + } + ] + } + ] + } + ] + }, + { + "type": "container", + "id": "nav-overlay", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "children": [ + { + "type": "element", + "id": "nav-prev", + "elementType": "button", + "bindings": { + "text": "‹" + }, + "layout": { + "width": { + "value": 11, + "unit": "percent" + }, + "height": { + "value": 44, + "unit": "dp" + }, + "offset": { + "x": 2, + "y": 38, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#CC000000", + "textColor": "#FFFFFFFF", + "fontSize": 22, + "borderRadius": 22 + } + }, + { + "type": "element", + "id": "nav-next", + "elementType": "button", + "bindings": { + "text": "›" + }, + "layout": { + "width": { + "value": 11, + "unit": "percent" + }, + "height": { + "value": 44, + "unit": "dp" + }, + "offset": { + "x": 87, + "y": 38, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#CC000000", + "textColor": "#FFFFFFFF", + "fontSize": 22, + "borderRadius": 22 + } + } + ] + } + ] + }, + { + "type": "container", + "id": "footer", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 20, + "unit": "percent" + }, + "padding": { + "left": 16, + "right": 12, + "unit": "dp" + } + }, + "arrangement": { + "strategy": "space_between" + }, + "style": { + "backgroundColor": "#FFFAFAFA" + }, + "children": [ + { + "type": "element", + "id": "footer-info", + "elementType": "text", + "bindings": { + "text": "Deal ends midnight" + }, + "layout": { + "width": { + "value": 55, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#FF6B7280", + "lineHeight": 22, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "footer-cta", + "elementType": "button", + "bindings": { + "text": "Grab Deal" + }, + "layout": { + "width": { + "value": 38, + "unit": "percent" + }, + "height": { + "value": 44, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF1D4ED8", + "textColor": "#FFFFFFFF", + "fontSize": 14, + "fontWeight": "bold", + "borderRadius": 18 + } + } + ] + } + ] + } + ] + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-174-gallery-box-snapping-indicators-only.json b/flutter-sample/assets/test-configs/test-174-gallery-box-snapping-indicators-only.json new file mode 100644 index 0000000..61785e9 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-174-gallery-box-snapping-indicators-only.json @@ -0,0 +1,667 @@ +{ + "theme": { + "id": "gallery-174" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "vertical", + "layout": { + "width": { + "special": "match_parent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "all": 12, + "unit": "dp" + } + }, + "children": [ + { + "type": "container", + "id": "outer-card", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 1.3 + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 20, + "shadowColor": "#40000000", + "shadowRadius": 16, + "shadowOffsetX": 0, + "shadowOffsetY": 6 + }, + "children": [ + { + "type": "container", + "id": "card-content", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "children": [ + { + "type": "container", + "id": "title-section", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 22, + "unit": "percent" + }, + "padding": { + "left": 16, + "right": 16, + "top": 14, + "bottom": 10, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF5B21B6" + }, + "children": [ + { + "type": "container", + "id": "title-content", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "arrangement": { + "spacing": 6, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "category-tag", + "elementType": "text", + "bindings": { + "text": "EXCLUSIVE" + }, + "style": { + "fontSize": 11, + "fontWeight": "bold", + "textColor": "#CCFFFFFF", + "lineHeight": 16 + } + }, + { + "type": "element", + "id": "big-title", + "elementType": "text", + "bindings": { + "text": "Members Only" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 22, + "fontWeight": "bold", + "textColor": "#FFFFFFFF", + "lineHeight": 30 + } + } + ] + }, + { + "type": "element", + "id": "see-all", + "elementType": "text", + "bindings": { + "text": "All →" + }, + "layout": { + "width": { + "special": "wrap_content" + }, + "height": { + "special": "wrap_content" + }, + "offset": { + "x": 76, + "y": 10, + "unit": "percent" + } + }, + "style": { + "fontSize": 13, + "textColor": "#DDFFFFFF", + "lineHeight": 18, + "maxLines": 1 + } + } + ] + }, + { + "type": "container", + "id": "gallery-wrapper", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 58, + "unit": "percent" + } + }, + "children": [ + { + "type": "container", + "id": "gallery", + "containerType": "gallery", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "galleryConfig": { + "mode": "snapping", + "orientation": "horizontal", + "snapBehavior": "center", + "peekPercentage": 10, + "spacing": 12, + "showIndicators": true + }, + "children": [ + { + "type": "container", + "id": "item-1", + "containerType": "vertical", + "layout": { + "width": { + "value": 80, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 14, + "shadowColor": "#1A000000", + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 3 + }, + "children": [ + { + "type": "element", + "id": "item-1-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-31.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 65, + "unit": "percent" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-1-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 35, + "unit": "percent" + }, + "padding": { + "left": 12, + "right": 12, + "top": 10, + "bottom": 8, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 4, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-1-title", + "elementType": "text", + "bindings": { + "text": "Gold Handbag" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 15, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 22, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-1-subtitle", + "elementType": "text", + "bindings": { + "text": "Limited edition" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 12, + "textColor": "#FF6B7280", + "lineHeight": 17, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-2", + "containerType": "vertical", + "layout": { + "width": { + "value": 80, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 14, + "shadowColor": "#1A000000", + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 3 + }, + "children": [ + { + "type": "element", + "id": "item-2-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-33.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 65, + "unit": "percent" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-2-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 35, + "unit": "percent" + }, + "padding": { + "left": 12, + "right": 12, + "top": 10, + "bottom": 8, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 4, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-2-title", + "elementType": "text", + "bindings": { + "text": "Designer Watch" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 15, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 22, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-2-subtitle", + "elementType": "text", + "bindings": { + "text": "Swiss movement" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 12, + "textColor": "#FF6B7280", + "lineHeight": 17, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-3", + "containerType": "vertical", + "layout": { + "width": { + "value": 80, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 14, + "shadowColor": "#1A000000", + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 3 + }, + "children": [ + { + "type": "element", + "id": "item-3-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-49.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 65, + "unit": "percent" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-3-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 35, + "unit": "percent" + }, + "padding": { + "left": 12, + "right": 12, + "top": 10, + "bottom": 8, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 4, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-3-title", + "elementType": "text", + "bindings": { + "text": "Silk Saree" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 15, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 22, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-3-subtitle", + "elementType": "text", + "bindings": { + "text": "Handwoven" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 12, + "textColor": "#FF6B7280", + "lineHeight": 17, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "container", + "id": "footer", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 20, + "unit": "percent" + }, + "padding": { + "left": 16, + "right": 12, + "unit": "dp" + } + }, + "arrangement": { + "strategy": "space_between" + }, + "style": { + "backgroundColor": "#FFFAFAFA" + }, + "children": [ + { + "type": "element", + "id": "footer-info", + "elementType": "text", + "bindings": { + "text": "Exclusive access" + }, + "layout": { + "width": { + "value": 55, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#FF6B7280", + "lineHeight": 22, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "footer-cta", + "elementType": "button", + "bindings": { + "text": "Unlock" + }, + "layout": { + "width": { + "value": 38, + "unit": "percent" + }, + "height": { + "value": 44, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF5B21B6", + "textColor": "#FFFFFFFF", + "fontSize": 14, + "fontWeight": "bold", + "borderRadius": 18 + } + } + ] + } + ] + } + ] + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-175-gallery-box-snapping-navbtns-only.json b/flutter-sample/assets/test-configs/test-175-gallery-box-snapping-navbtns-only.json new file mode 100644 index 0000000..ba8a5d1 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-175-gallery-box-snapping-navbtns-only.json @@ -0,0 +1,741 @@ +{ + "theme": { + "id": "gallery-175" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "vertical", + "layout": { + "width": { + "special": "match_parent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "all": 12, + "unit": "dp" + } + }, + "children": [ + { + "type": "container", + "id": "outer-card", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 1.3 + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 20, + "shadowColor": "#40000000", + "shadowRadius": 16, + "shadowOffsetX": 0, + "shadowOffsetY": 6 + }, + "children": [ + { + "type": "container", + "id": "card-content", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "children": [ + { + "type": "container", + "id": "title-section", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 22, + "unit": "percent" + }, + "padding": { + "left": 16, + "right": 16, + "top": 14, + "bottom": 10, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF0F766E" + }, + "children": [ + { + "type": "container", + "id": "title-content", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "arrangement": { + "spacing": 6, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "category-tag", + "elementType": "text", + "bindings": { + "text": "STREAMING" + }, + "style": { + "fontSize": 11, + "fontWeight": "bold", + "textColor": "#CCFFFFFF", + "lineHeight": 16 + } + }, + { + "type": "element", + "id": "big-title", + "elementType": "text", + "bindings": { + "text": "Now Playing" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 22, + "fontWeight": "bold", + "textColor": "#FFFFFFFF", + "lineHeight": 30 + } + } + ] + }, + { + "type": "element", + "id": "see-all", + "elementType": "text", + "bindings": { + "text": "All →" + }, + "layout": { + "width": { + "special": "wrap_content" + }, + "height": { + "special": "wrap_content" + }, + "offset": { + "x": 76, + "y": 10, + "unit": "percent" + } + }, + "style": { + "fontSize": 13, + "textColor": "#DDFFFFFF", + "lineHeight": 18, + "maxLines": 1 + } + } + ] + }, + { + "type": "container", + "id": "gallery-wrapper", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 58, + "unit": "percent" + } + }, + "children": [ + { + "type": "container", + "id": "gallery", + "containerType": "gallery", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "galleryConfig": { + "mode": "snapping", + "orientation": "horizontal", + "snapBehavior": "center", + "peek": { + "before": 16, + "after": 16 + }, + "spacing": 12, + "showIndicators": false + }, + "children": [ + { + "type": "container", + "id": "item-1", + "containerType": "vertical", + "layout": { + "width": { + "value": 80, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 14, + "shadowColor": "#1A000000", + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 3 + }, + "children": [ + { + "type": "element", + "id": "item-1-video", + "elementType": "video", + "bindings": { + "url": "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4", + "autoPlay": "false", + "loop": "false", + "muted": "false", + "showControls": "true" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 65, + "unit": "percent" + } + } + }, + { + "type": "container", + "id": "item-1-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 35, + "unit": "percent" + }, + "padding": { + "left": 12, + "right": 12, + "top": 10, + "bottom": 8, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 4, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-1-title", + "elementType": "text", + "bindings": { + "text": "Action Thriller" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 15, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 22, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-1-subtitle", + "elementType": "text", + "bindings": { + "text": "Ep 1 · 45 min" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 12, + "textColor": "#FF6B7280", + "lineHeight": 17, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-2", + "containerType": "vertical", + "layout": { + "width": { + "value": 80, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 14, + "shadowColor": "#1A000000", + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 3 + }, + "children": [ + { + "type": "element", + "id": "item-2-video", + "elementType": "video", + "bindings": { + "url": "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4", + "autoPlay": "false", + "loop": "false", + "muted": "false", + "showControls": "true" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 65, + "unit": "percent" + } + } + }, + { + "type": "container", + "id": "item-2-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 35, + "unit": "percent" + }, + "padding": { + "left": 12, + "right": 12, + "top": 10, + "bottom": 8, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 4, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-2-title", + "elementType": "text", + "bindings": { + "text": "Romance Drama" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 15, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 22, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-2-subtitle", + "elementType": "text", + "bindings": { + "text": "Ep 3 · 40 min" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 12, + "textColor": "#FF6B7280", + "lineHeight": 17, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-3", + "containerType": "vertical", + "layout": { + "width": { + "value": 80, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 14, + "shadowColor": "#1A000000", + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 3 + }, + "children": [ + { + "type": "element", + "id": "item-3-video", + "elementType": "video", + "bindings": { + "url": "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4", + "autoPlay": "false", + "loop": "false", + "muted": "false", + "showControls": "true" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 65, + "unit": "percent" + } + } + }, + { + "type": "container", + "id": "item-3-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 35, + "unit": "percent" + }, + "padding": { + "left": 12, + "right": 12, + "top": 10, + "bottom": 8, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 4, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-3-title", + "elementType": "text", + "bindings": { + "text": "Sci-Fi Series" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 15, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 22, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-3-subtitle", + "elementType": "text", + "bindings": { + "text": "Ep 7 · 52 min" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 12, + "textColor": "#FF6B7280", + "lineHeight": 17, + "maxLines": 1, + "overflow": "ellipsis" + } + } + ] + } + ] + } + ] + }, + { + "type": "container", + "id": "nav-overlay", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "children": [ + { + "type": "element", + "id": "nav-prev", + "elementType": "button", + "bindings": { + "text": "‹" + }, + "layout": { + "width": { + "value": 11, + "unit": "percent" + }, + "height": { + "value": 44, + "unit": "dp" + }, + "offset": { + "x": 2, + "y": 38, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#CC000000", + "textColor": "#FFFFFFFF", + "fontSize": 22, + "borderRadius": 22 + } + }, + { + "type": "element", + "id": "nav-next", + "elementType": "button", + "bindings": { + "text": "›" + }, + "layout": { + "width": { + "value": 11, + "unit": "percent" + }, + "height": { + "value": 44, + "unit": "dp" + }, + "offset": { + "x": 87, + "y": 38, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#CC000000", + "textColor": "#FFFFFFFF", + "fontSize": 22, + "borderRadius": 22 + } + } + ] + } + ] + }, + { + "type": "container", + "id": "footer", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 20, + "unit": "percent" + }, + "padding": { + "left": 16, + "right": 12, + "unit": "dp" + } + }, + "arrangement": { + "strategy": "space_between" + }, + "style": { + "backgroundColor": "#FFFAFAFA" + }, + "children": [ + { + "type": "element", + "id": "footer-info", + "elementType": "text", + "bindings": { + "text": "HD · Offline ready" + }, + "layout": { + "width": { + "value": 55, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#FF6B7280", + "lineHeight": 22, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "footer-cta", + "elementType": "button", + "bindings": { + "text": "Play Now" + }, + "layout": { + "width": { + "value": 38, + "unit": "percent" + }, + "height": { + "value": 44, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF0F766E", + "textColor": "#FFFFFFFF", + "fontSize": 14, + "fontWeight": "bold", + "borderRadius": 18 + } + } + ] + } + ] + } + ] + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-176-gallery-box-snapping-minimal.json b/flutter-sample/assets/test-configs/test-176-gallery-box-snapping-minimal.json new file mode 100644 index 0000000..501394c --- /dev/null +++ b/flutter-sample/assets/test-configs/test-176-gallery-box-snapping-minimal.json @@ -0,0 +1,673 @@ +{ + "theme": { + "id": "gallery-176" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "vertical", + "layout": { + "width": { + "special": "match_parent" + }, + "height": { + "special": "wrap_content" + }, + "padding": { + "all": 12, + "unit": "dp" + } + }, + "children": [ + { + "type": "container", + "id": "outer-card", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "aspectRatio": 1.3 + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 20, + "shadowColor": "#40000000", + "shadowRadius": 16, + "shadowOffsetX": 0, + "shadowOffsetY": 6 + }, + "children": [ + { + "type": "container", + "id": "card-content", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "children": [ + { + "type": "container", + "id": "title-section", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 22, + "unit": "percent" + }, + "padding": { + "left": 16, + "right": 16, + "top": 14, + "bottom": 10, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF065F46" + }, + "children": [ + { + "type": "container", + "id": "title-content", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "arrangement": { + "spacing": 6, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "category-tag", + "elementType": "text", + "bindings": { + "text": "ORGANIC" + }, + "style": { + "fontSize": 11, + "fontWeight": "bold", + "textColor": "#CCFFFFFF", + "lineHeight": 16 + } + }, + { + "type": "element", + "id": "big-title", + "elementType": "text", + "bindings": { + "text": "Natural Choice" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 22, + "fontWeight": "bold", + "textColor": "#FFFFFFFF", + "lineHeight": 30 + } + } + ] + }, + { + "type": "element", + "id": "see-all", + "elementType": "text", + "bindings": { + "text": "All →" + }, + "layout": { + "width": { + "special": "wrap_content" + }, + "height": { + "special": "wrap_content" + }, + "offset": { + "x": 76, + "y": 10, + "unit": "percent" + } + }, + "style": { + "fontSize": 13, + "textColor": "#DDFFFFFF", + "lineHeight": 18, + "maxLines": 1 + } + } + ] + }, + { + "type": "container", + "id": "gallery-wrapper", + "containerType": "box", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 58, + "unit": "percent" + } + }, + "children": [ + { + "type": "container", + "id": "gallery", + "containerType": "gallery", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "galleryConfig": { + "mode": "snapping", + "orientation": "horizontal", + "snapBehavior": "center", + "peek": { + "before": 16, + "after": 16 + }, + "spacing": 12, + "showIndicators": false + }, + "children": [ + { + "type": "container", + "id": "item-1", + "containerType": "vertical", + "layout": { + "width": { + "value": 80, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 14, + "shadowColor": "#1A000000", + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 3 + }, + "children": [ + { + "type": "element", + "id": "item-1-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-50.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 65, + "unit": "percent" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-1-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 35, + "unit": "percent" + }, + "padding": { + "left": 12, + "right": 12, + "top": 10, + "bottom": 8, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 4, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-1-title", + "elementType": "text", + "bindings": { + "text": "Moringa Oil" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 15, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 22, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-1-btn", + "elementType": "button", + "bindings": { + "text": "Shop Now" + }, + "layout": { + "width": { + "value": 90, + "unit": "percent" + }, + "height": { + "value": 32, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF065F46", + "textColor": "#FFFFFFFF", + "fontSize": 13, + "fontWeight": "bold", + "borderRadius": 10 + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-2", + "containerType": "vertical", + "layout": { + "width": { + "value": 80, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 14, + "shadowColor": "#1A000000", + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 3 + }, + "children": [ + { + "type": "element", + "id": "item-2-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-52.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 65, + "unit": "percent" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-2-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 35, + "unit": "percent" + }, + "padding": { + "left": 12, + "right": 12, + "top": 10, + "bottom": 8, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 4, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-2-title", + "elementType": "text", + "bindings": { + "text": "Tulsi Honey" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 15, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 22, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-2-btn", + "elementType": "button", + "bindings": { + "text": "Shop Now" + }, + "layout": { + "width": { + "value": 90, + "unit": "percent" + }, + "height": { + "value": 32, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF065F46", + "textColor": "#FFFFFFFF", + "fontSize": 13, + "fontWeight": "bold", + "borderRadius": 10 + } + } + ] + } + ] + }, + { + "type": "container", + "id": "item-3", + "containerType": "vertical", + "layout": { + "width": { + "value": 80, + "unit": "percent" + }, + "height": { + "special": "match_parent" + } + }, + "style": { + "backgroundColor": "#FFFFFFFF", + "borderRadius": 14, + "shadowColor": "#1A000000", + "shadowRadius": 6, + "shadowOffsetX": 0, + "shadowOffsetY": 3 + }, + "children": [ + { + "type": "element", + "id": "item-3-img", + "elementType": "image", + "bindings": { + "url": "https://yavuzceliker.github.io/sample-images/image-54.jpg" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 65, + "unit": "percent" + } + }, + "style": { + "imageConfig": { + "fit": "crop" + } + } + }, + { + "type": "container", + "id": "item-3-info", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 35, + "unit": "percent" + }, + "padding": { + "left": 12, + "right": 12, + "top": 10, + "bottom": 8, + "unit": "dp" + } + }, + "arrangement": { + "spacing": 4, + "spacingUnit": "dp", + "strategy": "spaced" + }, + "children": [ + { + "type": "element", + "id": "item-3-title", + "elementType": "text", + "bindings": { + "text": "Shea Butter" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 15, + "fontWeight": "bold", + "textColor": "#FF1A1A2E", + "lineHeight": 22, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "item-3-btn", + "elementType": "button", + "bindings": { + "text": "Shop Now" + }, + "layout": { + "width": { + "value": 90, + "unit": "percent" + }, + "height": { + "value": 32, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF065F46", + "textColor": "#FFFFFFFF", + "fontSize": 13, + "fontWeight": "bold", + "borderRadius": 10 + } + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "container", + "id": "footer", + "containerType": "horizontal", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 20, + "unit": "percent" + }, + "padding": { + "left": 16, + "right": 12, + "unit": "dp" + } + }, + "arrangement": { + "strategy": "space_between" + }, + "style": { + "backgroundColor": "#FFFAFAFA" + }, + "children": [ + { + "type": "element", + "id": "footer-info", + "elementType": "text", + "bindings": { + "text": "100% organic · Verified" + }, + "layout": { + "width": { + "value": 55, + "unit": "percent" + }, + "height": { + "special": "wrap_content" + } + }, + "style": { + "fontSize": 14, + "textColor": "#FF6B7280", + "lineHeight": 22, + "maxLines": 1, + "overflow": "ellipsis" + } + }, + { + "type": "element", + "id": "footer-cta", + "elementType": "button", + "bindings": { + "text": "Shop Now" + }, + "layout": { + "width": { + "value": 38, + "unit": "percent" + }, + "height": { + "value": 44, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#FF065F46", + "textColor": "#FFFFFFFF", + "fontSize": 14, + "fontWeight": "bold", + "borderRadius": 18 + } + } + ] + } + ] + } + ] + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-177-html-inline-basic.json b/flutter-sample/assets/test-configs/test-177-html-inline-basic.json new file mode 100644 index 0000000..c1482b6 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-177-html-inline-basic.json @@ -0,0 +1,51 @@ +{ + "root": { + "type": "container", + "id": "root", + "containerType": "vertical", + "layout": { + "width": { "special": "match_parent" }, + "height": { "special": "wrap_content" } + }, + "style": { + "backgroundColor": "#1A1A2E" + }, + "children": [ + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "HTML Element — Inline" + }, + "layout": { + "width": { "special": "match_parent" }, + "height": { "special": "wrap_content" }, + "padding": { "all": 16 } + }, + "style": { + "textColor": "#FFFFFF", + "fontSize": 20, + "fontWeight": "bold" + } + }, + { + "type": "element", + "id": "html-inline", + "elementType": "html", + "bindings": { + "html": "

Welcome!

This is inline HTML rendered in a WebView. It supports rich formatting, links, and more.

" + }, + "htmlConfig": { + "javascriptEnabled": false, + "scrollEnabled": false, + "transparentBackground": true + }, + "layout": { + "width": { "special": "match_parent" }, + "height": { "value": 150, "unit": "dp" } + } + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-178-html-with-javascript.json b/flutter-sample/assets/test-configs/test-178-html-with-javascript.json new file mode 100644 index 0000000..c11a6f5 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-178-html-with-javascript.json @@ -0,0 +1,51 @@ +{ + "root": { + "type": "container", + "id": "root", + "containerType": "vertical", + "layout": { + "width": { "special": "match_parent" }, + "height": { "special": "wrap_content" } + }, + "style": { + "backgroundColor": "#0F0F23" + }, + "children": [ + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "HTML Element — JavaScript Enabled" + }, + "layout": { + "width": { "special": "match_parent" }, + "height": { "special": "wrap_content" }, + "padding": { "all": 16 } + }, + "style": { + "textColor": "#FFFFFF", + "fontSize": 20, + "fontWeight": "bold" + } + }, + { + "type": "element", + "id": "html-js", + "elementType": "html", + "bindings": { + "html": "
0
" + }, + "htmlConfig": { + "javascriptEnabled": true, + "scrollEnabled": false, + "transparentBackground": true + }, + "layout": { + "width": { "special": "match_parent" }, + "height": { "value": 200, "unit": "dp" } + } + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-179-html-transparent-bg.json b/flutter-sample/assets/test-configs/test-179-html-transparent-bg.json new file mode 100644 index 0000000..65af3e0 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-179-html-transparent-bg.json @@ -0,0 +1,41 @@ +{ + "root": { + "type": "container", + "id": "root", + "containerType": "box", + "layout": { + "width": { "special": "match_parent" }, + "height": { "value": 300, "unit": "dp" } + }, + "children": [ + { + "type": "element", + "id": "bg-image", + "elementType": "image", + "bindings": { + "url": "https://picsum.photos/800/300" + }, + "layout": { + "width": { "special": "match_parent" }, + "height": { "special": "match_parent" } + } + }, + { + "type": "element", + "id": "html-overlay", + "elementType": "html", + "bindings": { + "html": "

Transparent Overlay

This HTML element has a transparent background, allowing the image behind it to show through.

" + }, + "htmlConfig": { + "transparentBackground": true, + "scrollEnabled": false + }, + "layout": { + "width": { "special": "match_parent" }, + "height": { "special": "match_parent" } + } + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-180-html-scrollable-content.json b/flutter-sample/assets/test-configs/test-180-html-scrollable-content.json new file mode 100644 index 0000000..827f611 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-180-html-scrollable-content.json @@ -0,0 +1,50 @@ +{ + "root": { + "type": "container", + "id": "root", + "containerType": "vertical", + "layout": { + "width": { "special": "match_parent" }, + "height": { "special": "wrap_content" } + }, + "style": { + "backgroundColor": "#FFFFFF" + }, + "children": [ + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "HTML Element — Scrollable" + }, + "layout": { + "width": { "special": "match_parent" }, + "height": { "special": "wrap_content" }, + "padding": { "all": 16 } + }, + "style": { + "textColor": "#333333", + "fontSize": 20, + "fontWeight": "bold" + } + }, + { + "type": "element", + "id": "html-scroll", + "elementType": "html", + "bindings": { + "html": "

Terms & Conditions

By using this application, you agree to the following terms and conditions. Please read carefully before proceeding.

Privacy Policy

We collect and process your data in accordance with applicable privacy regulations.

  • Usage data is collected anonymously
  • Personal information is encrypted
  • You may request data deletion at any time
  • Third-party sharing requires consent

Contact

For questions or concerns, please reach out to our support team.

" + }, + "htmlConfig": { + "scrollEnabled": true, + "transparentBackground": false + }, + "layout": { + "width": { "special": "match_parent" }, + "height": { "value": 250, "unit": "dp" } + } + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-VERIFY-percentage-offset-fix.json b/flutter-sample/assets/test-configs/test-VERIFY-percentage-offset-fix.json new file mode 100644 index 0000000..e88c0e1 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-VERIFY-percentage-offset-fix.json @@ -0,0 +1,204 @@ +{ + "theme": { + "id": "verify-fix", + "defaultStyle": { + "textColor": "#000000", + "fontSize": 14, + "lineHeight": 20 + } + }, + "variables": { + "testName": "VERIFICATION: Percentage Offset Fix" + }, + "root": { + "type": "container", + "id": "root", + "containerType": "vertical", + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "height": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 16, + "unit": "dp" + }, + "arrangement": { + "spacing": 16, + "spacingUnit": "dp", + "strategy": "spaced" + } + }, + "style": { + "backgroundColor": "#F5F5F5" + }, + "children": [ + { + "type": "element", + "id": "title", + "elementType": "text", + "bindings": { + "text": "{{testName}}" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 18, + "fontWeight": "bold", + "textColor": "#333333", + "lineHeight": 25 + } + }, + { + "type": "element", + "id": "description", + "elementType": "text", + "bindings": { + "text": "Parent box is 300dp x 300dp. Child box is 50dp x 50dp with 10% offset. If correct, child should be at (30dp, 30dp) from parent's top-left, NOT at (5dp, 5dp)." + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + } + }, + "style": { + "fontSize": 12, + "textColor": "#666666", + "lineHeight": 17 + } + }, + { + "type": "container", + "id": "parent-box", + "containerType": "box", + "layout": { + "width": { + "value": 300, + "unit": "dp" + }, + "height": { + "value": 300, + "unit": "dp" + } + }, + "style": { + "backgroundColor": "#E0E0E0", + "borderWidth": 2, + "borderColor": "#000000" + }, + "children": [ + { + "type": "element", + "id": "grid-line-30", + "elementType": "text", + "bindings": { + "text": "← 30dp marker" + }, + "layout": { + "width": { + "value": 100, + "unit": "dp" + }, + "offset": { + "x": 30, + "y": 30, + "unit": "dp" + } + }, + "style": { + "fontSize": 10, + "textColor": "#999999", + "lineHeight": 14 + } + }, + { + "type": "container", + "id": "child-box", + "containerType": "box", + "layout": { + "width": { + "value": 50, + "unit": "dp" + }, + "height": { + "value": 50, + "unit": "dp" + }, + "offset": { + "x": 10, + "y": 10, + "unit": "percent" + } + }, + "style": { + "backgroundColor": "#2196F3", + "borderWidth": 2, + "borderColor": "#FFFFFF" + }, + "children": [ + { + "type": "element", + "id": "child-label", + "elementType": "text", + "bindings": { + "text": "10%" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 8, + "unit": "dp" + } + }, + "style": { + "fontSize": 12, + "fontWeight": "bold", + "textColor": "#FFFFFF", + "textAlign": "center", + "lineHeight": 17 + } + } + ] + } + ] + }, + { + "type": "element", + "id": "expected-result", + "elementType": "text", + "bindings": { + "text": "✅ EXPECTED: Blue box should align with the '30dp marker' text above it\n❌ BUG: Blue box would be near top-left corner (only 5dp offset)" + }, + "layout": { + "width": { + "value": 100, + "unit": "percent" + }, + "padding": { + "all": 12, + "unit": "dp" + } + }, + "style": { + "fontSize": 12, + "textColor": "#333333", + "backgroundColor": "#FFFFCC", + "borderRadius": 4, + "lineHeight": 17 + } + } + ] + } +} diff --git a/flutter-sample/assets/test-configs/test-parity-80pct-ar.json b/flutter-sample/assets/test-configs/test-parity-80pct-ar.json new file mode 100644 index 0000000..806c774 --- /dev/null +++ b/flutter-sample/assets/test-configs/test-parity-80pct-ar.json @@ -0,0 +1,97 @@ +{ + "root": { + "type": "container", + "id": "root", + "containerType": "box", + "layout": { + "padding": {"all": 0}, + "width": {"unit": "percent", "value": 80}, + "aspectRatio": 1.7778, + "height": {"unit": "percent", "value": 60} + }, + "style": { + "background": { + "type": "linear_gradient", + "angle": 180, + "colors": ["#d4b8ff", "#b8d4ff"] + }, + "borderRadius": 12 + }, + "children": [ + { + "type": "element", + "id": "text-1", + "elementType": "text", + "layout": { + "offset": {"unit": "percent", "x": 1.0, "y": 2.0}, + "width": {"unit": "percent", "value": 99.0}, + "height": {"unit": "percent", "value": 16.0} + }, + "bindings": {"text": "80% width + AR 16:9"}, + "style": { + "fontSize": {"unit": "percent", "value": 130}, + "fontWeight": "bold", + "fontStyle": "italic", + "textColor": "#333333", + "textAlign": "center" + } + }, + { + "type": "element", + "id": "img-1", + "elementType": "image", + "layout": { + "offset": {"unit": "percent", "x": 5.0, "y": 24.0}, + "width": {"unit": "percent", "value": 43.0}, + "height": {"unit": "percent", "value": 50.0} + }, + "bindings": {"url": "https://images.unsplash.com/photo-1520763185298-1b434c919102?w=400"} + }, + { + "type": "element", + "id": "img-2", + "elementType": "image", + "layout": { + "offset": {"unit": "percent", "x": 52.0, "y": 24.0}, + "width": {"unit": "percent", "value": 43.0}, + "height": {"unit": "percent", "value": 50.0} + }, + "bindings": {"url": "https://images.unsplash.com/photo-1559128010-7c1ad6e1b6a5?w=400"} + }, + { + "type": "element", + "id": "btn-1", + "elementType": "button", + "layout": { + "offset": {"unit": "percent", "x": 5.0, "y": 78.0}, + "width": {"unit": "percent", "value": 43.0}, + "height": {"unit": "percent", "value": 14.0} + }, + "bindings": {"text": "Flower"}, + "style": { + "backgroundColor": "#4a4a7a", + "textColor": "#FFFFFF", + "fontSize": {"unit": "percent", "value": 90}, + "borderRadius": 6 + } + }, + { + "type": "element", + "id": "btn-2", + "elementType": "button", + "layout": { + "offset": {"unit": "percent", "x": 52.0, "y": 78.0}, + "width": {"unit": "percent", "value": 43.0}, + "height": {"unit": "percent", "value": 14.0} + }, + "bindings": {"text": "Rainbow"}, + "style": { + "backgroundColor": "#7a3a3a", + "textColor": "#FFFFFF", + "fontSize": {"unit": "percent", "value": 90}, + "borderRadius": 6 + } + } + ] + } +} diff --git a/flutter-sample/lib/screens/clevertap_integration_screen.dart b/flutter-sample/lib/screens/clevertap_integration_screen.dart index 62ef9b8..6ea6ac0 100644 --- a/flutter-sample/lib/screens/clevertap_integration_screen.dart +++ b/flutter-sample/lib/screens/clevertap_integration_screen.dart @@ -1,12 +1,9 @@ -import 'dart:async'; import 'dart:convert'; +import 'package:clevertap_plugin/clevertap_plugin.dart'; import 'package:flutter/material.dart'; import 'package:clevertap_native_display/clevertap_native_display.dart'; -/// Mirrors the Android CleverTapIntegrationScreen. -/// Subscribes to NativeDisplayBridge.eventStream to receive display units pushed -/// from the native CleverTap Core SDK and renders them via NativeDisplayView. class CleverTapIntegrationScreen extends StatefulWidget { const CleverTapIntegrationScreen({super.key}); @@ -17,71 +14,129 @@ class CleverTapIntegrationScreen extends StatefulWidget { class _CleverTapIntegrationScreenState extends State { final _eventController = TextEditingController(); final List _log = []; - final List _units = []; - StreamSubscription>? _unitSubscription; + final List<_UnitEntry> _units = []; + + // CleverTapPlugin() returns the singleton — needed for instance method registration. + final _ct = CleverTapPlugin(); @override void initState() { super.initState(); - _subscribeToUnits(); + _ct.setCleverTapDisplayUnitsLoadedHandler(_onUnitsLoaded); + _fetchCachedUnits(); } @override void dispose() { - _unitSubscription?.cancel(); _eventController.dispose(); super.dispose(); } - void _log_(String msg) { - final now = TimeOfDay.now(); - final ts = '[${now.hour.toString().padLeft(2, '0')}:${now.minute.toString().padLeft(2, '0')}]'; - setState(() => _log.add('$ts $msg')); + Future _fetchCachedUnits() async { + try { + final units = await CleverTapPlugin.getAllDisplayUnits(); + if (units != null && units.isNotEmpty) { + _onUnitsLoaded(units.cast()); + } + } catch (e) { + _addLog('ERROR fetching cached units: $e'); + } } - void _subscribeToUnits() { - _unitSubscription = NativeDisplayBridge.eventStream.listen( - (event) { - if (event['type'] == 'units_updated') { - final rawUnits = (event['units'] as List?)?.cast() ?? []; - final configs = []; - for (final jsonStr in rawUnits) { - if (jsonStr.isEmpty) continue; - try { - final map = jsonDecode(jsonStr) as Map; - configs.add(NativeDisplayConfig.fromJson(map)); - } catch (e) { - _log_('ERROR parsing unit: $e'); - } - } - setState(() { - _units - ..clear() - ..addAll(configs); - _log.add( - '[${_timestamp()}] Received ${configs.length} Native Display unit(s)', - ); - }); - } - }, - onError: (Object e) => _log_('ERROR from event stream: $e'), - ); + void _onUnitsLoaded(List? units) { + if (units == null || units.isEmpty) return; + final parsed = <_UnitEntry>[]; + for (final unit in units) { + final entry = _extractEntry(unit); + if (entry != null) { + parsed.add(entry); + } else { + _addLog('WARN: unit has no recognisable NativeDisplayConfig'); + } + } + if (!mounted) return; + setState(() { + _units + ..clear() + ..addAll(parsed); + _log.add('[${_ts()}] Received ${parsed.length} Native Display unit(s)'); + }); + } + + /// Platform channel maps arrive as Map at every nesting level. + /// This recursively converts the entire tree to Map. + static Map _deepCast(Map raw) => + raw.map((k, v) => MapEntry(k.toString(), _deepCastValue(v))); + + static dynamic _deepCastValue(dynamic v) { + if (v is Map) return _deepCast(v); + if (v is List) return v.map(_deepCastValue).toList(); + return v; } - String _timestamp() { - final now = TimeOfDay.now(); - return '${now.hour.toString().padLeft(2, '0')}:${now.minute.toString().padLeft(2, '0')}'; + /// Mirrors the 3-strategy extraction from the old SampleApplication.kt. + _UnitEntry? _extractEntry(dynamic raw) { + if (raw is! Map) return null; + final unit = _deepCast(raw); + final unitId = unit['wzrk_id']?.toString() ?? unit['slot_id']?.toString(); + + // Strategy 1: native_display_config key + final ndRaw = unit['native_display_config']; + if (ndRaw is Map) { + try { + return _UnitEntry(NativeDisplayConfig.fromJson(ndRaw), unitId); + } catch (e) { + _addLog('ERROR parsing native_display_config: $e'); + } + } + + // Strategy 2: custom_kv.nd_config string + final kv = unit['custom_kv']; + if (kv is Map) { + final ndStr = kv['nd_config']; + if (ndStr is String && ndStr.isNotEmpty) { + try { + return _UnitEntry( + NativeDisplayConfig.fromJson(jsonDecode(ndStr) as Map), + unitId, + ); + } catch (e) { + _addLog('ERROR parsing custom_kv.nd_config: $e'); + } + } + } + + // Strategy 3: top-level root key + if (unit.containsKey('root')) { + try { + return _UnitEntry(NativeDisplayConfig.fromJson(unit), unitId); + } catch (e) { + _addLog('ERROR parsing root-level config: $e'); + } + } + + return null; } Future _sendEvent() async { final name = _eventController.text.trim(); if (name.isEmpty) return; - await NativeDisplayBridge.pushEvent(name); - _log_('Fired event: $name'); + await CleverTapPlugin.recordEvent(name, {}); + _addLog('Fired event: $name'); _eventController.clear(); setState(() {}); } + void _addLog(String msg) { + if (!mounted) return; + setState(() => _log.add('[${_ts()}] $msg')); + } + + String _ts() { + final t = TimeOfDay.now(); + return '${t.hour.toString().padLeft(2, '0')}:${t.minute.toString().padLeft(2, '0')}'; + } + @override Widget build(BuildContext context) { return Scaffold( @@ -95,30 +150,40 @@ class _CleverTapIntegrationScreenState extends State _FireEventHeader( controller: _eventController, onSend: _sendEvent, - isSendEnabled: _eventController.text.isNotEmpty, - eventController: _eventController, onChanged: () => setState(() {}), ), - Expanded(child: _CanvasContent(units: _units, onAction: _log_)), - _EventLogFooter(messages: _log, onClear: () => setState(() => _log.clear())), + Expanded( + child: _CanvasContent( + units: _units, + onAction: _addLog, + ), + ), + _EventLogFooter( + messages: _log, + onClear: () => setState(() => _log.clear()), + ), ], ), ); } } +class _UnitEntry { + final NativeDisplayConfig config; + final String? unitId; + const _UnitEntry(this.config, this.unitId); +} + +// ── Header ──────────────────────────────────────────────────────────────────── + class _FireEventHeader extends StatelessWidget { final TextEditingController controller; final VoidCallback onSend; - final bool isSendEnabled; - final TextEditingController eventController; final VoidCallback onChanged; const _FireEventHeader({ required this.controller, required this.onSend, - required this.isSendEnabled, - required this.eventController, required this.onChanged, }); @@ -146,7 +211,7 @@ class _FireEventHeader extends StatelessWidget { ), const SizedBox(width: 8), FilledButton( - onPressed: isSendEnabled ? onSend : null, + onPressed: controller.text.isNotEmpty ? onSend : null, child: const Text('Send Event'), ), ], @@ -166,8 +231,10 @@ class _FireEventHeader extends StatelessWidget { } } +// ── Canvas ──────────────────────────────────────────────────────────────────── + class _CanvasContent extends StatelessWidget { - final List units; + final List<_UnitEntry> units; final void Function(String) onAction; const _CanvasContent({required this.units, required this.onAction}); @@ -184,17 +251,28 @@ class _CanvasContent extends StatelessWidget { } return ListView.builder( itemCount: units.length, - itemBuilder: (ctx, i) => Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: NativeDisplayView( - config: units[i], - actionListener: (action, nodeId, params) => onAction('ACTION $action on $nodeId'), - ), - ), + itemBuilder: (ctx, i) { + final entry = units[i]; + // DEBUG: outer slot shown in red, inner card in its natural state + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: NativeDisplayView( + config: entry.config, + actionListener: (action, nodeId, params) { + onAction('ACTION $action on $nodeId'); + if (entry.unitId != null) { + CleverTapPlugin.pushDisplayUnitClickedEvent(entry.unitId!); + } + }, + ), + ); + }, ); } } +// ── Event Log ───────────────────────────────────────────────────────────────── + class _EventLogFooter extends StatefulWidget { final List messages; final VoidCallback onClear; @@ -206,16 +284,16 @@ class _EventLogFooter extends StatefulWidget { } class _EventLogFooterState extends State<_EventLogFooter> { - final _scrollController = ScrollController(); + final _scroll = ScrollController(); @override void didUpdateWidget(_EventLogFooter old) { super.didUpdateWidget(old); if (widget.messages.length != old.messages.length) { WidgetsBinding.instance.addPostFrameCallback((_) { - if (_scrollController.hasClients) { - _scrollController.animateTo( - _scrollController.position.maxScrollExtent, + if (_scroll.hasClients) { + _scroll.animateTo( + _scroll.position.maxScrollExtent, duration: const Duration(milliseconds: 200), curve: Curves.easeOut, ); @@ -226,14 +304,14 @@ class _EventLogFooterState extends State<_EventLogFooter> { @override void dispose() { - _scrollController.dispose(); + _scroll.dispose(); super.dispose(); } - Color _msgColor(String msg) { - if (msg.contains('EVENT')) return const Color(0xFFFFD54F); + Color _color(String msg) { + if (msg.contains('Fired')) return const Color(0xFFFFD54F); if (msg.contains('ACTION')) return const Color(0xFF81D4FA); - if (msg.contains('ERROR')) return const Color(0xFFEF9A9A); + if (msg.contains('ERROR') || msg.contains('WARN')) return const Color(0xFFEF9A9A); if (msg.contains('Received')) return const Color(0xFFA5D6A7); return const Color(0xFF80CBC4); } @@ -252,7 +330,10 @@ class _EventLogFooterState extends State<_EventLogFooter> { children: [ const Text('Event Log', style: TextStyle(fontWeight: FontWeight.w600, fontSize: 13)), if (widget.messages.isNotEmpty) - TextButton(onPressed: widget.onClear, child: const Text('Clear', style: TextStyle(fontSize: 12))), + TextButton( + onPressed: widget.onClear, + child: const Text('Clear', style: TextStyle(fontSize: 12)), + ), ], ), Container( @@ -266,21 +347,17 @@ class _EventLogFooterState extends State<_EventLogFooter> { child: widget.messages.isEmpty ? const Text( 'No events yet', - style: TextStyle( - fontFamily: 'monospace', - fontSize: 12, - color: Color(0xFF607D8B), - ), + style: TextStyle(fontFamily: 'monospace', fontSize: 12, color: Color(0xFF607D8B)), ) : ListView.builder( - controller: _scrollController, + controller: _scroll, itemCount: widget.messages.length, itemBuilder: (ctx, i) => Text( widget.messages[i], style: TextStyle( fontFamily: 'monospace', fontSize: 11, - color: _msgColor(widget.messages[i]), + color: _color(widget.messages[i]), height: 1.4, ), ), diff --git a/flutter-sample/lib/screens/test_browser_screen.dart b/flutter-sample/lib/screens/test_browser_screen.dart index 8f04b5c..eb4fb92 100644 --- a/flutter-sample/lib/screens/test_browser_screen.dart +++ b/flutter-sample/lib/screens/test_browser_screen.dart @@ -185,6 +185,7 @@ const _testFiles = [ 'test-179-html-transparent-bg.json', 'test-180-html-scrollable-content.json', 'test-172-video-fullscreen-openurl.json', + 'test-parity-80pct-ar.json', ]; class TestBrowserScreen extends StatefulWidget { diff --git a/flutter-sample/linux/.gitignore b/flutter-sample/linux/.gitignore new file mode 100644 index 0000000..d3896c9 --- /dev/null +++ b/flutter-sample/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/flutter-sample/linux/CMakeLists.txt b/flutter-sample/linux/CMakeLists.txt new file mode 100644 index 0000000..32d9076 --- /dev/null +++ b/flutter-sample/linux/CMakeLists.txt @@ -0,0 +1,128 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "clevertap_native_display_sample") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.clevertap.flutter.clevertap_native_display_sample") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/flutter-sample/linux/flutter/CMakeLists.txt b/flutter-sample/linux/flutter/CMakeLists.txt new file mode 100644 index 0000000..d5bd016 --- /dev/null +++ b/flutter-sample/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/flutter-sample/linux/flutter/generated_plugin_registrant.cc b/flutter-sample/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..f6f23bf --- /dev/null +++ b/flutter-sample/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include + +void fl_register_plugins(FlPluginRegistry* registry) { + 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/flutter-sample/linux/flutter/generated_plugin_registrant.h b/flutter-sample/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..e0f0a47 --- /dev/null +++ b/flutter-sample/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/flutter-sample/linux/flutter/generated_plugins.cmake b/flutter-sample/linux/flutter/generated_plugins.cmake new file mode 100644 index 0000000..f16b4c3 --- /dev/null +++ b/flutter-sample/linux/flutter/generated_plugins.cmake @@ -0,0 +1,24 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + url_launcher_linux +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/flutter-sample/linux/runner/CMakeLists.txt b/flutter-sample/linux/runner/CMakeLists.txt new file mode 100644 index 0000000..e97dabc --- /dev/null +++ b/flutter-sample/linux/runner/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the application ID. +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") diff --git a/flutter-sample/linux/runner/main.cc b/flutter-sample/linux/runner/main.cc new file mode 100644 index 0000000..e7c5c54 --- /dev/null +++ b/flutter-sample/linux/runner/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/flutter-sample/linux/runner/my_application.cc b/flutter-sample/linux/runner/my_application.cc new file mode 100644 index 0000000..3d940d9 --- /dev/null +++ b/flutter-sample/linux/runner/my_application.cc @@ -0,0 +1,130 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "clevertap_native_display_sample"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "clevertap_native_display_sample"); + } + + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GApplication::startup. +static void my_application_startup(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application startup. + + G_APPLICATION_CLASS(my_application_parent_class)->startup(application); +} + +// Implements GApplication::shutdown. +static void my_application_shutdown(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application shutdown. + + G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; + G_APPLICATION_CLASS(klass)->startup = my_application_startup; + G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + // Set the program name to the application ID, which helps various systems + // like GTK and desktop environments map this running application to its + // corresponding .desktop file. This ensures better integration by allowing + // the application to be recognized beyond its binary name. + g_set_prgname(APPLICATION_ID); + + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_NON_UNIQUE, + nullptr)); +} diff --git a/flutter-sample/linux/runner/my_application.h b/flutter-sample/linux/runner/my_application.h new file mode 100644 index 0000000..72271d5 --- /dev/null +++ b/flutter-sample/linux/runner/my_application.h @@ -0,0 +1,18 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/flutter-sample/macos/.gitignore b/flutter-sample/macos/.gitignore new file mode 100644 index 0000000..746adbb --- /dev/null +++ b/flutter-sample/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/flutter-sample/macos/Flutter/Flutter-Debug.xcconfig b/flutter-sample/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 0000000..4b81f9b --- /dev/null +++ b/flutter-sample/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/flutter-sample/macos/Flutter/Flutter-Release.xcconfig b/flutter-sample/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 0000000..5caa9d1 --- /dev/null +++ b/flutter-sample/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/flutter-sample/macos/Flutter/GeneratedPluginRegistrant.swift b/flutter-sample/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 0000000..f1dbe4c --- /dev/null +++ b/flutter-sample/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,20 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import path_provider_foundation +import sqflite_darwin +import url_launcher_macos +import video_player_avfoundation +import webview_flutter_wkwebview + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) + FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) + WebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "WebViewFlutterPlugin")) +} diff --git a/flutter-sample/macos/Podfile b/flutter-sample/macos/Podfile new file mode 100644 index 0000000..29c8eb3 --- /dev/null +++ b/flutter-sample/macos/Podfile @@ -0,0 +1,42 @@ +platform :osx, '10.14' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/flutter-sample/macos/Runner.xcodeproj/project.pbxproj b/flutter-sample/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..6f8a873 --- /dev/null +++ b/flutter-sample/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,705 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* clevertap_native_display_sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "clevertap_native_display_sample.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* clevertap_native_display_sample.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* clevertap_native_display_sample.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.clevertap.flutter.clevertapNativeDisplaySample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/clevertap_native_display_sample.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/clevertap_native_display_sample"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.clevertap.flutter.clevertapNativeDisplaySample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/clevertap_native_display_sample.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/clevertap_native_display_sample"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.clevertap.flutter.clevertapNativeDisplaySample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/clevertap_native_display_sample.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/clevertap_native_display_sample"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/flutter-sample/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/flutter-sample/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..b956b50 --- /dev/null +++ b/flutter-sample/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/flutter-sample/macos/Runner/AppDelegate.swift b/flutter-sample/macos/Runner/AppDelegate.swift new file mode 100644 index 0000000..b3c1761 --- /dev/null +++ b/flutter-sample/macos/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Cocoa +import FlutterMacOS + +@main +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } +} diff --git a/flutter-sample/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/flutter-sample/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..a2ec33f --- /dev/null +++ b/flutter-sample/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/flutter-sample/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/flutter-sample/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000000000000000000000000000000000000..82b6f9d9a33e198f5747104729e1fcef999772a5 GIT binary patch literal 102994 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ⅅ8MglG&ER{i5lN315cO?EdHNLrg? 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 z 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 Pe7HEdAEg|=c=LY&YVNkY literal 0 HcmV?d00001 diff --git a/flutter-sample/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/flutter-sample/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000000000000000000000000000000000000..13b35eba55c6dabc3aac36f33d859266c18fa0d0 GIT binary patch literal 5680 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 literal 0 HcmV?d00001 diff --git a/flutter-sample/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/flutter-sample/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 0000000000000000000000000000000000000000..0a3f5fa40fb3d1e0710331a48de5d256da3f275d GIT binary patch literal 520 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uuz(rC1}QWNE&K#jR^;j87-Auq zoUlN^K{r-Q+XN;zI ze|?*NFmgt#V#GwrSWaz^2G&@SBmck6ZcIFMww~vE<1E?M2#KUn1CzsB6D2+0SuRV@ zV2kK5HvIGB{HX-hQzs0*AB%5$9RJ@a;)Ahq#p$GSP91^&hi#6sg*;a~dt}4AclK>h z_3MoPRQ{i;==;*1S-mY<(JFzhAxMI&<61&m$J0NDHdJ3tYx~j0%M-uN6Zl8~_0DOkGXc0001@sz3l12C6Xg{AT~( zm6w64BA|AX`Ve)YY-glyudNN>MAfkXz-T7`_`fEolM;0T0BA)(02-OaW z0*cW7Z~ec94o8&g0D$N>b!COu{=m}^%oXZ4?T8ZyPZuGGBPBA7pbQMoV5HYhiT?%! zcae~`(QAN4&}-=#2f5fkn!SWGWmSeCISBcS=1-U|MEoKq=k?_x3apK>9((R zuu$9X?^8?@(a{qMS%J8SJPq))v}Q-ZyDm6Gbie0m92=`YlwnQPQP1kGSm(N2UJ3P6 z^{p-u)SSCTW~c1rw;cM)-uL2{->wCn2{#%;AtCQ!m%AakVs1K#v@(*-6QavyY&v&*wO_rCJXJuq$c$7ZjsW+pJo-$L^@!7X04CvaOpPyfw|FKvu;e(&Iw>Tbg zL}#8e^?X%TReXTt>gsBByt0kSU20oQx*~P=4`&tcZ7N6t-6LiK{LxX*p6}9c<0Pu^ zLx1w_P4P2V>bX=`F%v$#{sUDdF|;rbI{p#ZW`00Bgh(eB(nOIhy8W9T>3aQ=k8Z9% zB+TusFABF~J?N~fAd}1Rme=@4+1=M{^P`~se7}e3;mY0!%#MJf!XSrUC{0uZqMAd7%q zQY#$A>q}noIB4g54Ue)x>ofVm3DKBbUmS4Z-bm7KdKsUixva)1*&z5rgAG2gxG+_x zqT-KNY4g7eM!?>==;uD9Y4iI(Hu$pl8!LrK_Zb}5nv(XKW{9R144E!cFf36p{i|8pRL~p`_^iNo z{mf7y`#hejw#^#7oKPlN_Td{psNpNnM?{7{R-ICBtYxk>?3}OTH_8WkfaTLw)ZRTfxjW+0>gMe zpKg~`Bc$Y>^VX;ks^J0oKhB#6Ukt{oQhN+o2FKGZx}~j`cQB%vVsMFnm~R_1Y&Ml? zwFfb~d|dW~UktY@?zkau>Owe zRroi(<)c4Ux&wJfY=3I=vg)uh;sL(IYY9r$WK1$F;jYqq1>xT{LCkIMb3t2jN8d`9 z=4(v-z7vHucc_fjkpS}mGC{ND+J-hc_0Ix4kT^~{-2n|;Jmn|Xf9wGudDk7bi*?^+ z7fku8z*mbkGm&xf&lmu#=b5mp{X(AwtLTf!N`7FmOmX=4xwbD=fEo8CaB1d1=$|)+ z+Dlf^GzGOdlqTO8EwO?8;r+b;gkaF^$;+#~2_YYVH!hD6r;PaWdm#V=BJ1gH9ZK_9 zrAiIC-)z)hRq6i5+$JVmR!m4P>3yJ%lH)O&wtCyum3A*})*fHODD2nq!1@M>t@Za+ zH6{(Vf>_7!I-APmpsGLYpl7jww@s5hHOj5LCQXh)YAp+y{gG(0UMm(Ur z3o3n36oFwCkn+H*GZ-c6$Y!5r3z*@z0`NrB2C^q#LkOuooUM8Oek2KBk}o1PU8&2L z4iNkb5CqJWs58aR394iCU^ImDqV;q_Pp?pl=RB2372(Io^GA^+oKguO1(x$0<7w3z z)j{vnqEB679Rz4i4t;8|&Zg77UrklxY9@GDq(ZphH6=sW`;@uIt5B?7Oi?A0-BL}(#1&R;>2aFdq+E{jsvpNHjLx2t{@g1}c~DQcPNmVmy| zNMO@ewD^+T!|!DCOf}s9dLJU}(KZy@Jc&2Nq3^;vHTs}Hgcp`cw&gd7#N}nAFe3cM1TF%vKbKSffd&~FG9y$gLyr{#to)nxz5cCASEzQ}gz8O)phtHuKOW6p z@EQF(R>j%~P63Wfosrz8p(F=D|Mff~chUGn(<=CQbSiZ{t!e zeDU-pPsLgtc#d`3PYr$i*AaT!zF#23htIG&?QfcUk+@k$LZI}v+js|yuGmE!PvAV3 ztzh90rK-0L6P}s?1QH`Ot@ilbgMBzWIs zIs6K<_NL$O4lwR%zH4oJ+}JJp-bL6~%k&p)NGDMNZX7)0kni&%^sH|T?A)`z z=adV?!qnWx^B$|LD3BaA(G=ePL1+}8iu^SnnD;VE1@VLHMVdSN9$d)R(Wk{JEOp(P zm3LtAL$b^*JsQ0W&eLaoYag~=fRRdI>#FaELCO7L>zXe6w*nxN$Iy*Q*ftHUX0+N- zU>{D_;RRVPbQ?U+$^%{lhOMKyE5>$?U1aEPist+r)b47_LehJGTu>TcgZe&J{ z{q&D{^Ps~z7|zj~rpoh2I_{gAYNoCIJmio3B}$!5vTF*h$Q*vFj~qbo%bJCCRy509 zHTdDh_HYH8Zb9`}D5;;J9fkWOQi%Y$B1!b9+ESj+B@dtAztlY2O3NE<6HFiqOF&p_ zW-K`KiY@RPSY-p9Q99}Hcd05DT79_pfb{BV7r~?9pWh=;mcKBLTen%THFPo2NN~Nf zriOtFnqx}rtO|A6k!r6 zf-z?y-UD{dT0kT9FJ`-oWuPHbo+3wBS(}?2ql(+e@VTExmfnB*liCb zmeI+v5*+W_L;&kQN^ChW{jE0Mw#0Tfs}`9bk3&7UjxP^Ke(%eJu2{VnW?tu7Iqecm zB5|=-QdzK$=h50~{X3*w4%o1FS_u(dG2s&427$lJ?6bkLet}yYXCy)u_Io1&g^c#( z-$yYmSpxz{>BL;~c+~sxJIe1$7eZI_9t`eB^Pr0)5CuA}w;;7#RvPq|H6!byRzIJG ziQ7a4y_vhj(AL`8PhIm9edCv|%TX#f50lt8+&V+D4<}IA@S@#f4xId80oH$!_!q?@ zFRGGg2mTv&@76P7aTI{)Hu%>3QS_d)pQ%g8BYi58K~m-Ov^7r8BhX7YC1D3vwz&N8{?H*_U7DI?CI)+et?q|eGu>42NJ?K4SY zD?kc>h@%4IqNYuQ8m10+8xr2HYg2qFNdJl=Tmp&ybF>1>pqVfa%SsV*BY$d6<@iJA ziyvKnZ(~F9xQNokBgMci#pnZ}Igh0@S~cYcU_2Jfuf|d3tuH?ZSSYBfM(Y3-JBsC|S9c;# zyIMkPxgrq};0T09pjj#X?W^TFCMf1-9P{)g88;NDI+S4DXe>7d3Mb~i-h&S|Jy{J< zq3736$bH?@{!amD!1Ys-X)9V=#Z={fzsjVYMX5BG6%}tkzwC#1nQLj1y1f#}8**4Y zAvDZHw8)N)8~oWC88CgzbwOrL9HFbk4}h85^ptuu7A+uc#$f^9`EWv1Vr{5+@~@Uv z#B<;-nt;)!k|fRIg;2DZ(A2M2aC65kOIov|?Mhi1Sl7YOU4c$T(DoRQIGY`ycfkn% zViHzL;E*A{`&L?GP06Foa38+QNGA zw3+Wqs(@q+H{XLJbwZzE(omw%9~LPZfYB|NF5%j%E5kr_xE0u;i?IOIchn~VjeDZ) zAqsqhP0vu2&Tbz3IgJvMpKbThC-@=nk)!|?MIPP>MggZg{cUcKsP8|N#cG5 zUXMXxcXBF9`p>09IR?x$Ry3;q@x*%}G#lnB1}r#!WL88I@uvm}X98cZ8KO&cqT1p> z+gT=IxPsq%n4GWgh-Bk8E4!~`r@t>DaQKsjDqYc&h$p~TCh8_Mck5UB84u6Jl@kUZCU9BA-S!*bf>ZotFX9?a_^y%)yH~rsAz0M5#^Di80_tgoKw(egN z`)#(MqAI&A84J#Z<|4`Co8`iY+Cv&iboMJ^f9ROUK0Lm$;-T*c;TCTED_0|qfhlcS zv;BD*$Zko#nWPL}2K8T-?4}p{u)4xon!v_(yVW8VMpxg4Kh^J6WM{IlD{s?%XRT8P|yCU`R&6gwB~ zg}{At!iWCzOH37!ytcPeC`(({ovP7M5Y@bYYMZ}P2Z3=Y_hT)4DRk}wfeIo%q*M9UvXYJq!-@Ly79m5aLD{hf@BzQB>FdQ4mw z6$@vzSKF^Gnzc9vbccii)==~9H#KW<6)Uy1wb~auBn6s`ct!ZEos`WK8e2%<00b%# zY9Nvnmj@V^K(a_38dw-S*;G-(i(ETuIwyirs?$FFW@|66a38k+a%GLmucL%Wc8qk3 z?h_4!?4Y-xt)ry)>J`SuY**fuq2>u+)VZ+_1Egzctb*xJ6+7q`K$^f~r|!i?(07CD zH!)C_uerf-AHNa?6Y61D_MjGu*|wcO+ZMOo4q2bWpvjEWK9yASk%)QhwZS%N2_F4& z16D18>e%Q1mZb`R;vW{+IUoKE`y3(7p zplg5cBB)dtf^SdLd4n60oWie|(ZjgZa6L*VKq02Aij+?Qfr#1z#fwh92aV-HGd^_w zsucG24j8b|pk>BO7k8dS86>f-jBP^Sa}SF{YNn=^NU9mLOdKcAstv&GV>r zLxKHPkFxpvE8^r@MSF6UA}cG`#yFL8;kA7ccH9D=BGBtW2;H>C`FjnF^P}(G{wU;G z!LXLCbPfsGeLCQ{Ep$^~)@?v`q(uI`CxBY44osPcq@(rR-633!qa zsyb>?v%@X+e|Mg`+kRL*(;X>^BNZz{_kw5+K;w?#pReiw7eU8_Z^hhJ&fj80XQkuU z39?-z)6Fy$I`bEiMheS(iB6uLmiMd1i)cbK*9iPpl+h4x9ch7x- z1h4H;W_G?|)i`z??KNJVwgfuAM=7&Apd3vm#AT8uzQZ!NII}}@!j)eIfn53h{NmN7 zAKG6SnKP%^k&R~m5#@_4B@V?hYyHkm>0SQ@PPiw*@Tp@UhP-?w@jW?nxXuCipMW=L zH*5l*d@+jXm0tIMP_ec6Jcy6$w(gKK@xBX8@%oPaSyG;13qkFb*LuVx3{AgIyy&n3 z@R2_DcEn|75_?-v5_o~%xEt~ONB>M~tpL!nOVBLPN&e5bn5>+7o0?Nm|EGJ5 zmUbF{u|Qn?cu5}n4@9}g(G1JxtzkKv(tqwm_?1`?YSVA2IS4WI+*(2D*wh&6MIEhw z+B+2U<&E&|YA=3>?^i6)@n1&&;WGHF-pqi_sN&^C9xoxME5UgorQ_hh1__zzR#zVC zOQt4q6>ME^iPJ37*(kg4^=EFqyKH@6HEHXy79oLj{vFqZGY?sVjk!BX^h$SFJlJnv z5uw~2jLpA)|0=tp>qG*tuLru?-u`khGG2)o{+iDx&nC}eWj3^zx|T`xn5SuR;Aw8U z`p&>dJw`F17@J8YAuW4=;leBE%qagVTG5SZdh&d)(#ZhowZ|cvWvGMMrfVsbg>_~! z19fRz8CSJdrD|Rl)w!uznBF&2-dg{>y4l+6(L(vzbLA0Bk&`=;oQQ>(M8G=3kto_) zP8HD*n4?MySO2YrG6fwSrVmnesW+D&fxjfEmp=tPd?RKLZJcH&K(-S+x)2~QZ$c(> zru?MND7_HPZJVF%wX(49H)+~!7*!I8w72v&{b={#l9yz+S_aVPc_So%iF8>$XD1q1 zFtucO=rBj0Ctmi0{njN8l@}!LX}@dwl>3yMxZ;7 z0Ff2oh8L)YuaAGOuZ5`-p%Z4H@H$;_XRJQ|&(MhO78E|nyFa158gAxG^SP(vGi^+< zChY}o(_=ci3Wta#|K6MVljNe0T$%Q5ylx-v`R)r8;3+VUpp-)7T`-Y&{Zk z*)1*2MW+_eOJtF5tCMDV`}jg-R(_IzeE9|MBKl;a7&(pCLz}5<Zf+)T7bgNUQ_!gZtMlw=8doE}#W+`Xp~1DlE=d5SPT?ymu!r4z%&#A-@x^=QfvDkfx5-jz+h zoZ1OK)2|}_+UI)i9%8sJ9X<7AA?g&_Wd7g#rttHZE;J*7!e5B^zdb%jBj&dUDg4&B zMMYrJ$Z%t!5z6=pMGuO-VF~2dwjoXY+kvR>`N7UYfIBMZGP|C7*O=tU z2Tg_xi#Q3S=1|=WRfZD;HT<1D?GMR%5kI^KWwGrC@P2@R>mDT^3qsmbBiJc21kip~ zZp<7;^w{R;JqZ)C4z-^wL=&dBYj9WJBh&rd^A^n@07qM$c+kGv^f+~mU5_*|eePF| z3wDo-qaoRjmIw<2DjMTG4$HP{z54_te_{W^gu8$r=q0JgowzgQPct2JNtWPUsjF8R zvit&V8$(;7a_m%%9TqPkCXYUp&k*MRcwr*24>hR! z$4c#E=PVE=P4MLTUBM z7#*RDe0}=B)(3cvNpOmWa*eH#2HR?NVqXdJ=hq);MGD07JIQQ7Y0#iD!$C+mk7x&B zMwkS@H%>|fmSu#+ zI!}Sb(%o29Vkp_Th>&&!k7O>Ba#Om~B_J{pT7BHHd8(Ede(l`7O#`_}19hr_?~JP9 z`q(`<)y>%)x;O7)#-wfCP{?llFMoH!)ZomgsOYFvZ1DxrlYhkWRw#E-#Qf*z@Y-EQ z1~?_=c@M4DO@8AzZ2hKvw8CgitzI9yFd&N1-{|vP#4IqYb*#S0e3hrjsEGlnc4xwk z4o!0rxpUt8j&`mJ8?+P8G{m^jbk)bo_UPM+ifW*y-A*et`#_Ja_3nYyRa9fAG1Xr5 z>#AM_@PY|*u)DGRWJihZvgEh#{*joJN28uN7;i5{kJ*Gb-TERfN{ERe_~$Es~NJCpdKLRvdj4658uYYx{ng7I<6j~w@p%F<7a(Ssib|j z51;=Py(Nu*#hnLx@w&8X%=jrADn3TW>kplnb zYbFIWWVQXN7%Cwn6KnR)kYePEBmvM45I)UJb$)ninpdYg3a5N6pm_7Q+9>!_^xy?k za8@tJ@OOs-pRAAfT>Nc2x=>sZUs2!9Dwa%TTmDggH4fq(x^MW>mcRyJINlAqK$YQCMgR8`>6=Sg$ zFnJZsA8xUBXIN3i70Q%8px@yQPMgVP=>xcPI38jNJK<=6hC={a07+n@R|$bnhB)X$ z(Zc%tadp70vBTnW{OUIjTMe38F}JIH$#A}PB&RosPyFZMD}q}5W%$rh>5#U;m`z2K zc(&WRxx7DQLM-+--^w*EWAIS%bi>h587qkwu|H=hma3T^bGD&Z!`u(RKLeNZ&pI=q$|HOcji(0P1QC!YkAp*u z3%S$kumxR}jU<@6`;*-9=5-&LYRA<~uFrwO3U0k*4|xUTp4ZY7;Zbjx|uw&BWU$zK(w55pWa~#=f$c zNDW0O68N!xCy>G}(CX=;8hJLxAKn@Aj(dbZxO8a$+L$jK8$N-h@4$i8)WqD_%Snh4 zR?{O%k}>lr>w$b$g=VP8mckcCrjnp>uQl5F_6dPM8FWRqs}h`DpfCv20uZhyY~tr8 zkAYW4#yM;*je)n=EAb(q@5BWD8b1_--m$Q-3wbh1hM{8ihq7UUQfg@)l06}y+#=$( z$x>oVYJ47zAC^>HLRE-!HitjUixP6!R98WU+h>zct7g4eD;Mj#FL*a!VW!v-@b(Jv zj@@xM5noCp5%Vk3vY{tyI#oyDV7<$`KG`tktVyC&0DqxA#>V;-3oH%NW|Q&=UQ&zU zXNIT67J4D%5R1k#bW0F}TD`hlW7b)-=-%X4;UxQ*u4bK$mTAp%y&-(?{sXF%e_VH6 zTkt(X)SSN|;8q@8XX6qfR;*$r#HbIrvOj*-5ND8RCrcw4u8D$LXm5zlj@E5<3S0R# z??=E$p{tOk96$SloZ~ARe5`J=dB|Nj?u|zy2r(-*(q^@YwZiTF@QzQyPx_l=IDKa) zqD@0?IHJqSqZ_5`)81?4^~`yiGh6>7?|dKa8!e|}5@&qV!Iu9<@G?E}Vx9EzomB3t zEbMEm$TKGwkHDpirp;FZD#6P5qIlQJ8}rf;lHoz#h4TFFPYmS3+8(13_Mx2`?^=8S z|0)0&dQLJTU6{b%*yrpQe#OKKCrL8}YKw+<#|m`SkgeoN69TzIBQOl_Yg)W*w?NW) z*WxhEp$zQBBazJSE6ygu@O^!@Fr46j=|K`Mmb~xbggw7<)BuC@cT@Bwb^k?o-A zKX^9AyqR?zBtW5UA#siILztgOp?r4qgC`9jYJG_fxlsVSugGprremg-W(K0{O!Nw-DN%=FYCyfYA3&p*K>+|Q}s4rx#CQK zNj^U;sLM#q8}#|PeC$p&jAjqMu(lkp-_50Y&n=qF9`a3`Pr9f;b`-~YZ+Bb0r~c+V z*JJ&|^T{}IHkwjNAaM^V*IQ;rk^hnnA@~?YL}7~^St}XfHf6OMMCd9!vhk#gRA*{L zp?&63axj|Si%^NW05#87zpU_>QpFNb+I00v@cHwvdBn+Un)n2Egdt~LcWOeBW4Okm zD$-e~RD+W|UB;KQ;a7GOU&%p*efGu2$@wR74+&iP8|6#_fmnh^WcJLs)rtz{46);F z4v0OL{ZP9550>2%FE(;SbM*#sqMl*UXOb>ch`fJ|(*bOZ9=EB1+V4fkQ)hjsm3-u^Pk-4ji_uDDHdD>84tER!MvbH`*tG zzvbhBR@}Yd`azQGavooV=<WbvWLlO#x`hyO34mKcxrGv=`{ssnP=0Be5#1B;Co9 zh{TR>tjW2Ny$ZxJpYeg57#0`GP#jxDCU0!H15nL@@G*HLQcRdcsUO3sO9xvtmUcc{F*>FQZcZ5bgwaS^k-j5mmt zI7Z{Xnoml|A(&_{imAjK!kf5>g(oDqDI4C{;Bv162k8sFNr;!qPa2LPh>=1n z=^_9)TsLDvTqK7&*Vfm5k;VXjBW^qN3Tl&}K=X5)oXJs$z3gk0_+7`mJvz{pK|FVs zHw!k&7xVjvY;|(Py<;J{)b#Yjj*LZO7x|~pO4^MJ2LqK3X;Irb%nf}L|gck zE#55_BNsy6m+W{e zo!P59DDo*s@VIi+S|v93PwY6d?CE=S&!JLXwE9{i)DMO*_X90;n2*mPDrL%{iqN!?%-_95J^L z=l<*{em(6|h7DR4+4G3Wr;4*}yrBkbe3}=p7sOW1xj!EZVKSMSd;QPw>uhKK z#>MlS@RB@-`ULv|#zI5GytO{=zp*R__uK~R6&p$q{Y{iNkg61yAgB8C^oy&``{~FK z8hE}H&nIihSozKrOONe5Hu?0Zy04U#0$fB7C6y~?8{or}KNvP)an=QP&W80mj&8WL zEZQF&*FhoMMG6tOjeiCIV;T{I>jhi9hiUwz?bkX3NS-k5eWKy)Mo_orMEg4sV6R6X&i-Q%JG;Esl+kLpn@Bsls9O|i9z`tKB^~1D5)RIBB&J<6T@a4$pUvh$IR$%ubH)joi z!7>ON0DPwx=>0DA>Bb^c?L8N0BBrMl#oDB+GOXJh;Y&6I)#GRy$W5xK%a;KS8BrER zX)M>Rdoc*bqP*L9DDA3lF%U8Yzb6RyIsW@}IKq^i7v&{LeIc=*ZHIbO68x=d=+0T( zev=DT9f|x!IWZNTB#N7}V4;9#V$%Wo0%g>*!MdLOEU>My0^gni9ocID{$g9ytD!gy zKRWT`DVN(lcYjR|(}f0?zgBa3SwunLfAhx><%u0uFkrdyqlh8_g zDKt#R6rA2(Vm2LW_>3lBNYKG_F{TEnnKWGGC15y&OebIRhFL4TeMR*v9i0wPoK#H< zu4){s4K&K)K(9~jgGm;H7lS7y_RYfS;&!Oj5*eqbvEcW^a*i67nevzOZxN6F+K~A%TYEtsAVsR z@J=1hc#Dgs7J2^FL|qV&#WBFQyDtEQ2kPO7m2`)WFhqAob)Y>@{crkil6w9VoA?M6 zADGq*#-hyEVhDG5MQj677XmcWY1_-UO40QEP&+D)rZoYv^1B_^w7zAvWGw&pQyCyx zD|ga$w!ODOxxGf_Qq%V9Z7Q2pFiUOIK818AGeZ-~*R zI1O|SSc=3Z?#61Rd|AXx2)K|F@Z1@x!hBBMhAqiU)J=U|Y)T$h3D?ZPPQgkSosnN! zIqw-t$0fqsOlgw3TlHJF*t$Q@bg$9}A3X=cS@-yU3_vNG_!#9}7=q7!LZ?-%U26W4 z$d>_}*s1>Ac%3uFR;tnl*fNlylJ)}r2^Q3&@+is3BIv<}x>-^_ng;jhdaM}6Sg3?p z0jS|b%QyScy3OQ(V*~l~bK>VC{9@FMuW_JUZO?y(V?LKWD6(MXzh}M3r3{7b4eB(#`(q1m{>Be%_<9jw8HO!x#yF6vez$c#kR+}s zZO-_;25Sxngd(}){zv?ccbLqRAlo;yog>4LH&uZUK1n>x?u49C)Y&2evH5Zgt~666 z_2_z|H5AO5Iqxv_Bn~*y1qzRPcob<+Otod5Xd2&z=C;u+F}zBB@b^UdGdUz|s!H}M zXG%KiLzn3G?FZgdY&3pV$nSeY?ZbU^jhLz9!t0K?ep}EFNqR1@E!f*n>x*!uO*~JF zW9UXWrVgbX1n#76_;&0S7z}(5n-bqnII}_iDsNqfmye@)kRk`w~1 z6j4h4BxcPe6}v)xGm%=z2#tB#^KwbgMTl2I*$9eY|EWAHFc3tO48Xo5rW z5oHD!G4kb?MdrOHV=A+8ThlIqL8Uu+7{G@ zb)cGBm|S^Eh5= z^E^SZ=yeC;6nNCdztw&TdnIz}^Of@Ke*@vjt)0g>Y!4AJvWiL~e7+9#Ibhe)> ziNwh>gWZL@FlWc)wzihocz+%+@*euwXhW%Hb>l7tf8aJe5_ZSH1w-uG|B;9qpcBP0 zM`r1Hu#htOl)4Cl1c7oY^t0e4Jh$-I(}M5kzWqh{F=g&IM#JiC`NDSd@BCKX#y<P@Gwl$3a3w z6<(b|K(X5FIR22M)sy$4jY*F4tT{?wZRI+KkZFb<@j@_C316lu1hq2hA|1wCmR+S@ zRN)YNNE{}i_H`_h&VUT5=Y(lN%m?%QX;6$*1P}K-PcPx>*S55v)qZ@r&Vcic-sjkm z! z=nfW&X`}iAqa_H$H%z3Tyz5&P3%+;93_0b;zxLs)t#B|up}JyV$W4~`8E@+BHQ+!y zuIo-jW!~)MN$2eHwyx-{fyGjAWJ(l8TZtUp?wZWBZ%}krT{f*^fqUh+ywHifw)_F> zp76_kj_B&zFmv$FsPm|L7%x-j!WP>_P6dHnUTv!9ZWrrmAUteBa`rT7$2ixO;ga8U z3!91micm}{!Btk+I%pMgcKs?H4`i+=w0@Ws-CS&n^=2hFTQ#QeOmSz6ttIkzmh^`A zYPq)G1l3h(E$mkyr{mvz*MP`x+PULBn%CDhltKkNo6Uqg!vJ#DA@BIYr9TQ`18Un2 zv$}BYzOQuay9}w(?JV63F$H6WmlYPPpH=R|CPb%C@BCv|&Q|&IcW7*LX?Q%epS z`=CPx{1HnJ9_46^=0VmNb>8JvMw-@&+V8SDLRYsa>hZXEeRbtf5eJ>0@Ds47zIY{N z42EOP9J8G@MXXdeiPx#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91AfN*P1ONa40RR91AOHXW0IY^$^8f$?lu1NER9Fe^SItioK@|V(ZWmgL zZT;XwPgVuWM>O%^|Dc$VK;n&?9!&g5)aVsG8cjs5UbtxVVnQNOV~7Mrg3+jnU;rhE z6fhW6P)R>_eXrXo-RW*y6RQ_qcb^s1wTu$TwriZ`=JUws>vRi}5x}MW1MR#7p|gIWJlaLK;~xaN}b< z<-@=RX-%1mt`^O0o^~2=CD7pJ<<$Rp-oUL-7PuG>do^5W_Mk#unlP}6I@6NPxY`Q} zuXJF}!0l)vwPNAW;@5DjPRj?*rZxl zwn;A(cFV!xe^CUu+6SrN?xe#mz?&%N9QHf~=KyK%DoB8HKC)=w=3E?1Bqj9RMJs3U z5am3Uv`@+{jgqO^f}Lx_Jp~CoP3N4AMZr~4&d)T`R?`(M{W5WWJV^z~2B|-oih@h^ zD#DuzGbl(P5>()u*YGo*Och=oRr~3P1wOlKqI)udc$|)(bacG5>~p(y>?{JD7nQf_ z*`T^YL06-O>T(s$bi5v~_fWMfnE7Vn%2*tqV|?~m;wSJEVGkNMD>+xCu#um(7}0so zSEu7?_=Q64Q5D+fz~T=Rr=G_!L*P|(-iOK*@X8r{-?oBlnxMNNgCVCN9Y~ocu+?XA zjjovJ9F1W$Nf!{AEv%W~8oahwM}4Ruc+SLs>_I_*uBxdcn1gQ^2F8a*vGjgAXYyh? zWCE@c5R=tbD(F4nL9NS?$PN1V_2*WR?gjv3)4MQeizuH`;sqrhgykEzj z593&TGlm3h`sIXy_U<7(dpRXGgp0TB{>s?}D{fwLe>IV~exweOfH!qM@CV5kib!YA z6O0gvJi_0J8IdEvyP#;PtqP*=;$iI2t(xG2YI-e!)~kaUn~b{6(&n zp)?iJ`z2)Xh%sCV@BkU`XL%_|FnCA?cVv@h*-FOZhY5erbGh)%Q!Av#fJM3Csc_g zC2I6x%$)80`Tkz#KRA!h1FzY`?0es3t!rKDT5EjPe6B=BLPr7s0GW!if;Ip^!AmGW zL;$`Vdre+|FA!I4r6)keFvAx3M#1`}ijBHDzy)3t0gwjl|qC2YB`SSxFKHr(oY#H$)x{L$LL zBdLKTlsOrmb>T0wd=&6l3+_Te>1!j0OU8%b%N342^opKmT)gni(wV($s(>V-fUv@0p8!f`=>PxC|9=nu ze{ToBBj8b<{PLfXV$h8YPgA~E!_sF9bl;QOF{o6t&JdsX?}rW!_&d`#wlB6T_h;Xf zl{4Tz5>qjF4kZgjO7ZiLPRz_~U@k5%?=30+nxEh9?s78gZ07YHB`FV`4%hlQlMJe@J`+e(qzy+h(9yY^ckv_* zb_E6o4p)ZaWfraIoB2)U7_@l(J0O%jm+Or>8}zSSTkM$ASG^w3F|I? z$+eHt7T~04(_WfKh27zqS$6* zzyy-ZyqvSIZ0!kkSvHknm_P*{5TKLQs8S6M=ONuKAUJWtpxbL#2(_huvY(v~Y%%#~ zYgsq$JbLLprKkV)32`liIT$KKEqs$iYxjFlHiRNvBhxbDg*3@Qefw4UM$>i${R5uB zhvTgmqQsKA{vrKN;TSJU2$f9q=y{$oH{<)woSeV>fkIz6D8@KB zf4M%v%f5U2?<8B(xn}xV+gWP?t&oiapJhJbfa;agtz-YM7=hrSuxl8lAc3GgFna#7 zNjX7;`d?oD`#AK+fQ=ZXqfIZFEk{ApzjJF0=yO~Yj{7oQfXl+6v!wNnoqwEvrs81a zGC?yXeSD2NV!ejp{LdZGEtd1TJ)3g{P6j#2jLR`cpo;YX}~_gU&Gd<+~SUJVh+$7S%`zLy^QqndN<_9 zrLwnXrLvW+ew9zX2)5qw7)zIYawgMrh`{_|(nx%u-ur1B7YcLp&WFa24gAuw~& zKJD3~^`Vp_SR$WGGBaMnttT)#fCc^+P$@UHIyBu+TRJWbcw4`CYL@SVGh!X&y%!x~ zaO*m-bTadEcEL6V6*{>irB8qT5Tqd54TC4`h`PVcd^AM6^Qf=GS->x%N70SY-u?qr>o2*OV7LQ=j)pQGv%4~z zz?X;qv*l$QSNjOuQZ>&WZs2^@G^Qas`T8iM{b19dS>DaXX~=jd4B2u`P;B}JjRBi# z_a@&Z5ev1-VphmKlZEZZd2-Lsw!+1S60YwW6@>+NQ=E5PZ+OUEXjgUaXL-E0fo(E* zsjQ{s>n33o#VZm0e%H{`KJi@2ghl8g>a~`?mFjw+$zlt|VJhSU@Y%0TWs>cnD&61fW4e0vFSaXZa4-c}U{4QR8U z;GV3^@(?Dk5uc@RT|+5C8-24->1snH6-?(nwXSnPcLn#X_}y3XS)MI_?zQ$ZAuyg+ z-pjqsw}|hg{$~f0FzmmbZzFC0He_*Vx|_uLc!Ffeb8#+@m#Z^AYcWcZF(^Os8&Z4g zG)y{$_pgrv#=_rV^D|Y<_b@ICleUv>c<0HzJDOsgJb#Rd-Vt@+EBDPyq7dUM9O{Yp zuGUrO?ma2wpuJuwl1M=*+tb|qx7Doj?!F-3Z>Dq_ihFP=d@_JO;vF{iu-6MWYn#=2 zRX6W=`Q`q-+q@Db|6_a1#8B|#%hskH82lS|9`im0UOJn?N#S;Y0$%xZw3*jR(1h5s z?-7D1tnIafviko>q6$UyqVDq1o@cwyCb*})l~x<@s$5D6N=-Uo1yc49p)xMzxwnuZ zHt!(hu-Ek;Fv4MyNTgbW%rPF*dB=;@r3YnrlFV{#-*gKS_qA(G-~TAlZ@Ti~Yxw;k za1EYyX_Up|`rpbZ0&Iv#$;eC|c0r4XGaQ-1mw@M_4p3vKIIpKs49a8Ns#ni)G314Z z8$Ei?AhiT5dQGWUYdCS|IC7r z=-8ol>V?u!n%F*J^^PZ(ONT&$Ph;r6X;pj|03HlDY6r~0g~X#zuzVU%a&!fs_f|m?qYvg^Z{y?9Qh7Rn?T*F%7lUtA6U&={HzhYEzA`knx1VH> z{tqv?p@I(&ObD5L4|YJV$QM>Nh-X3cx{I&!$FoPC_2iIEJfPk-$;4wz>adRu@n`_y z_R6aN|MDHdK;+IJmyw(hMoDCFCQ(6?hCAG5&7p{y->0Uckv# zvooVuu04$+pqof777ftk<#42@KQ((5DPcSMQyzGOJ{e9H$a9<2Qi_oHjl{#=FUL9d z+~0^2`tcvmp0hENwfHR`Ce|<1S@p;MNGInXCtHnrDPXCKmMTZQ{HVm_cZ>@?Wa6}O zHsJc7wE)mc@1OR2DWY%ZIPK1J2p6XDO$ar`$RXkbW}=@rFZ(t85AS>>U0!yt9f49^ zA9@pc0P#k;>+o5bJfx0t)Lq#v4`OcQn~av__dZ-RYOYu}F#pdsl31C^+Qgro}$q~5A<*c|kypzd} ziYGZ~?}5o`S5lw^B{O@laad9M_DuJle- z*9C7o=CJh#QL=V^sFlJ0c?BaB#4bV^T(DS6&Ne&DBM_3E$S^S13qC$7_Z?GYXTpR@wqr70wu$7+qvf-SEUa5mdHvFbu^7ew!Z1a^ zo}xKOuT*gtGws-a{Tx}{#(>G~Y_h&5P@Q8&p!{*s37^QX_Ibx<6XU*AtDOIvk|^{~ zPlS}&DM5$Ffyu-T&0|KS;Wnaqw{9DB&B3}vcO14wn;)O_e@2*9B&0I_ zZz{}CMxx`hv-XouY>^$Y@J(_INeM>lIQI@I>dBAqq1)}?Xmx(qRuX^i4IV%=MF306 z9g)i*79pP%_7Ex?m6ag-4Tlm=Z;?DQDyC-NpUIb#_^~V_tsL<~5<&;Gf2N+p?(msn zzUD~g>OoW@O}y0@Z;RN)wjam`CipmT&O7a|YljZqU=U86 zedayEdY)2F#BJ6xvmW8K&ffdS*0!%N<%RB!2~PAT4AD*$W7yzHbX#Eja9%3aD+Ah2 zf#T;XJW-GMxpE=d4Y>}jE=#U`IqgSoWcuvgaWQ9j1CKzG zDkoMDDT)B;Byl3R2PtC`ip=yGybfzmVNEx{xi_1|Cbqj>=FxQc{g`xj6fIfy`D8fA z##!-H_e6o0>6Su&$H2kQTujtbtyNFeKc}2=|4IfLTnye#@$Au7Kv4)dnA;-fz@D_8 z)>irG$)dkBY~zX zC!ZXLy*L3xr6cb70QqfN#Q>lFIc<>}>la4@3%7#>a1$PU&O^&VszpxLC%*!m-cO{B z-Y}rQr4$84(hvy#R69H{H zJ*O#uJh)TF6fbXy;fZkk%X=CjsTK}o5N1a`d7kgYYZLPxsHx%9*_XN8VWXEkVJZ%A z1A+5(B;0^{T4aPYr8%i@i32h)_)|q?9vws)r+=5u)1YNftF5mknwfd*%jXA2TeP}Z zQ!m?xJ3?9LpPM?_A3$hQ1QxNbR&}^m z!F999s?p^ak#C4NM_x2p9FoXWJ$>r?lJ)2bG)sX{gExgLA2s5RwHV!h6!C~d_H||J z>9{E{mEv{Z1z~65Vix@dqM4ZqiU|!)eWX$mwS5mLSufxbpBqqS!jShq1bmwCR6 z4uBri7ezMeS6ycaXPVu(i2up$L; zjpMtB`k~WaNrdgM_R=e#SN?Oa*u%nQy01?()h4A(jyfeNfx;5o+kX?maO4#1A^L}0 zYNyIh@QVXIFiS0*tE}2SWTrWNP3pH}1Vz1;E{@JbbgDFM-_Mky^7gH}LEhl~Ve5PexgbIyZ(IN%PqcaV@*_`ZFb=`EjspSz%5m2E34BVT)d=LGyHVz@-e%9Ova*{5@RD;7=Ebkc2GP%pIP^P7KzKapnh`UpH?@h z$RBpD*{b?vhohOKf-JG3?A|AX|2pQ?(>dwIbWhZ38GbTm4AImRNdv_&<99ySX;kJ| zo|5YgbHZC#HYgjBZrvGAT4NZYbp}qkVSa;C-LGsR26Co+i_HM&{awuO9l)Ml{G8zD zs$M8R`r+>PT#Rg!J(K6T4xHq7+tscU(}N$HY;Yz*cUObX7J7h0#u)S7b~t^Oj}TBF zuzsugnst;F#^1jm>22*AC$heublWtaQyM6RuaquFd8V#hJ60Z3j7@bAs&?dD#*>H0SJaDwp%U~27>zdtn+ z|8sZzklZy$%S|+^ie&P6++>zbrq&?+{Yy11Y>@_ce@vU4ZulS@6yziG6;iu3Iu`M= zf3rcWG<+3F`K|*(`0mE<$89F@jSq;j=W#E>(R}2drCB7D*0-|D;S;(;TwzIJkGs|q z2qH{m_zZ+el`b;Bv-#bQ>}*VPYC|7`rgBFf2oivXS^>v<&HHTypvd4|-zn|=h=TG{ z05TH2+{T%EnADO>3i|CB zCu60#qk`}GW{n4l-E$VrqgZGbI zbQW690KgZt4U3F^5@bdO1!xu~p@7Y~*_FfWg2CdvED5P5#w#V46LH`<&V0{t&Ml~4 zHNi7lIa+#i+^Z6EnxO7KJQw)wD)4~&S-Ki8)3=jpqxmx6c&zU&<&h%*c$I(5{1HZT zc9WE}ijcWJiVa^Q^xC|WX0habl89qycOyeViIbi(LFsEY_8a|+X^+%Qv+W4vzj>`y zpuRnjc-eHNkvXvI_f{=*FX=OKQzT?bck#2*qoKTHmDe>CDb&3AngA1O)1b}QJ1Tun z_<@yVEM>qG7664Pa@dzL@;DEh`#?yM+M|_fQS<7yv|i*pw)|Z8)9IR+QB7N3v3K(wv4OY*TXnH&X0nQB}?|h2XQeGL^q~N7N zDFa@x0E(UyN7k9g%IFq7Sf+EAfE#K%%#`)!90_)Dmy3Bll&e1vHQyPA87TaF(xbqMpDntVp?;8*$87STop$!EAnGhZ?>mqPJ(X zFsr336p3P{PpZCGn&^LP(JjnBbl_3P3Kcq+m}xVFMVr1zdCPJMDIV_ki#c=vvTwbU z*gKtfic&{<5ozL6Vfpx>o2Tts?3fkhWnJD&^$&+Mh5WGGyO7fG@6WDE`tEe(8<;+q z@Ld~g08XDzF8xtmpIj`#q^(Ty{Hq>t*v`pedHnuj(0%L(%sjkwp%s}wMd!a<*L~9T z9MM@s)Km~ogxlqEhIw5(lc46gCPsSosUFsgGDr8H{mj%OzJz{N#;bQ;KkV+ZWA1(9 zu0PXzyh+C<4OBYQ0v3z~Lr;=C@qmt8===Ov2lJ1=DeLfq*#jgT{YQCuwz?j{&3o_6 zsqp2Z_q-YWJg?C6=!Or|b@(zxTlg$ng2eUQzuC<+o)k<6^9ju_Z*#x+oioZ5T8Z_L zz9^A1h2eFS0O5muq8;LuDKwOv4A9pxmOjgb6L*i!-(0`Ie^d5Fsgspon%X|7 zC{RRXEmYn!5zP9XjG*{pLa)!2;PJB2<-tH@R7+E1cRo=Wz_5Ko8h8bB$QU%t9#vol zAoq?C$~~AsYC|AQQ)>>7BJ@{Cal)ZpqE=gjT+Juf!RD-;U0mbV1ED5PbvFD6M=qj1 zZ{QERT5@(&LQ~1X9xSf&@%r|3`S#ZCE=sWD`D4YQZ`MR`G&s>lN{y2+HqCfvgcw3E z-}Kp(dfGG?V|97kAHQX+OcKCZS`Q%}HD6u*e$~Ki&Vx53&FC!x94xJd4F2l^qQeFO z?&JdmgrdVjroKNJx64C!H&Vncr^w zzR#XI}Dn&o8jB~_YlVM^+#0W(G1LZH5K^|uYT@KSR z^Y5>^*Bc45E1({~EJB(t@4n9gb-eT#s@@7)J^^<_VV`Pm!h7av8XH6^5zO zOcQBhTGr;|MbRsgxCW69w{bl4EW#A~);L?d4*y#j8Ne=Z@fmJP0k4{_cQ~KA|Y#_#BuUiYx8y*za3_6Y}c=GSe7(2|KAfhdzud!Zq&}j)=o4 z7R|&&oX7~e@~HmyOOsCCwy`AR+deNjZ3bf6ijI_*tKP*_5JP3;0d;L_p(c>W1b%sG zJ*$wcO$ng^aW0E(5ldckV9unU7}OB7s?Wx(761?1^&8tA5y0_(ieV>(x-e@}1`lWC z-YH~G$D>#ud!SxK2_Iw{K%92=+{4yb-_XC>ji&j7)1ofp(OGa4jjF;Hd*`6YQL+Jf zffg+6CPc8F@EDPN{Kn96yip;?g@)qgkPo^nVKFqY?8!=h$G$V=<>%5J&iVjwR!7H0 z$@QL|_Q81I;Bnq8-5JyNRv$Y>`sWl{qhq>u+X|)@cMlsG!{*lu?*H`Tp|!uv z9oEPU1jUEj@ueBr}%Y)7Luyi)REaJV>eQ{+uy4uh0ep0){t;OU8D*RZ& zE-Z-&=BrWQLAD^A&qut&4{ZfhqK1ZQB0fACP)=zgx(0(o-`U62EzTkBkG@mXqbjXm z>w`HNeQM?Is&4xq@BB(K;wv5nI6EXas)XXAkUuf}5uSrZLYxRCQPefn-1^#OCd4aO zzF=dQ*CREEyWf@n6h7(uXLNgJIwGp#Xrsj6S<^bzQ7N0B0N{XlT;`=m9Olg<>KL}9 zlp>EKTx-h|%d1Ncqa=wnQEuE;sIO-f#%Bs?g4}&xS?$9MG?n$isHky0caj za8W+B^ERK#&h?(x)7LLpOqApV5F>sqB`sntV%SV>Q1;ax67qs+WcssfFeF3Xk=e4^ zjR2^(%K1oBq%0%Rf!y&WT;lu2Co(rHi|r1_uW)n{<7fGc-c=ft7Z0Q}r4W$o$@tQF#i?jDBwZ8h+=SC}3?anUp3mtRVv9l#H?-UD;HjTF zQ*>|}e=6gDrgI9p%c&4iMUkQa4zziS$bO&i#DI$Wu$7dz7-}XLk%!US^XUIFf2obO zFCTjVEtkvYSKWB;<0C;_B{HHs~ax_48^Cml*mjfBC5*7^HJZiLDir(3k&BerVIZF8zF;0q80eX8c zPN4tc+Dc5DqEAq$Y3B3R&XPZ=AQfFMXv#!RQnGecJONe0H;+!f^h5x0wS<+%;D}MpUbTNUBA}S2n&U59-_5HKr{L^jPsV8B^%NaH|tUr)mq=qCBv_- ziZ1xUp(ZzxUYTCF@C}To;u60?RIfTGS?#JnB8S8@j`TKPkAa)$My+6ziGaBcA@){d z91)%+v2_ba7gNecdj^8*I4#<11l!{XKl6s0zkXfJPxhP+@b+5ev{a>p*W-3*25c&} zmCf{g9mPWVQ$?Sp*4V|lT@~>RR)9iNdN^7KT@>*MU3&v^3e?=NTbG9!h6C|9zO097 zN{Qs6YwR-5$)~ z`b~qs`a1Dbx8P>%V=1XGjBptMf%P~sl1qbHVm1HYpY|-Z^Dar8^HqjIw}xaeRlsYa zJ_@Apy-??`gxPmb`m`0`z`#G7*_C}qiSZe~l2z65tE~IwMw$1|-u&t|z-8SxliH00 zlh1#kuqB56s+E&PWQ7Nz17?c}pN+A@-c^xLqh(j;mS|?>(Pf7(?qd z5q@jkc^nA&!K-}-1P=Ry0yyze0W!+h^iW}7jzC1{?|rEFFWbE^Yu7Y}t?jmP-D$f+ zmqFT7nTl0HL|4jwGm7w@a>9 zKD)V~+g~ysmei$OT5}%$&LK8?ib|8aY|>W3;P+0B;=oD=?1rg+PxKcP(d;OEzq1CKA&y#boc51P^ZJPPS)z5 zAZ)dd2$glGQXFj$`XBBJyl2y-aoBA8121JC9&~|_nY>nkmW>TLi%mWdn-^Jks-Jv| zSR*wij;A3Fcy8KsDjQ15?Z9oOj|Qw2;jgJiq>dxG(2I2RE- z$As!#zSFIskebqU2bnoM^N<4VWD2#>!;saPSsY8OaCCQqkCMdje$C?Sp%V}f2~tG5 z0whMYk6tcaABwu*x)ak@n4sMElGPX1_lmv@bgdI2jPdD|2-<~Jf`L`@>Lj7{<-uLQ zE3S_#3e10q-ra=vaDQ42QUY^@edh>tnTtpBiiDVUk5+Po@%RmuTntOlE29I4MeJI?;`7;{3e4Qst#i-RH6s;>e(Sc+ubF2_gwf5Qi%P!aa89fx6^{~A*&B4Q zKTF|Kx^NkiWx=RDhe<{PWXMQ;2)=SC=yZC&mh?T&CvFVz?5cW~ritRjG2?I0Av_cI z)=s!@MXpXbarYm>Kj0wOxl=eFMgSMc?62U#2gM^li@wKPK9^;;0_h7B>F>0>I3P`{ zr^ygPYp~WVm?Qbp6O3*O2)(`y)x>%ZXtztz zMAcwKDr=TCMY!S-MJ8|2MJCVNUBI0BkJV6?(!~W!_dC{TS=eh}t#X+2D>Kp&)ZN~q zvg!ogxUXu^y(P*;Q+y_rDoGeSCYxkaGPldDDx)k;ocJvvGO#1YKoQLHUf2h_pjm&1 zqh&!_KFH03FcJvSdfgUYMp=5EpigZ*8}7N_W%Ms^WSQ4hH`9>3061OEcxmf~TcYn5_oHtscWn zo5!ayj<_fZ)vHu3!A!7M;4y1QIr8YGy$P2qDD_4+T8^=^dB6uNsz|D>p~4pF3Nrb6 zcpRK*($<~JUqOya#M1=#IhOZ zG)W+rJS-x(6EoVz)P zsSo>JtnChdj9^);su%SkFG~_7JPM zEDz3gk2T7Y%x>1tWyia|op(ilEzvAujW?Xwlw>J6d7yEi8E zv30riR|a_MM%ZZX&n!qm0{2agq(s?x9E@=*tyT$nND+{Djpm7Rsy!+c$j+wqMwTOF zZL8BQ|I`<^bGW)5apO{lh(Asqen?_U`$_n0-Ob~Yd%^89oEe%9yGumQ_8Be+l2k+n zCxT%s?bMpv|AdWP7M1LQwLm|x+igA~;+iK-*+tClF&ueX_V}>=4gvZ01xpubQWXD_ zi?Un>&3=$fu)dgk-Z;0Ll}HK5_YM->l^Czrd0^cJ))(DwL2g3aZuza7ga9^|mT_70 z))}A}r1#-(9cxtn<9jGRwOB4hb9kK@YCgjfOM-90I$8@l=H^`K$cyhe2mTM|FY9vW znH~h)I<_aa#V1xmhk?Ng@$Jw-s%a!$BI4Us+Df+?J&gKAF-M`v}j`OWKP3>6`X`tEmhe#y*(Xm$_^Ybbs=%;L7h zp7q^C*qM}Krqsinq|WolR99>_!GL#Z71Hhz|IwQQv<>Ds09B?Je(lhI1(FInO8mc} zl$RyKCUmfku+Cd^8s0|t+e}5g7M{ZPJQH=UB3(~U&(w#Bz#@DTDHy>_UaS~AtN>4O zJ-I#U@R($fgupHebcpuEBX`SZ>kN!rW$#9>s{^3`86ZRQRtYTY)hiFm_9wU3c`SC8 z-5M%g)h}3Pt|wyj#F%}pGC@VL`9&>9P+_UbudCkS%y2w&*o})hBplrB*@Z?gel5q+ z%|*59(sR9GMk3xME}wd%&k?7~J)OL`rK#4d-haC7uaU8-L@?$K6(r<0e<;y83rK&` z3Q!1rD9WkcB8WBQ|WT|$u^lkr0UL4WH4EQTJyk@5gzHb18cOte4w zS`fLv8q;PvAZyY;*Go3Qw1~5#gP0D0ERla6M6#{; zr1l?bR}Nh+OC7)4bfAs(0ZD(axaw6j9v`^jh5>*Eo&$dAnt?c|Y*ckEORIiJXfGcM zEo`bmIq6rJm`XhkXR-^3d8^RTK2;nmVetHfUNugJG(4XLOu>HJA;0EWb~?&|0abr6 zxqVp@p=b3MN^|~?djPe!=eex(u!x>RYFAj|*T$cTi*Sd3Bme7Pri1tkK9N`KtRmXf zZYNBNtik97ct1R^vamQBfo9ZUR@k*LhIg8OR9d_{iv#t)LQV91^5}K5u{eyxwOFoU zHMVq$C>tfa@uNDW^_>EmO~WYQd(@!nKmAvSSIb&hPO|}g-3985t?|R&WZXvxS}Kt2i^eRe>WHb_;-K5cM4=@AN1>E&1c$k!w4O*oscx(f=<1K6l#8Exi)U(ZiZ zdr#YTP6?m1e1dOKysUjQ^>-MR={OuD00g6+(a^cvcmn#A_%Fh3Of%(qP5nvjS1=(> z|Ld8{u%(J}%2SY~+$4pjy{()5HN2MYUjg1X9umxOMFFPdM+IwOVEs4Z(olynvT%G) zt9|#VR}%O2@f6=+6uvbZv{3U)l;C{tuc zZ{K$rut=eS%3_~fQv^@$HV6#9)K9>|0qD$EV2$G^XUNBLM|5-ZmFF!KV)$4l^KVj@ zZ4fI}Knv*K%zPqK77}B-h_V{66VrmoZP2>@^euu8Rc}#qwRwt5uEBWcJJE5*5rT2t zA4Jpx`QQ~1Sh_n_a9x%Il!t1&B~J6p54zxAJx`REov${jeuL8h8x-z=?qwMAmPK5i z_*ES)BW(NZluu#Bmn1-NUKQip_X&_WzJy~J`WYxEJQ&Gu7DD< z&F9urE;}8S{x4{yB zaq~1Zrz%8)<`prSQv$eu5@1RY2WLu=waPTrn`WK%;G5(jt^FeM;gOdvXQjYhax~_> z{bS_`;t#$RYMu-;_Dd&o+LD<5Afg6v{NK?0d8dD5ohAN?QoocETBj?y{MB)jQ%UQ}#t3j&iL!qr@#6JEajR3@^k5wgLfI9S9dT2^f`2wd z%I#Q*@Ctk@w=(u)@QC}yBvUP&fFRR-uYKJ){Wp3&$s(o~W7OzgsUIPx0|ph2L1(r*_Pa@T@mcH^JxBjh09#fgo|W#gG7}|)k&uD1iZxb0 z@|Y)W79SKj9sS&EhmTD;uI#)FE6VwQ*YAr&foK$RI5H8_ripb$^=;U%gWbrrk4!5P zXDcyscEZoSH~n6VJu8$^6LE6)>+=o#Q-~*jmob^@191+Ot1w454e3)WMliLtY6~^w zW|n#R@~{5K#P+(w+XC%(+UcOrk|yzkEes=!qW%imu6>zjdb!B#`efaliKtN}_c!Jp zfyZa`n+Nx8;*AquvMT2;c8fnYszdDA*0(R`bsof1W<#O{v%O!1IO4WZe=>XBu_D%d zOwWDaEtX%@B>4V%f1+dKqcXT>m2!|&?}(GK8e&R=&w?V`*Vj)sCetWp9lr@@{xe6a zE)JL&;p}OnOO}Nw?vFyoccXT*z*?r}E8{uPtd;4<(hmX;d$rqJhEF}I+kD+m(ke;J z7Cm$W*CSdcD=RYEBhedg>tuT{PHqwCdDP*NkHv4rvQTXkzEn*Mb0oJz&+WfWIOS4@ zzpPJ|e%a-PIwOaOC7uQcHQ-q(SE(e@fj+7oC@34wzaBNaP;cw&gm{Z8yYX?V(lIv5 zKbg*zo1m5aGA4^lwJ|bAU=j3*d8S{vp!~fLFcK8s6%Ng55_qW_d*3R%e=34aDZPfD z&Le39j|ahp6E7B0*9OVdeMNrTErFatiE+=Z!XZ^tv0y%zZKXRTBuPyP&C{5(H?t)S zKV24_-TKpOmCPzU&by8R1Q5HY^@IDoeDA9MbgizgQ*F1Er~HVmvSU>vx}pZVQ&tr| zOtZl8vfY2#L<)gZ=ba&wG~EI*Vd?}lRMCf+!b5CDz$8~be-HKMo5omk$w7p4`Mym*IR8WiTz4^kKcUo^8Hkcsu14u z`Pkg`#-Y^A%CqJ0O@UF|caAulf68@(zhqp~YjzInh7qSN7Ov%Aj(Qz%{3zW|xubJ- ztNE_u_MO7Q_585r;xD?e=Er}@U1G@BKW5v$UM((eByhH2p!^g9W}99OD8VV@7d{#H zv)Eam+^K(5>-Ot~U!R$Um3prQmM)7DyK=iM%vy>BRX4#aH7*oCMmz07YB(EL!^%F7?CA#>zXqiYDhS;e?LYPTf(bte6B ztrfvDXYG*T;ExK-w?Knt{jNv)>KMk*sM^ngZ-WiUN;=0Ev^GIDMs=AyLg2V@3R z7ugNc45;4!RPxvzoT}3NCMeK$7j#q3r_xV(@t@OPRyoKBzHJ#IepkDsm$EJRxL)A* zf{_GQYttu^OXr$jHQn}zs$Eh|s|Z!r?Yi+bS-bi+PE*lH zo|6ztu6$r_?|B~S#m>imI!kQP9`6X426uHRri!wGcK;J;`%sFM(D#*Le~W*t2uH`Q z(HEO9-c_`mhA@4QhbW+tgtt9Pzx=_*3Kh~TB$SKmU4yx-Ay&)n%PZPKg#rD4H{%Ke zdMY@rf5EAFfqtrf?Vmk&N(_d-<=bvfOdPrYwY*;5%j@O6@O#Qj7LJTk-x3LN+dEKy+X z>~U8j3Ql`exr1jR>+S4nEy+4c2f{-Q!3_9)yY758tLGg7k^=nt<6h$YE$ltA+13S<}uOg#XHe6 zZHKdNsAnMQ_RIuB;mdoZ%RWpandzLR-BnjN2j@lkBbBd+?i ze*!5mC}!Qj(Q!rTu`KrRRqp22c=hF6<^v&iCDB`n7mHl;vdclcer%;{;=kA(PwdGG zdX#BWoC!leBC4);^J^tPkPbIe<)~nYb6R3u{HvC!NOQa?DC^Q`|_@ zcz;rk`a!4rSLAS>_=b@g?Yab4%=J3Cc7pRv8?_rHMl_aK*HSPU%0pG2Fyhef_biA!aW|-(( z*RIdG&Lmk(=(nk28Q1k1Oa$8Oa-phG%Mc6dT3>JIylcMMIc{&FsBYBD^n@#~>C?HG z*1&FpYVvXOU@~r2(BUa+KZv;tZ15#RewooEM0LFb>guQN;Z0EBFMFMZ=-m$a3;gVD z)2EBD4+*=6ZF?+)P`z@DOT;azK0Q4p4>NfwDR#Pd;no|{q_qB!zk1O8QojE;>zhPu z1Q=1z^0MYHo1*``H3ex|bW-Zy==5J4fE2;g6sq6YcXMYK5i|S^9(OSw#v!3^!EB<% zZF~J~CleS`V-peStyf*I%1^R88D;+8{{qN6-t!@gTARDg^w2`uSzFZbPQ!)q^oC}m zPo8VOQxq2BaIN`pAVFGu8!{p3}(+iZ`f4ck2ygVpEZMQW38nLpj3NQx+&sAkb8`}P3- zc>N*k6AG?r}bfO6_vccTuKX+*- z7W4Q#2``P0jIHYs)F>uG#AM#I6W2)!Nu2nD5{CRV_PmkDS2ditmbd#pggqEgAo%5oC?|CP zGa0CV)wA*ko!xC7pZYkqo{10CN_e00FX5SjWkI3?@XG}}bze!(&+k2$C-C`6temSk z_YyYpB^wh3woo`B zrMSTd4T?(X-jh`FeO76C(3xsOm9s2BP_b%ospg^!#*2*o9N;tf4(X9$qc_d(()yz5 zDk@1}u_Xd+86vy5RBs?LQCuYKCGPS;E4uFOi@V%1JTK&|eRf~lp$AV#;*#O}iRI2=i3rFL8{ zA^ptDZ0l6k-mq=hUJ0x$Y@J>UNfz~I5l63H(`~*v;qX`Z{zwsQQD-!wp0D&hyB8&Z z7$R07gIKGJ^%AvQ{4KM0edM39iFRx=P^6`!<1(s0t|JbB2tXs_B_IH9#ajH0C=-n+ z`nz`fKMBKLlf?2AC+|83M+0rqR%uhNGD;uKA6jOjp7YDe^4%0fRB<^bcjlS2KF~F; zu09wh1x0&4pG&76M;x8$u`b134t=dEPBn6PV|X29<#T4F1mxGF*HOgiWU8tN@cguI z_F@o+XL7FJztR63wC|j4x_DANzcX94r7Iz-O2x$({&qd*mdLG=-Rv)uZ}UlMR+F&q zU}=lkfb0p1>1Ho){o$@}mSKIV;h*$AND7~Dl)QzpFBlSM99Kx+F7GsVK5xcR? z_4Q(Z%cgk8ST}U;;=!LwyZVu^S$>B-Waeik%wzcKTIqeX=0FP(TGQ=nxi=dsS5BYF zl@?}NT!Y!Iyos^@v7XWXA{_bV~1lxz7gC?xuXxy0_?GaN!AhRRM5>)^t%&ODd;@HN5L{MD3 zc>i2keQZVm#?NrDwbfd}_<*5^U&w0zv~n-y8=GGN-!=_`FU^cM8oVCWRFxw?BM^YD zi=Vxz4q|jwPTg+?q7_XI)-S@gQkh>w0ZUB}a{^ z_i;`Y(~fvpI!vmW*A^|P7(6+@C4UeL2WATf{P1?H5rk`5{TL zcf!CgP6Mi{MvjZS)rfo7JLDZK7M7ANd$3`{j9baD*7{#Zu-33fOYUzjvtKzR2)_T1I1s7fe&z|=)QkX;=`zX8!Byw-veM#yr;|wjO^II>!B*B z0+w%;0(=*G3V@88t!}~zx)&do(uF=073Yeh*fEhZb3Vn>t!m(9p~Y_FdV3IgR)9eT z)~e9xpI%2deTWyHlXA(7srrfc_`7ACm!R>SoIgkuF8 z!wkOhrixFy9y@)GdxAntd!!7@=L_tFD2T5OdSUO)I%yj02le`qeQ=yKq$g^h)NG;# za(0J@#VBi^5YI|QI=rq{KlxwGabZJ0dKmfWDROkcM}lUN$@DV`K7fU?8CP2H23QPi zG?YF*=Vn=kTK*#Y_{AQN&oLju|0#E=fx%YVh>S{puu&K$b;BN*jIo@VYhqPiJPzzM>#kxoy0vW9i;ne2_BIG0zyRFp<3M(iY(%*M_>q0ulV2K}Tg zkG{EWKS{i%4DUuHi%DVKy%e+Q!~Uf`>>F6NgD{{I8~nO4!VgOvtFOc7(O)X`|7n*f zxBa4CJ-v9fUUH+`7sPVvpM_C*udZ@OTGTzx56QM5y~OlrZc&w9=)B?nmd@keRn+^= zvm~4sa5987LFDnU{(N|N zJAR8H@}p1fC+H(yTI4n#%~TbImMpuqYn9cQ<0QQ%=PzZItLkC*ef9WJUvfITKWh#D zc#__8`4am9%#NslIUw+<82#SR8AYG|woLfBg#!-&dqq}@P>|I0%lbdy0lSMmNe+}o zj0zZuFr6Wb?Y{Qy-S=|r`bdrDmhnmvkRnkdn`YCleU>Q$=je}LGhh>_QAj6aa_0Oc z%Swsmui;IRx7bN*=AAS@5yW&Y2hy;3&|HAiA8}!HT6!Z!RVn~MZg`RmI6&%#tBZDx zfD+y@Z~NWlk*4l13vmt3AK2wP!fQlnBbECL>?p)F?T)<`w&QN>cP_V>r7UTcsTaaP zTOb$f!P@zf$6>890NVKbIkG8rE?9!Y97sMSZjfF?A zYR8lp`LMoz~O?iaZN;gcX;LC-%Ia*R%A&SLx!YIf29?P+=XAAojK8!^OU*@?R&DK!#G_lsn!#;S375uZ&B0HH1|BO0R90$U>qs zSvHv>H~mAgNCcjo-e+;RjY6B9NCbQrZ|BHjTkehaU<9CSkdd>Vl*ifA2LNOP&R2Qdy3k3-TQ+ zbq=#vI43x`s=%~cGyN&y4Y!FxhwgDe@i6uv8^BLL&3z*SO=D0aLjih?gY4-9uWp5or)H+v~w6n5X#F-I52z=Z_p4JB(;M| zeaVFhuR2|3UD2MzVc~^nSoD2(dD#uL_1PdnIxeA{V5n`#3xf1Zx@4lw(DsQ&H$h zw#%3O<1173hjg2_nhKi!d1ej=h7y`hVjCNB6|HTnx>SWuCE-kgTnfT+YGX4_Lun({ zDv2`>d3vrS)tTf7ps_vvh!Cx^e1BFuWnEAh0(7fkNk|-3oU|iRWdsC6U)?Raft~HN z;^$U}vZK5O8|LV$>6X5T(uYkblv{zwPxnQBh(BQ5tA~J!vGiAMYP^_ki~pkIxDfOZ zUJDwq%O~WueeV6%uN<54&u*c&E4y431cklBNrb06zGOOy4XNT~JS-q(s6@)F@ovbe ze`fial(O4(-su%6@@1+V0MsdLLMyE8;)nou(7}czU(5ASaZYDT(kUZ0L(&g$nF^n9 z9-Pi`ZZLX&)^*M6As4_2Mmc9S7OT)F8KkL2NJ)KJcnCuWU=Wy402A&45#Q9Id~BBH z0cY*xlv!uXzKrXLH!xQu(OtJvEj|0-DmRj1vjFz{c*I4$Pe(+_V|^b~S!0xm{8lq= zZv)@NlcyL3Xdz+*|L137F7y6L-2VsrKw=q^S>F6i%<{Fr8zk06$Ay-(!L$fY@7mcng!2}L0t zgi|KxfB63Xtk_Q8#ZPipQ@!zgjdpEIbK_?q17Hoi4Eiyun$hrc>T(7pOLVLQE=lgGwA+A308p& z7@=09(|$>eLy5gLe{*|3b(M;1n;C^~v?o88jYib48eR4$QGsBFzd}3QuwO^_XE(=B zq+hMi0UFC|dB{LCwch7;zYT=NK})O%sgi0k#yV;My@24^B1+CuZmYOh0^b)5Ba_)) zC%i#_Iev&nsu%I|1N5=MVc#PrlunKAs&hY|3s5;@}`>sB>}gzxuB zB=2vrRyB3uiyW(hkDUNe1@&(b`;>ZvGgw|@s{zVC#_`HXIN_^J@Etb zA7A+F?ot37T{<-vTy8h&b3e+WKHE1oh;pUQrN4yRRrx?mT_9jRa2i4l1fUnLW^Cbl z!I1>VzyFe?VELWWhM?@?t-YPZkD-Qjo@bC2(o#ZtZmr{KZsdFWItV`rs$gp{724@C zL8K5}E0+DHcWcL^{BGei4>@J-3%a#$y6;I}=upc};-NDv-z#kPX26ylOpH)Ov1uU{ zkLj6oiH6l_s+B~_z;|Jc2oi?naS7#3H63~~lWj4rUnd=fCnKdkik<@R&kch9q##G{ z4u!%=rlM~Yp3jk*t8}1B`Sv6<%Z^}~1e@aq zg|JQ`QO2pSjAm-g*?IrNc$^~sIrNBo2$m|Sxanr?Mfs>2@Auu49 zGXlsS<9XS1&8h(dD*Hl&5HBDG!^pJ*lkau_Ur+7`7z;rcs$hT4we?3bT=7Fe<>{5( z2m2(c+hUz2BTHM8dCe*Z3XX&Av;b~a=$6EF>&^E8%nyxO@m_n!q&XD^A{SRjRZQ0L~qDeC=j&0$j6=LNIz@`ni^>ch|sv}^6 zlm>?28yPl@WmDPR?Y-A9X{U9Dv_IsbXJnzKCjkRksLOg#42uG2mE_acbTQ4)J|1V>%U@K(FP3AYhL0U zdeOCPN1qLv!|#c=p!_+%VNV(GHt`RuLRV^vz<5tt-r)yOK**kUWPspVAf|}ZL{LS= z@k(@@!P&W!>wwe`x{+GrFSWhHov7hu?{KuuT%kl#WO@*WX$i_@retlhQBj++SVNCx z5$78LxP>Z=^aJ)D280r_jj=zFfMJFXCIe^B{~V@d1rl_F(qo&AB4bC-vYL>x2jSKX zpuTG-6kgp3e^T&+dtV*i6a~)v@n?n*MffN59y}<0djUX zt27R+SE#hp8bzc#;rk$jw3r4)Q@eI$*`_)=Pvge8@8|8>H3X)<9YX6cXa=ii#Le;(qKm@%0-7$>2ShnYc`j#zJ7gu_FE^?uAkL|H)UIH#gPu^40!6^J=^ zr`}iwa^!4tzW~vOMZAaKF>*8A{^8m$i(VK)>?=#l`xrVe>wseSvM_aF zATNkY>kM_P3?1kE`uIq#mvr-wuTgUH0N<&JhF=(E9%^NS*HLm!4GZ4_XI zL=R5tlG5Mk_1rPfg)sk^llFuKPMPBhuU|L5q#yP_mzxp1o&pAzi-X31sgFpIHn@($ z_>=`AB5(8tP6p2zS5VEvH5J$M` z_much3>S7t3Yo`Yx!>83-hW9LYzDKP?mKdkD#QAK8*M((sx{eBQdrR<^3ZhFP81+& zBnJMUefQyNBji~$5d88Wfw1Lv59aJN9t2!pABLg;ewJ#LXL-10;QcJl+Y4Mtngb)k6JZlCf)3uD_u)J3sYyN;NN5hNbg$%W!i-GK%e&!Us)2IExWSss$YG(hm3kJ-h%yD z>8q^n$+4I(_y_mbT{du4P%h1j3oSpjhY97{+IZ`aA4ug!vNJ6*p?<2H(2w+GD3j$I z1TUXGyNzdf>_yB3grP~FZUs<2Quw;eEi*7s(-MiIkQ%@J^+WGdQvYSUN+TRiD-xto zJ=OUU+kxGYc!HCLNbCvR4lGTp~#L;DFzGd-#gJe*xf(P3hDQz|y)?b9mwU3WUVnpcqXM<@w%r-k*Wr^gzAv)8T^sqA=Ye z!7qy&exJmAcAt~CwS#@yNmjr8*T*!A6w4~E*ibaLRs0CFo(;R3=ODhDt6zWNodmo0 zXx&bT$6&+5c>a|WJ)F4G-^GjY0H#*tY=UNyYr_q5fsrcjk(c^~e*7Lf`!Jd`)p412 zn|^*hV= zFI4UbwA%X@smDd$cQOiMC%jfitTxTb+#`9`G=2rJDfK!E=5ra|So>lc{X1$~w28i+ z4p&cTGwZ#5VueiXS9O8#;RR$yg7tL9!^)Sz&pZYIzlSh}0}V{LxL$Cu%B4U5_}k}- zm~|CsD<076x@<>m=6w6N?WaThIBP`!u{-;WF)xc=2otx*lwf|5+MkdJePjh(B z9SH+%cHGCMAXNxB{_3^otDWdsV7Ob6n{0 z+&!(;iaHOX__5z_$Qk{%xYV%Ig@7iokGBwR`3642ZP#H#v9QGbWl8<|MS*=@qO@Uj z6+SZ_v9`1paUe5tFN~v(b#J3a_Lx0+;r9giZIx-A5TxdbG>xi#AZ5_z1V}B^n)sxT zz49}eK7EWb6wR!6-qQOrHQHkUvshvq%=G2d&@(#XM*Am1;WbnJ{X_!a{ZkphD$^TQ z=Iskb&}=lBm(RHiwJoGg`*NiQ6#RB$T#LF+>#ef;Jne&MxKPX!#r`&TVEFsp2jnNx>dClzpcPy&G&13a_<0qaR3i+k212~hoQ z8nMk{JP-t04I{GW5gUBqcJW-jSMrlw}>p)ptx?WKuCUV77taMiV zHok9V=6yv+Uts@fMY&A}amC=!Yj}eL@=e%XJ#%?agkt1jWF+10{(E9mHLDa>Ll7Vj zG=3cp%ljIB-6pC}6&`xJ*6WCP|IlglLWJ^?yviI8Ve)?V_i4%n;olzny62_`-|IGi z^=}p_O>Z8M;c4|RExu70E7ePW(HWVS&E$+LL6xSQgB`QfMQJ|4pCTFowA39p5P-|$ zUtM_H2HnP8_RoS~Vwk(FhbG zH41licj%=0a;Ln2STFBvU}Ne&O&%8bYKj!h1FA#sNM`232fX|U3QPp#3C?mN2;hE9 z;)!@5ixSPl<89^7gwhHc2YAX1KJK$#*3`KOMIQ253q7-*RJ5k)zp9GBO|Ga~X*^}US5oN@aG&waHV%vi~r{t^`ptTxb zL}q1W8S7*>7oWwvgV4uFLZ(@k`R*=LO_|Gu`prs~!WQXj-NLIa^2(7IHg>BG^N zc|i{-^=&Cek9dkJFQys|sjG9i>LLz|;yCv{^1i%c*h>8zF91kLvS9HBQi~ZU!JL`B zK8N+U0fr1*6??Ium)AF!6tc1eGhXIYL6IRT7rmKp7+>?%5Pa6zC5)KY$ycF0ZJ`G5nEQDG100U-jLkH8^UE4g6wq?sg%pP=-$&G#bcN`^?w3a6 z((s$6eRKcSEIslW-kk5Qi|5Mg-(xdLF}PxxVh$PuO}#aR6pW1kV4Af!Bqh*btXNNZ z>-4(IUl+L4dw+3LcpGut=qB45O+W)Q5?*zZ2A6rJcg`qkSvWA!j^r2mqKuCm6`Py? z@^T#Ux04HemPGd!Hs7NkZdVn1}8_j`o?)*OKZGS!`ff)gF zG?v-lj$wWNWCcw2Mg2o18D~1?3_b0XzdiKBNkYSDpcv@&kp0POmweJE2ZkIQ3B!a! zIgIoE+Xv?;34kyo^QYjZk+tEqZvq^#QG(OzX4~X+KtsoQoddTWUR(yo8R+ObEF1j<-syWOb>)JQ&Zbdu(sctU%Mt zW&YR0{ttY2TTXYZ?~WNU&cES1Z2q(7SrWDh``!J(JM+Nk$!hu&Y;(7E`ZNKTe0w+% zJc?Qnw2B+%UR}0;cB0Rufa(7-3FF}?629@LgTiEC&2uyL6NxexOp?AKT^aAx3gi(W zao>r>MPw0eQ3>IV02uLsC@>yK_epX6GRg4{NEL2wPPF9=*L2RV3yyK8DhuEK>rmmV z`&Q~#c`lgR&93TdOCja|ewOXmPNRh7!&dMT(1ett#iDr8HZW~VqWW@7fe9B6;7S+? zbC`d4@MEau&mKlOPKd>*10q0c{~^baw6!a*w^sY#0Xim{oOsiXiDOhbG&kl3c$$n1 zMRrD83&QucDSEcV*7LIp8VTA@F<%qe+_c`L;6on(>SjAU^}5c9!BCffT>$VQhe=)z z8(=Ej{5>jhmjB3{xDfj2R@VmHQ!CqjlO4KnuOmvHy3K#po$yp_V;p_MKjh1`(rzj6 zHW956k1yvntz{_g?Xbs`avK(IjlTnsu%htO;D7 z?J#x^EzuvVn&NA=!MEj7cwe5A-Z$Zk2LBZH$~%E* zf`((xH0?`}hs|HA%mtwfOEsZJxxrennkTYcwP#FKO5%Lpc^JXhSpV|ZH$Wr;`}`_( zIP==gd3LYyVtwD|*ZJGi{7~x8{=^bGVqu0RJ`n_BZH9+}kz%-4ZRsImi@rx%=ZEKs zcPnUXo6hbJV>fH;@1|bAHIe0ijYI*&kdT|HkDS$9No9 zCHo=*HWb~U+Dtzxr+Esao}6@|;Pf+E$ay0$kQp#s{wlw+7aIKbMdf`OqhoG*;Tco0 zjrP}VQG#Y2cJuqoJg&5({)S(BA}q9T1lGeWRyu=Je|)I!6a+aj!IP^1({)ZYe&x6w zt3a)Dq^TB+A7CdB0-}#z2Ur$W&h3YVw8==!xONy$uQmDWh-@15iEOt!q2m&?ZLA|w z8loSb(0}7y6Xu0?M5Uf4>VZGluB`wMf2oh;m)ghxVda>3m}4%V)r^0nVQ5V6f3>*) z0&VN!N0~GC^P}vj$`EDMZEmVV;N&RISY2C;$0;2(<{Lt&PKzqRByQdiEHGAbwtbS zPj`Da5%U6k1oEtVzI}QNw;!hT6F+~|@=c@$C4NtO@=xgP?|5MyZAyuCzcvq4rdAv@C06%gZ`9%I);R6UGiGJobfux+<0DLS&|MSG4UH z_~o{^^9>ixMg~mY!-@Fai{xaE4^;qy9iZN15Gbn5ZqHWf>Jc5Rv6(#n8`1NcCsdmG zab*dSXVPaE?)wCalD;$ivF%@nB#7D`@YG04p6ed9m}4iJW|pfVMLE<-c{=-8$e?cH zUdU#mCj4gb zZKA^b9p*9S(}8@tw~1RNPHr7tQr;P+-)D8|sq=*o)G%RGqt> zzP5yf`pVxb)I51D_G~Xp^GNK zVI6sAX)a9s)e{8N3?35YA6aQTXuyszK3ah~CemzA&CII#8F&F#KN41~8I^&_%}6MCNb{W87qAF`zj_Y^szhb> z3p3}KbOxotY|(lD=;)`fYE_*{S}x;f^SW#)SU&5X#o|-R|trpa|L5PS5aa0 zTHw8%SDSVtU4?vyrhnq+^@dgFS)|(y{~(4j%3UEiO-rBM9%`)8(dh33pMLiuurNY# z#10AsQ7%*0Cu_DSAU}P;X(JwA64~Q_^R%d_zSm^6Aux?Pn70PM>9EvLeOX z&w9c)pGmcL22;MO3C_B>=NC0RJpMp8?#ZUf=GWRvy z6RHq3B}=MGVg?9@iKFBpsvnkVh3{Vpp=`CcD=u~@ql{my|6?3ssi3mCOPnjI&E}VC zc@X+Yl>;;DNo0W0`0th!X{?luDhOC{E8N=?!w}K1{V=)+1={m(f`Oc|N=07>}3;z{-(A zm{JL=j?Sro5iecmE2-pWlRf(r%|HEQ7kgwQ9+kt=NBhtQI7OwcZ#3%$Uf%^r2nhjY zoQ08MfC%_X{O9~WcirMZMhn#z^ux4Erx-tf-6bHD)9eH&^L>^jvAd^9A^DCDs?0;k zkm7LE*KjP6`2d17MrQaaLqd_Rka}J$csvUec#hw78<=s(hyR>065~YCVCA9+#Q+; za(*L0IEw!r5P|@-;x33L$Lv9 zcuN8YG&g{<(SeJG18~(b!5yywSqQiLAX0;---;}mF5&b4lg|T?LwKREa{9YX_-zL@ZE?Zqi@HxK^2KO1>0LATu{te=T zprmHtY)bDVfxI1S}KBE7V zznP7KQ8HekWU#W6mw`dr-boV}pMQR==&5=Q5T=_q091jfc;R*jX#&=MQ%~@E@9^?`$v48ks<>(fI(F6L(5ppKy|$HWng*bKOb(4|cMUB&z$#ob#XV z5-mg)gmFIybZf=znm3ZPyUO^GJfxt0kmHjaTZ|sthsxXw&}Y)fOUSg=JhRSR^UjZ- zhqqb}Wsyw4zdnj6@#BAJa#-PdI4_dgafFXh85DsEQ_cT+5)XpZq$fZlBA_9UsE9r6 zEFec5?uqN@QhJ^IzwZrwl-5J`CmVPv{(YDTqEqWR^dI;5hXc~cxP%B3v&~s0`Ct89 z@S`i~a^c%V^N81dDT*ItFS*&IN;@O$EgzX0e7x&}TD=!zS}hTpezBLS>mdX(5< z)8DEI(-o_D)c-UX@dA1MuJ*yc>Hf4|`*B2S_O>w*-tbUwtiu`;W(Ud{HTty@(&x(T(F&;M zJ=?H>6`B7nf-90e8V`WSVp|0oEKB-P2M{}4ZDawzvM&a!y>`Y#jCsD%T_l``@ah(I2nJs~Q|%uSKu@k!m~*8B*IoA{*TgtF<(5sHCGG;n@NE%~Xt(G$^&<87u;}Na zx-8cq0g`uA(&RBFo=-4Y1GUZ<``Zw{xL4jfHkZw~%~wvtGueszcXt)_QwH8g!; z%s&3kSa~R$dO$-%L-)c@_hi7&>{6L_M>OZFkUQu;{sL_bUMStNrt{{&O(Wn~*zPOk zB>dnfszb29NSTf2pqIs68k|p-UrSrxgLHqi?3N-UFa!LHy9n1)=s>`yS+J{MEzS@ zNlfGtpma7kG&LR3JE@wB%rFA*h~~KitlO=IP)ZjN6dQLM6qsry zHkB#cyNh#n`)}bCrN1My*;k)^@>e4gJ`LJK?2)Pwp?4Tl4)4FA0(tvY+#1jOUM)xw zlMz4x-f@g^+yKUN`?Vu)|AwujArnM~Pa@y*Q9S8eS(u{-S%(Z5=R~pRl5ZGDjdqH% zC8rW&{##wOpU_oTIG4WXMk4&%2t1;lWcW5&!yxmOT*!hBcKyTqEcNoO+R2;Q?Yj+W z1-Y4?59fijz4(MIDwGe4-baYf08UCs;r|YefD-Md2ST;=cxwpgW=tR76-dQVAhn^= zG9Wk5lQk%jIR@KNU!UMp6@BfU;r+;y4VQ)D2!Il9HX%yW-9nOzV+m$YKzVaO`B8S7t z$!S2Mz`xw>V(RjE`0>bQp<0y&h~Y=M#jpy!#=dE>`=e_AjSZq6u!Dy1xJf~-7|0F! zPR9|n`e_7D2DIV2H(CESQ}hA>U>n|6`%z?YKEA~)BOVY%y=jPV zT=44R!L?J)736X#csn|lfBJ)o8ixaZclguWgrGO<`TN2FMfO}7;5}d+BlK0yTSH3* z4!=;5rOh85&2|x=46hkNaz?)U8&=bcfh=N_#8BNpZ2v$aVBo;sk^*X`v;4-LU;D>! zM*h12MxXIQy)SfAqE4;jY)wgnppazZkdNNVVF;(PLf^qK$FgY9+VFyBKE7UC|f z`R|?&egV11K3s$rJ6!GvoeW=jV*!-e(wA;x(2=d0E_e_%0x--0o8#~m^H1%AH5Z^B zn!TNPn927*bvaf0pt}zhK0o^V@WlGwwKo(*nQ|Q~4_;>~-8y20`HP>@UJa)3nEnGG z5Hwhs|FcmFG16ZVNb5hL`2Gc1{zWIMM{_OiKewV!hCi}U!VuE?s9wU-QbZ!)+Y^tS zGzp5OSi5iq6hmEr$w}&9DFgoB+i*`q`8TBi^MVS{SKEb8Aw%@K7@XCo(De2A`6%mf&a2#~y1N)+kJLD$1HCP!22)(U}xo2|j?WRzt(11j8Z_*v;P$R+Ug*Gy3VxV4K; zGGUGabnW*`Z}~`ydXL-l9e=GC$pY#z|63vy>E*m=$=j}iWP{sRTh0%H54`t>2xYH% zsk+M&u&pNgMCM@3e)Xc?jBWX-TIR_cQ1Z!RW7!B zBjZX=+^3}?SE)B+$EP+0oi1Fp5blDT?*}nsP>filqXH{ms zxU<$hetC`u)Wi+x|EKL-`y^#aQX+sDYIa{M;V%LqLrOk~lR>u0Q!+pyQSU4zY`?E^ z|5@)C)w6G_=i5YYC5SE_u(7hDNYr}uKT|@DSqF%S++lTIbIk^$a>{~0IH8KNFEy%+ zW#$&!ynpgNJh>6uR~?2c)ZMW+h0OKu231(7L_vETPaR+(P)Zy%0~yGm>E9?@@x!Jy z3PYgS}Q@b}x}E#F27@F+j}0=&Ql4gES&f8acMrPAVlVs9$97`FR))R5wI zc&}KFI1UIewh>3PkhnB7u zS3AT8_*|nexznG|Z*DU0c!K@jsI4J)5#DyNi#|e#`l1Vv1`1)*NVcy0LZ``aL0n8B zecupJ(rhq3u8bW0NIRhKYq$v1li+jp*4hfAd&wxYDE8vn1TQ7S@bTM|I2Ob z8vMOIxA7&_j{AKmD+O@EyXT`|dElt0pED^@IV0m)RPBUs*5jW60>>w1!@_G3aBKzG z_f(KfAPBk}-jQtR*Sroq!*3rbQ_m27e+YdzQjUb<_*k8vc_C)y!@cj5E>NxUhPu&g z@Z2<~esU`)ih+4opWe+K7sbN9n*9@n>#@n3*o z?xoROgDuvhq>jJ;Ve{6i<3roQNfgo5^4Q4(|GNExO2Dr7GjgA2zWuKp_K)K0R(6lv z!l$!zW-+T6mb3gQaAFviTQi{|*t%>{(mhTdy+y;Re4qT@kccy#{b z&zWy~kLO@>*WPj2k#H)|7L&gAJ37DmHQAme#@m;(Y8Nu^`D5vf8sZFW#+lA2!HK=( zJ)#hO6JD*`o~&c*&46d}g=Qj@SsoB5ikC z^1V8E+&<-OzuS_C`p5<<(A6fB`LXT(!kV^0_~hL6PpW4={l%|#xgdh?5EIk~lu8{D z2hiyhv3Yxij_#$Wu>P@7SYsl`-~3;}Ktx{34_NL^Kwin&=?!HDv3elQDbcU*qyYpN z(#yw~f1vFGK-t%CC-qa-4FYHbA^h>bag-I&*qaxwn?Qv|idE$<>1H|Gr6JtUu(he2$eg!N z@HTF@dG1)*y;4fxe)4_ZkpaBHH9hXp9p4|gLrRQyuevRd@gSS}JhRnWqrvm|U@>qM z=yl7RQROTKwQtzP3!zUF)_6Ld#NGA6v~2{J9Dd`h6{%+XsU#qGLh%`fB1Hc?wfayK zN`H4BpDp)npVQuu$DVW1qsBS&AJ2eP%6Qw>;k{)Z$8%HL=Q4(a$Ng2_vHw&vA!1L+9zc8vaX2GtqJ{L-;gvF0IR$em zMQ8@{Qp3+3Quk)TJ$?I<8KmwzD*7#(q<@Mc`dchngW}cRG14(Z6K7{T|LhFXwhqUQ;BET;cYqPcAcMgt6M$V9$(?jHo@Sud$an$U&5F zZ1QNh^ztt)E*d#Ij;<43oSKKnd+WNr$_r}+s_O_x6DZSB10*5Q{ourqq>mTl| zx4y^(cy+9;t@R=*j>3_dmm_m)$k$#937V(sllby&5)Xex^UD-|m|q<(jEd#@DV(of zAd7sSdmS*zUDqJ9|K%O2J2OfdUiK{{b{PCy)pi<;hp~7v1CQj&4-10 zgO<3dqhYH1#-Fa}Q{pjql5>>P6gZH21zLfxZ4$SK4T@7b!|`nWF9b*84Bq8&Eht;9 z*P72x&NUCZ7*@B$`FtE=hz5b}S`|c6Ey+j@D1ZibjJaRlR;{cxAWv z?Nqa>QqV*H-*zzaPvpLMHt~nl(x6?vrPpR?zn7~wow?oj*1TKmx4j71>$hvtC$DLD zUrz0^tiP0792U&dxJxNv@r}Elsjn^aSLUu=9#mD{&9n8|ayIL$!H3s>%KEvbchBFW z%cd?VU83mGF#Dar9*s~w&AnmQRQIOvR+uWsuZ?+|a=TzApXO@q^(r%8=}iv#wCnFq z=K9}JbqU@k99Q%j-}NNk+qLCP)jXfmOO|)@?mHcnynd6({mJisP1_}u7k)|eYHXWK z63eQ)E$ufFi!3CWUY2gw%e>omCv}qEX66aH-k&35f9`Q@Us|NPetVqe8=dX*VxJdn ze`q7b=Dn(UA(2sf&g)cOmQFhNJ#<-aMELJZbA#@to>25@kbW<)&!X01 z%NMJt>1ST)tyX)h@?`DxhbgCHr>S4wv}WC&Nw-!{+Z7$2D}74QAcXTvip=M0%Tp_N zor=k`)t|ra^ySr-+(|R9mB(E=`MX#y(wSw)$!iymzB;^c*>%&^*7HxTnRga=soSZT zdDl+9s;r!v8hk6POtzBaig4pRp7eWF(<8gufvNHPu6xs-=e{;mnHzJyGKE+8L0j}; z@%8-e^UCL5HhMiR>sD3Rve&yVZ#{Q1*CO8c+qSr^Z#CN;)(X5>tGG5yUw3<+CfhaL z%bP;hZ?jvgJU67BWyiy74_)6r)_nSxttxn0`0?HE^5(uydHVgP+HE$V?Lv)Leti43 zWA|;f-RqX``95>)^P-fw!Vi{3KNsII-*5f){gdxqd%gVdB1sOBNe=nEW%;i~g_P8J w!5uhoe-Jcg1nPN%MiEAtgE$;km@@t6ukO)1^!cY^83Pb_y85}Sb4q9e0FIsP9{>OV literal 0 HcmV?d00001 diff --git a/flutter-sample/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/flutter-sample/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 0000000000000000000000000000000000000000..2f1632cfddf3d9dade342351e627a0a75609fb46 GIT binary patch literal 2218 zcmV;b2vzrqP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91K%fHv1ONa40RR91KmY&$07g+lumAuE6iGxuRCodHTWf3-RTMruyW6Fu zQYeUM04eX6D5c0FCjKKPrco1(K`<0SL=crI{PC3-^hZU0kQie$gh-5!7z6SH6Q0J% zqot*`H1q{R5fHFYS}dje@;kG=v$L0(yY0?wY2%*c?A&{2?!D*x?m71{of2gv!$5|C z3>qG_BW}7K_yUcT3A5C6QD<+{aq?x;MAUyAiJn#Jv8_zZtQ{P zTRzbL3U9!qVuZzS$xKU10KiW~Bgdcv1-!uAhQxf3a7q+dU6lj?yoO4Lq4TUN4}h{N z*fIM=SS8|C2$(T>w$`t@3Tka!(r!7W`x z-isCVgQD^mG-MJ;XtJuK3V{Vy72GQ83KRWsHU?e*wrhKk=ApIYeDqLi;JI1e zuvv}5^Dc=k7F7?nm3nIw$NVmU-+R>> zyqOR$-2SDpJ}Pt;^RkJytDVXNTsu|mI1`~G7yw`EJR?VkGfNdqK9^^8P`JdtTV&tX4CNcV4 z&N06nZa??Fw1AgQOUSE2AmPE@WO(Fvo`%m`cDgiv(fAeRA%3AGXUbsGw{7Q`cY;1BI#ac3iN$$Hw z0LT0;xc%=q)me?Y*$xI@GRAw?+}>=9D+KTk??-HJ4=A>`V&vKFS75@MKdSF1JTq{S zc1!^8?YA|t+uKigaq!sT;Z!&0F2=k7F0PIU;F$leJLaw2UI6FL^w}OG&!;+b%ya1c z1n+6-inU<0VM-Y_s5iTElq)ThyF?StVcebpGI znw#+zLx2@ah{$_2jn+@}(zJZ{+}_N9BM;z)0yr|gF-4=Iyu@hI*Lk=-A8f#bAzc9f z`Kd6K--x@t04swJVC3JK1cHY-Hq+=|PN-VO;?^_C#;coU6TDP7Bt`;{JTG;!+jj(` zw5cLQ-(Cz-Tlb`A^w7|R56Ce;Wmr0)$KWOUZ6ai0PhzPeHwdl0H(etP zUV`va_i0s-4#DkNM8lUlqI7>YQLf)(lz9Q3Uw`)nc(z3{m5ZE77Ul$V%m)E}3&8L0 z-XaU|eB~Is08eORPk;=<>!1w)Kf}FOVS2l&9~A+@R#koFJ$Czd%Y(ENTV&A~U(IPI z;UY+gf+&6ioZ=roly<0Yst8ck>(M=S?B-ys3mLdM&)ex!hbt+ol|T6CTS+Sc0jv(& z7ijdvFwBq;0a{%3GGwkDKTeG`b+lyj0jjS1OMkYnepCdoosNY`*zmBIo*981BU%%U z@~$z0V`OVtIbEx5pa|Tct|Lg#ZQf5OYMUMRD>Wdxm5SAqV2}3!ceE-M2 z@O~lQ0OiKQp}o9I;?uxCgYVV?FH|?Riri*U$Zi_`V2eiA>l zdSm6;SEm6#T+SpcE8Ro_f2AwxzI z44hfe^WE3!h@W3RDyA_H440cpmYkv*)6m1XazTqw%=E5Xv7^@^^T7Q2wxr+Z2kVYr + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/flutter-sample/macos/Runner/Configs/AppInfo.xcconfig b/flutter-sample/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 0000000..9fe7ad6 --- /dev/null +++ b/flutter-sample/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = clevertap_native_display_sample + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.clevertap.flutter.clevertapNativeDisplaySample + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2026 com.clevertap.flutter. All rights reserved. diff --git a/flutter-sample/macos/Runner/Configs/Debug.xcconfig b/flutter-sample/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 0000000..36b0fd9 --- /dev/null +++ b/flutter-sample/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/flutter-sample/macos/Runner/Configs/Release.xcconfig b/flutter-sample/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 0000000..dff4f49 --- /dev/null +++ b/flutter-sample/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/flutter-sample/macos/Runner/Configs/Warnings.xcconfig b/flutter-sample/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 0000000..42bcbf4 --- /dev/null +++ b/flutter-sample/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/flutter-sample/macos/Runner/DebugProfile.entitlements b/flutter-sample/macos/Runner/DebugProfile.entitlements new file mode 100644 index 0000000..dddb8a3 --- /dev/null +++ b/flutter-sample/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/flutter-sample/macos/Runner/Info.plist b/flutter-sample/macos/Runner/Info.plist new file mode 100644 index 0000000..4789daa --- /dev/null +++ b/flutter-sample/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/flutter-sample/macos/Runner/MainFlutterWindow.swift b/flutter-sample/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 0000000..3cc05eb --- /dev/null +++ b/flutter-sample/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/flutter-sample/macos/Runner/Release.entitlements b/flutter-sample/macos/Runner/Release.entitlements new file mode 100644 index 0000000..852fa1a --- /dev/null +++ b/flutter-sample/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/flutter-sample/macos/RunnerTests/RunnerTests.swift b/flutter-sample/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..61f3bd1 --- /dev/null +++ b/flutter-sample/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Cocoa +import FlutterMacOS +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/flutter-sample/pubspec.lock b/flutter-sample/pubspec.lock index ea3fb96..d3d79d5 100644 --- a/flutter-sample/pubspec.lock +++ b/flutter-sample/pubspec.lock @@ -56,6 +56,14 @@ packages: relative: true source: path version: "0.1.0" + clevertap_plugin: + dependency: "direct main" + description: + name: clevertap_plugin + sha256: "2de4b90a98b1bf4c8bff0de3f113221d655162539d13bcc3bce5bdfd2ac83e05" + url: "https://pub.dev" + source: hosted + version: "4.0.0" clock: dependency: transitive description: diff --git a/flutter-sample/pubspec.yaml b/flutter-sample/pubspec.yaml index 5c281d3..a12dcd1 100644 --- a/flutter-sample/pubspec.yaml +++ b/flutter-sample/pubspec.yaml @@ -12,6 +12,7 @@ dependencies: sdk: flutter clevertap_native_display: path: ../flutter + clevertap_plugin: ^4.0.0 dev_dependencies: flutter_test: @@ -23,3 +24,4 @@ flutter: assets: - assets/banners/ - assets/configs/ + - assets/test-configs/ diff --git a/flutter-sample/web/favicon.png b/flutter-sample/web/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..8aaa46ac1ae21512746f852a42ba87e4165dfdd1 GIT binary patch literal 917 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|I14-?iy0X7 zltGxWVyS%@P(fs7NJL45ua8x7ey(0(N`6wRUPW#JP&EUCO@$SZnVVXYs8ErclUHn2 zVXFjIVFhG^g!Ppaz)DK8ZIvQ?0~DO|i&7O#^-S~(l1AfjnEK zjFOT9D}DX)@^Za$W4-*MbbUihOG|wNBYh(yU7!lx;>x^|#0uTKVr7USFmqf|i<65o z3raHc^AtelCMM;Vme?vOfh>Xph&xL%(-1c06+^uR^q@XSM&D4+Kp$>4P^%3{)XKjo zGZknv$b36P8?Z_gF{nK@`XI}Z90TzwSQO}0J1!f2c(B=V`5aP@1P1a|PZ!4!3&Gl8 zTYqUsf!gYFyJnXpu0!n&N*SYAX-%d(5gVjrHJWqXQshj@!Zm{!01WsQrH~9=kTxW#6SvuapgMqt>$=j#%eyGrQzr zP{L-3gsMA^$I1&gsBAEL+vxi1*Igl=8#8`5?A-T5=z-sk46WA1IUT)AIZHx1rdUrf zVJrJn<74DDw`j)Ki#gt}mIT-Q`XRa2-jQXQoI%w`nb|XblvzK${ZzlV)m-XcwC(od z71_OEC5Bt9GEXosOXaPTYOia#R4ID2TiU~`zVMl08TV_C%DnU4^+HE>9(CE4D6?Fz oujB08i7adh9xk7*FX66dWH6F5TM;?E2b5PlUHx3vIVCg!0Dx9vYXATM literal 0 HcmV?d00001 diff --git a/flutter-sample/web/icons/Icon-192.png b/flutter-sample/web/icons/Icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..b749bfef07473333cf1dd31e9eed89862a5d52aa GIT binary patch literal 5292 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 literal 0 HcmV?d00001 diff --git a/flutter-sample/web/icons/Icon-512.png b/flutter-sample/web/icons/Icon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..88cfd48dff1169879ba46840804b412fe02fefd6 GIT binary patch literal 8252 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 literal 0 HcmV?d00001 diff --git a/flutter-sample/web/icons/Icon-maskable-192.png b/flutter-sample/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000000000000000000000000000000000000..eb9b4d76e525556d5d89141648c724331630325d GIT binary patch literal 5594 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 X>c!)3PgKfL&lX^$Z>Cpu&6)6jvi^Z! literal 0 HcmV?d00001 diff --git a/flutter-sample/web/icons/Icon-maskable-512.png b/flutter-sample/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000000000000000000000000000000000000..d69c56691fbdb0b7efa65097c7cc1edac12a6d3e GIT binary patch literal 20998 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 MPgg&ebxsLQ025IeI{*Lx literal 0 HcmV?d00001 diff --git a/flutter-sample/web/index.html b/flutter-sample/web/index.html new file mode 100644 index 0000000..3035175 --- /dev/null +++ b/flutter-sample/web/index.html @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + clevertap_native_display_sample + + + + + + diff --git a/flutter-sample/web/manifest.json b/flutter-sample/web/manifest.json new file mode 100644 index 0000000..30c3c18 --- /dev/null +++ b/flutter-sample/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "clevertap_native_display_sample", + "short_name": "clevertap_native_display_sample", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/flutter-sample/windows/.gitignore b/flutter-sample/windows/.gitignore new file mode 100644 index 0000000..d492d0d --- /dev/null +++ b/flutter-sample/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/flutter-sample/windows/CMakeLists.txt b/flutter-sample/windows/CMakeLists.txt new file mode 100644 index 0000000..597dab5 --- /dev/null +++ b/flutter-sample/windows/CMakeLists.txt @@ -0,0 +1,108 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(clevertap_native_display_sample LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "clevertap_native_display_sample") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/flutter-sample/windows/flutter/CMakeLists.txt b/flutter-sample/windows/flutter/CMakeLists.txt new file mode 100644 index 0000000..903f489 --- /dev/null +++ b/flutter-sample/windows/flutter/CMakeLists.txt @@ -0,0 +1,109 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + ${FLUTTER_TARGET_PLATFORM} $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/flutter-sample/windows/flutter/generated_plugin_registrant.cc b/flutter-sample/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..4f78848 --- /dev/null +++ b/flutter-sample/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,14 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include + +void RegisterPlugins(flutter::PluginRegistry* registry) { + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); +} diff --git a/flutter-sample/windows/flutter/generated_plugin_registrant.h b/flutter-sample/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..dc139d8 --- /dev/null +++ b/flutter-sample/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/flutter-sample/windows/flutter/generated_plugins.cmake b/flutter-sample/windows/flutter/generated_plugins.cmake new file mode 100644 index 0000000..88b22e5 --- /dev/null +++ b/flutter-sample/windows/flutter/generated_plugins.cmake @@ -0,0 +1,24 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + url_launcher_windows +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/flutter-sample/windows/runner/CMakeLists.txt b/flutter-sample/windows/runner/CMakeLists.txt new file mode 100644 index 0000000..394917c --- /dev/null +++ b/flutter-sample/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/flutter-sample/windows/runner/Runner.rc b/flutter-sample/windows/runner/Runner.rc new file mode 100644 index 0000000..476cc13 --- /dev/null +++ b/flutter-sample/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.clevertap.flutter" "\0" + VALUE "FileDescription", "clevertap_native_display_sample" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "clevertap_native_display_sample" "\0" + VALUE "LegalCopyright", "Copyright (C) 2026 com.clevertap.flutter. All rights reserved." "\0" + VALUE "OriginalFilename", "clevertap_native_display_sample.exe" "\0" + VALUE "ProductName", "clevertap_native_display_sample" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/flutter-sample/windows/runner/flutter_window.cpp b/flutter-sample/windows/runner/flutter_window.cpp new file mode 100644 index 0000000..955ee30 --- /dev/null +++ b/flutter-sample/windows/runner/flutter_window.cpp @@ -0,0 +1,71 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/flutter-sample/windows/runner/flutter_window.h b/flutter-sample/windows/runner/flutter_window.h new file mode 100644 index 0000000..6da0652 --- /dev/null +++ b/flutter-sample/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/flutter-sample/windows/runner/main.cpp b/flutter-sample/windows/runner/main.cpp new file mode 100644 index 0000000..926510f --- /dev/null +++ b/flutter-sample/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"clevertap_native_display_sample", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/flutter-sample/windows/runner/resource.h b/flutter-sample/windows/runner/resource.h new file mode 100644 index 0000000..66a65d1 --- /dev/null +++ b/flutter-sample/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/flutter-sample/windows/runner/resources/app_icon.ico b/flutter-sample/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..c04e20caf6370ebb9253ad831cc31de4a9c965f6 GIT binary patch literal 33772 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 literal 0 HcmV?d00001 diff --git a/flutter-sample/windows/runner/runner.exe.manifest b/flutter-sample/windows/runner/runner.exe.manifest new file mode 100644 index 0000000..153653e --- /dev/null +++ b/flutter-sample/windows/runner/runner.exe.manifest @@ -0,0 +1,14 @@ + + + + + PerMonitorV2 + + + + + + + + + diff --git a/flutter-sample/windows/runner/utils.cpp b/flutter-sample/windows/runner/utils.cpp new file mode 100644 index 0000000..3a0b465 --- /dev/null +++ b/flutter-sample/windows/runner/utils.cpp @@ -0,0 +1,65 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + unsigned int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length == 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/flutter-sample/windows/runner/utils.h b/flutter-sample/windows/runner/utils.h new file mode 100644 index 0000000..3879d54 --- /dev/null +++ b/flutter-sample/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/flutter-sample/windows/runner/win32_window.cpp b/flutter-sample/windows/runner/win32_window.cpp new file mode 100644 index 0000000..60608d0 --- /dev/null +++ b/flutter-sample/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/flutter-sample/windows/runner/win32_window.h b/flutter-sample/windows/runner/win32_window.h new file mode 100644 index 0000000..e901dde --- /dev/null +++ b/flutter-sample/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/flutter/lib/src/renderer/containers/box_container.dart b/flutter/lib/src/renderer/containers/box_container.dart index 352b6de..1641491 100644 --- a/flutter/lib/src/renderer/containers/box_container.dart +++ b/flutter/lib/src/renderer/containers/box_container.dart @@ -23,15 +23,23 @@ class BoxContainer extends StatelessWidget { @override Widget build(BuildContext context) { - final children = node.children; - final hasPercentOffsets = children.any( - (c) => c.layout?.offset?.unit == DimensionUnit.percent, - ); + final needsParentSize = node.children.any((c) { + final layout = c.layout; + if (layout == null) return false; + final offset = layout.offset; + final w = layout.width; + final h = layout.height; + return (offset != null && offset.unit == DimensionUnit.percent) || + (w != null && w.special == null && w.unit == DimensionUnit.percent) || + (h != null && h.special == null && h.unit == DimensionUnit.percent); + }); - if (hasPercentOffsets) { + if (needsParentSize) { return LayoutBuilder( - builder: (ctx, constraints) => - _buildStack(constraints.maxWidth, constraints.maxHeight), + builder: (ctx, constraints) => _buildStack( + constraints.maxWidth, + constraints.maxHeight.isInfinite ? 0 : constraints.maxHeight, + ), ); } return _buildStack(0, 0); @@ -40,23 +48,58 @@ class BoxContainer extends StatelessWidget { Widget _buildStack(double parentWidth, double parentHeight) { return Stack( clipBehavior: Clip.hardEdge, - children: node.children.map((child) { - final offset = child.layout?.offset; - final renderer = NativeDisplayRenderer( - node: child, - evaluator: evaluator, - actionListener: actionListener, - componentListener: componentListener, - ); - if (offset == null) return renderer; - final x = offset.unit == DimensionUnit.percent - ? parentWidth * offset.x / 100.0 - : offset.x; - final y = offset.unit == DimensionUnit.percent - ? parentHeight * offset.y / 100.0 - : offset.y; - return Positioned(left: x, top: y, child: renderer); - }).toList(), + children: node.children + .map((child) => _buildChild(child, parentWidth, parentHeight)) + .toList(), + ); + } + + Widget _buildChild(NativeDisplayNode child, double parentWidth, double parentHeight) { + final renderer = NativeDisplayRenderer( + node: child, + evaluator: evaluator, + actionListener: actionListener, + componentListener: componentListener, + ); + + final layout = child.layout; + final offset = layout?.offset; + + double? left, top; + if (offset != null) { + left = offset.unit == DimensionUnit.percent + ? parentWidth * offset.x / 100.0 + : offset.x; + top = offset.unit == DimensionUnit.percent + ? parentHeight * offset.y / 100.0 + : offset.y; + } + + final hasAspectRatio = (layout?.aspectRatio ?? 0) > 0; + double? posWidth, posHeight; + final w = layout?.width; + final h = layout?.height; + if (w != null && w.special == null && w.unit == DimensionUnit.percent && parentWidth > 0) { + posWidth = parentWidth * w.value / 100.0; + } + if (!hasAspectRatio && h != null && h.special == null && h.unit == DimensionUnit.percent && parentHeight > 0) { + posHeight = parentHeight * h.value / 100.0; + } + + if (left == null && top == null && posWidth == null && posHeight == null) { + return renderer; + } + + if (left == null && top == null) { + return SizedBox(width: posWidth, height: posHeight, child: renderer); + } + + return Positioned( + left: left, + top: top, + width: posWidth, + height: posHeight, + child: renderer, ); } } diff --git a/flutter/lib/src/renderer/containers/horizontal_container.dart b/flutter/lib/src/renderer/containers/horizontal_container.dart index d15e3cb..0fefe55 100644 --- a/flutter/lib/src/renderer/containers/horizontal_container.dart +++ b/flutter/lib/src/renderer/containers/horizontal_container.dart @@ -1,10 +1,10 @@ import 'package:flutter/widgets.dart'; import '../../evaluator/variable_evaluator.dart'; +import '../../models/enums.dart'; import '../../models/native_display_node.dart'; import '../../models/style.dart'; import '../native_display_renderer.dart'; -// v1 stub — full arrangement logic deferred to v2 class HorizontalContainer extends StatelessWidget { final NativeDisplayContainer node; final Style style; @@ -23,16 +23,77 @@ class HorizontalContainer extends StatelessWidget { @override Widget build(BuildContext context) { + final arrangement = node.layout?.arrangement; + final strategy = arrangement?.strategy ?? ArrangementStrategy.spaced; + final spacing = arrangement?.spacing ?? 0.0; + final children = node.children; + + final hasMatchParent = children.any( + (c) => c.layout?.width?.special == SpecialDimension.matchParent, + ); + final needsMaxSize = hasMatchParent || + strategy == ArrangementStrategy.spaceBetween || + strategy == ArrangementStrategy.spaceEvenly || + strategy == ArrangementStrategy.spaceAround || + strategy == ArrangementStrategy.center || + strategy == ArrangementStrategy.end; + + final List widgets = []; + for (final child in children) { + Widget renderer = NativeDisplayRenderer( + node: child, + evaluator: evaluator, + actionListener: actionListener, + componentListener: componentListener, + ); + + // Percent height relative to the row height — FractionallySizedBox works + // here because Row provides tight height constraints to its children. + final h = child.layout?.height; + if (h != null && h.special == null && h.unit == DimensionUnit.percent) { + renderer = FractionallySizedBox( + heightFactor: h.value / 100.0, + alignment: Alignment.topLeft, + child: renderer, + ); + } + + // match_parent width fills remaining Row space via Expanded. + final w = child.layout?.width; + if (w?.special == SpecialDimension.matchParent) { + widgets.add(Expanded(child: renderer)); + continue; + } + + widgets.add(renderer); + } + + // 'spaced' strategy inserts a fixed gap between siblings. + final List spaced = []; + if (strategy == ArrangementStrategy.spaced && spacing > 0) { + for (int i = 0; i < widgets.length; i++) { + spaced.add(widgets[i]); + if (i < widgets.length - 1) spaced.add(SizedBox(width: spacing)); + } + } else { + spaced.addAll(widgets); + } + return Row( + mainAxisSize: needsMaxSize ? MainAxisSize.max : MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, - children: node.children - .map((c) => NativeDisplayRenderer( - node: c, - evaluator: evaluator, - actionListener: actionListener, - componentListener: componentListener, - )) - .toList(), + mainAxisAlignment: _mainAxis(strategy), + children: spaced, ); } + + MainAxisAlignment _mainAxis(ArrangementStrategy s) => switch (s) { + ArrangementStrategy.spaced => MainAxisAlignment.start, + ArrangementStrategy.spaceBetween => MainAxisAlignment.spaceBetween, + ArrangementStrategy.spaceEvenly => MainAxisAlignment.spaceEvenly, + ArrangementStrategy.spaceAround => MainAxisAlignment.spaceAround, + ArrangementStrategy.start => MainAxisAlignment.start, + ArrangementStrategy.center => MainAxisAlignment.center, + ArrangementStrategy.end => MainAxisAlignment.end, + }; } diff --git a/flutter/lib/src/renderer/containers/vertical_container.dart b/flutter/lib/src/renderer/containers/vertical_container.dart index ea03583..ced9e08 100644 --- a/flutter/lib/src/renderer/containers/vertical_container.dart +++ b/flutter/lib/src/renderer/containers/vertical_container.dart @@ -1,10 +1,10 @@ import 'package:flutter/widgets.dart'; import '../../evaluator/variable_evaluator.dart'; +import '../../models/enums.dart'; import '../../models/native_display_node.dart'; import '../../models/style.dart'; import '../native_display_renderer.dart'; -// v1 stub — full arrangement logic deferred to v2 class VerticalContainer extends StatelessWidget { final NativeDisplayContainer node; final Style style; @@ -23,16 +23,77 @@ class VerticalContainer extends StatelessWidget { @override Widget build(BuildContext context) { + final arrangement = node.layout?.arrangement; + final strategy = arrangement?.strategy ?? ArrangementStrategy.spaced; + final spacing = arrangement?.spacing ?? 0.0; + final children = node.children; + + final hasMatchParent = children.any( + (c) => c.layout?.height?.special == SpecialDimension.matchParent, + ); + final needsMaxSize = hasMatchParent || + strategy == ArrangementStrategy.spaceBetween || + strategy == ArrangementStrategy.spaceEvenly || + strategy == ArrangementStrategy.spaceAround || + strategy == ArrangementStrategy.center || + strategy == ArrangementStrategy.end; + + final List widgets = []; + for (final child in children) { + Widget renderer = NativeDisplayRenderer( + node: child, + evaluator: evaluator, + actionListener: actionListener, + componentListener: componentListener, + ); + + // Percent width relative to the column width — FractionallySizedBox works + // here because Column provides tight width constraints to its children. + final w = child.layout?.width; + if (w != null && w.special == null && w.unit == DimensionUnit.percent) { + renderer = FractionallySizedBox( + widthFactor: w.value / 100.0, + alignment: Alignment.topLeft, + child: renderer, + ); + } + + // match_parent height fills remaining Column space via Expanded. + final h = child.layout?.height; + if (h?.special == SpecialDimension.matchParent) { + widgets.add(Expanded(child: renderer)); + continue; + } + + widgets.add(renderer); + } + + // 'spaced' strategy inserts a fixed gap between siblings. + final List spaced = []; + if (strategy == ArrangementStrategy.spaced && spacing > 0) { + for (int i = 0; i < widgets.length; i++) { + spaced.add(widgets[i]); + if (i < widgets.length - 1) spaced.add(SizedBox(height: spacing)); + } + } else { + spaced.addAll(widgets); + } + return Column( + mainAxisSize: needsMaxSize ? MainAxisSize.max : MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, - children: node.children - .map((c) => NativeDisplayRenderer( - node: c, - evaluator: evaluator, - actionListener: actionListener, - componentListener: componentListener, - )) - .toList(), + mainAxisAlignment: _mainAxis(strategy), + children: spaced, ); } + + MainAxisAlignment _mainAxis(ArrangementStrategy s) => switch (s) { + ArrangementStrategy.spaced => MainAxisAlignment.start, + ArrangementStrategy.spaceBetween => MainAxisAlignment.spaceBetween, + ArrangementStrategy.spaceEvenly => MainAxisAlignment.spaceEvenly, + ArrangementStrategy.spaceAround => MainAxisAlignment.spaceAround, + ArrangementStrategy.start => MainAxisAlignment.start, + ArrangementStrategy.center => MainAxisAlignment.center, + ArrangementStrategy.end => MainAxisAlignment.end, + }; } diff --git a/flutter/lib/src/renderer/elements/button_element.dart b/flutter/lib/src/renderer/elements/button_element.dart index bd59e32..eebea5c 100644 --- a/flutter/lib/src/renderer/elements/button_element.dart +++ b/flutter/lib/src/renderer/elements/button_element.dart @@ -30,7 +30,15 @@ class ButtonElement extends StatelessWidget { return GestureDetector( onTap: _handleClick, child: StyleApplier.apply( - Center(child: Text(text, style: _buildTextStyle(rootHeight))), + Center( + child: Text( + text, + style: _buildTextStyle(rootHeight), + maxLines: style.maxLines, + overflow: _resolveOverflow(style.overflow), + softWrap: style.maxLines == null, + ), + ), style, rootHeight: rootHeight, padding: node.layout?.padding, @@ -48,9 +56,17 @@ class ButtonElement extends StatelessWidget { fontWeight: _resolveFontWeight(style.fontWeight), fontStyle: style.fontStyle == NDFontStyle.italic ? FontStyle.italic : FontStyle.normal, height: lineHeight != null ? lineHeight / fontSize : null, + letterSpacing: style.letterSpacing, ); } + TextOverflow? _resolveOverflow(NDTextOverflow? overflow) => switch (overflow) { + NDTextOverflow.clip => TextOverflow.clip, + NDTextOverflow.ellipsis => TextOverflow.ellipsis, + NDTextOverflow.visible => null, + null => null, + }; + FontWeight _resolveFontWeight(NDFontWeight? w) => switch (w) { NDFontWeight.light => FontWeight.w300, NDFontWeight.normal => FontWeight.w400, diff --git a/flutter/lib/src/renderer/native_display_renderer.dart b/flutter/lib/src/renderer/native_display_renderer.dart index 9458315..359e92c 100644 --- a/flutter/lib/src/renderer/native_display_renderer.dart +++ b/flutter/lib/src/renderer/native_display_renderer.dart @@ -1,6 +1,7 @@ import 'package:flutter/widgets.dart'; import '../evaluator/variable_evaluator.dart'; import '../models/enums.dart'; +import '../models/layout.dart'; import '../models/native_display_node.dart'; import '../models/style.dart'; import 'animation_modifier.dart'; @@ -16,6 +17,8 @@ import 'elements/spacer_element.dart'; import 'elements/text_element.dart'; import 'elements/video_element.dart'; import 'resolved_styles_scope.dart'; +import 'root_height_scope.dart'; +import 'style_applier.dart'; class NativeDisplayRenderer extends StatelessWidget { final NativeDisplayNode node; @@ -37,12 +40,28 @@ class NativeDisplayRenderer extends StatelessWidget { final resolvedStyles = ResolvedStylesScope.of(context); final style = resolvedStyles[node.id] ?? Style.empty; + final rootHeight = RootHeightScope.of(context); Widget built = switch (node) { NativeDisplayContainer c => _buildContainer(c, style), NativeDisplayElement e => _buildElement(context, e, style), }; + // Containers apply style here; elements apply their own style internally. + if (node is NativeDisplayContainer) { + built = StyleApplier.apply( + built, + style, + rootHeight: rootHeight, + padding: node.layout?.padding, + ); + } + + // Apply aspectRatio and fixed (dp/sp/px) sizing. + // Percent sizing is handled by each parent container: + // BOX → Positioned(width:, height:), VERTICAL/HORIZONTAL → FractionallySizedBox. + built = _wrapWithSizing(built, node.layout); + final anim = node.animation; if (anim != null && anim.type != AnimationType.none) { built = AnimationModifier(animation: anim, child: built); @@ -99,4 +118,43 @@ class NativeDisplayRenderer extends StatelessWidget { ElementType.html => HtmlElement(node: node, style: style, evaluator: evaluator), }; } + + /// Wraps [child] with an [AspectRatio] or [SizedBox] for fixed/ratio sizing. + /// + /// Mirrors the Android/iOS rule: + /// - AR is applied unless BOTH width AND height are fixed (dp/sp/px). + /// - Percent is NOT treated as "fixed", so AR wins over percent dimensions. + /// + /// Percent sizing is handled by the parent container (not here) to avoid + /// using FractionallySizedBox inside Stack/Positioned where constraints are loose. + Widget _wrapWithSizing(Widget child, Layout? layout) { + if (layout == null) return child; + + final w = layout.width; + final h = layout.height; + + final wIsFixed = w != null && + w.special == null && + w.unit != DimensionUnit.percent && + w.value > 0; + final hIsFixed = h != null && + h.special == null && + h.unit != DimensionUnit.percent && + h.value > 0; + + final ar = layout.aspectRatio; + if (ar != null && ar > 0 && !(wIsFixed && hIsFixed)) { + return AspectRatio(aspectRatio: ar, child: child); + } + + if (wIsFixed || hIsFixed) { + return SizedBox( + width: wIsFixed ? w.value : null, + height: hIsFixed ? h.value : null, + child: child, + ); + } + + return child; + } } diff --git a/flutter/lib/src/renderer/native_display_view.dart b/flutter/lib/src/renderer/native_display_view.dart index 94e985e..241ed49 100644 --- a/flutter/lib/src/renderer/native_display_view.dart +++ b/flutter/lib/src/renderer/native_display_view.dart @@ -37,54 +37,108 @@ class NativeDisplayView extends StatelessWidget { if (root == null) return const SizedBox.shrink(); final evaluator = VariableEvaluator(config.variables); + final screenHeight = MediaQuery.sizeOf(context).height; - Widget buildContent(double availableWidth, double availableHeight) { - return RootHeightScope( - rootHeight: availableHeight, - child: ResolvedStylesScope( - styles: _resolvedStyles, - child: NativeDisplayRenderer( - node: root, - evaluator: evaluator, - actionListener: actionListener, - componentListener: componentListener, + return LayoutBuilder( + builder: (context, constraints) { + final layouterWidth = + constraints.maxWidth.isInfinite ? screenHeight : constraints.maxWidth; + + final effectiveRootWidth = _effectiveWidth(root.layout, layouterWidth); + final rootHeight = + _computeRootHeight(root.layout, effectiveRootWidth, constraints, screenHeight); + + Widget child = RootHeightScope( + rootHeight: rootHeight, + child: ResolvedStylesScope( + styles: _resolvedStyles, + child: NativeDisplayRenderer( + node: root, + evaluator: evaluator, + actionListener: actionListener, + componentListener: componentListener, + ), ), - ), - ); - } + ); + + child = _applyRootSizing(child, root.layout, effectiveRootWidth, rootHeight, screenHeight); + + return child; + }, + ); + } + + Widget _applyRootSizing( + Widget child, + Layout? layout, + double effectiveRootWidth, + double rootHeight, + double screenHeight, + ) { + if (layout == null) return child; + + final rw = layout.width; + final rh = layout.height; + final ar = layout.aspectRatio; + final hasAR = ar != null && ar > 0; - final needsLayout = _needsLayoutBuilder(root.layout); + // AR takes precedence over all percent dimensions — it uses full available width + // and derives height. The AspectRatio widget in _wrapWithSizing handles the + // visual constraint; no additional sizing wrapper is needed here. + if (hasAR) return child; - if (needsLayout) { - return LayoutBuilder( - builder: (context, constraints) => buildContent( - constraints.maxWidth, - constraints.maxHeight.isInfinite ? 0 : constraints.maxHeight, - ), + final hasPercentWidth = + rw != null && rw.special == null && rw.unit == DimensionUnit.percent && rw.value > 0; + + if (hasPercentWidth) { + double? explicitHeight; + if (rh != null && rh.special == null && rh.unit == DimensionUnit.percent && rh.value > 0) { + explicitHeight = screenHeight * rh.value / 100.0; + } + return Align( + alignment: Alignment.topLeft, + child: SizedBox(width: effectiveRootWidth, height: explicitHeight, child: child), ); } - final w = _fixedSize(root.layout?.width); - final h = _fixedSize(root.layout?.height); - return SizedBox( - width: w, - height: h, - child: buildContent(w ?? double.infinity, h ?? 0), - ); + if (rh != null && rh.special == null && rh.unit == DimensionUnit.percent && rh.value > 0) { + return SizedBox(height: screenHeight * rh.value / 100.0, child: child); + } + + return child; } - bool _needsLayoutBuilder(Layout? layout) { - if (layout == null) return false; - if (_isPercent(layout.width) || _isPercent(layout.height)) return true; - if (layout.aspectRatio != null) return true; - return false; + double _effectiveWidth(Layout? layout, double availableWidth) { + // AR present → use full available width; percent is irrelevant. + final ar = layout?.aspectRatio; + if (ar != null && ar > 0) return availableWidth; + + final w = layout?.width; + if (w != null && w.special == null && w.unit == DimensionUnit.percent && w.value > 0) { + return availableWidth * w.value / 100.0; + } + return availableWidth; } - bool _isPercent(Dimension? dim) => - dim != null && dim.special == null && dim.unit == DimensionUnit.percent; + double _computeRootHeight( + Layout? layout, + double effectiveRootWidth, + BoxConstraints constraints, + double screenHeight, + ) { + if (layout == null) return screenHeight; + + final ar = layout.aspectRatio; + if (ar != null && ar > 0) return effectiveRootWidth / ar; + + final h = layout.height; + if (h != null && h.special == null && h.value > 0) { + if (h.unit == DimensionUnit.percent) return screenHeight * h.value / 100.0; + return h.value; + } + + if (!constraints.maxHeight.isInfinite) return constraints.maxHeight; - double? _fixedSize(Dimension? dim) { - if (dim == null || dim.special != null || dim.unit == DimensionUnit.percent) return null; - return dim.value; + return screenHeight; } } diff --git a/flutter/lib/src/renderer/style_applier.dart b/flutter/lib/src/renderer/style_applier.dart index c9bb0e8..9f7de15 100644 --- a/flutter/lib/src/renderer/style_applier.dart +++ b/flutter/lib/src/renderer/style_applier.dart @@ -151,14 +151,12 @@ class StyleApplier { } static Border? _resolveBorder(Style style, double rootHeight) { - final color = ColorParser.parse(style.borderColor); final width = style.borderWidth; - if (color == null && width == null) return null; - final resolvedWidth = width != null ? rootHeight * width / 1000.0 : 1.0; - return Border.all( - color: color ?? const Color(0xFF000000), - width: resolvedWidth, - ); + if (width == null || width <= 0) return null; + final resolvedWidth = rootHeight * width / 1000.0; + if (resolvedWidth <= 0) return null; + final color = ColorParser.parse(style.borderColor) ?? const Color(0xFF000000); + return Border.all(color: color, width: resolvedWidth); } static List? _resolveShadow(Style style) { From 15b36c3c00e08b3f9bf2ed6cc23b7639fb1d7d70 Mon Sep 17 00:00:00 2001 From: CTLalit <144685420+CTLalit@users.noreply.github.com> Date: Wed, 27 May 2026 03:38:36 +0530 Subject: [PATCH 10/12] SDK-5835: Document aspectRatio sizing resolution priority across all knowledge bases MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codifies the cross-platform rule discovered while debugging the Flutter renderer: when aspectRatio is set on a node, it takes full available parent width and derives height = parentWidth / aspectRatio. Any width.percent (or height.percent) value is ignored. The rule is identical on all three platforms: - Android: Compose applies aspectRatio() modifier before fillMaxWidth(fraction); the AR modifier locks minW=parentWidth, making fillMaxWidth ineffective. - iOS: resolveRootWidth() guard returns parentWidth when aspectRatio > 0, regardless of percent. Documented in NativeDisplayRenderer.swift line 244. - Flutter: _effectiveWidth returns availableWidth when layout.aspectRatio > 0; _applyRootSizing short-circuits when AR is set. aspectRatio is skipped only when BOTH width AND height are simultaneously fixed (dp/sp/px). Percent dimensions are never treated as "fixed" for this check. Files updated: - CLAUDE.md — layout system section - .claude/reference/CLAUDE_CODE_REFERENCE_ACTUAL.md — layout model + priority table - .claude/reference/JSON_STRUCTURE_REFERENCE.md — How it works + Priority Rules sections - docs/JSON_STRUCTURE_REFERENCE.md — same (kept in sync) - docs/BACKEND_PAYLOAD_SPEC.md — per-platform mechanism table - .claude/agents/android-sdk/knowledge/rendering-pipeline.md — Stage 4 precedence rule - .claude/agents/android-sdk/knowledge/gotchas.md — new gotcha entry - .claude/agents/ios-sdk/knowledge/architecture.md — Swift code evidence - .claude/agents/flutter-sdk/knowledge/rendering-pipeline.md — root sizing implementation - .claude/agents/flutter-sdk/knowledge/architecture.md — NativeDisplayView description - .claude/agents/testing/knowledge/json-generation-rules.md — Section 6 Aspect Ratio Co-Authored-By: Claude Sonnet 4.6 --- .claude/agents/android-sdk.md | 7 +- .../agents/android-sdk/knowledge/gotchas.md | 15 ++++ .../knowledge/rendering-pipeline.md | 29 ++++++++ .../flutter-sdk/knowledge/architecture.md | 36 ++++----- .../knowledge/rendering-pipeline.md | 73 ++++++++++++++----- .claude/agents/ios-sdk.md | 3 + .../agents/ios-sdk/knowledge/architecture.md | 23 ++++++ .../knowledge/json-generation-rules.md | 17 ++++- .../reference/CLAUDE_CODE_REFERENCE_ACTUAL.md | 16 ++++ .claude/reference/JSON_STRUCTURE_REFERENCE.md | 35 +++++++-- CLAUDE.md | 19 ++++- docs/BACKEND_PAYLOAD_SPEC.md | 35 +++++++++ docs/JSON_STRUCTURE_REFERENCE.md | 32 ++++++-- 13 files changed, 277 insertions(+), 63 deletions(-) diff --git a/.claude/agents/android-sdk.md b/.claude/agents/android-sdk.md index b020f3c..a46d99a 100644 --- a/.claude/agents/android-sdk.md +++ b/.claude/agents/android-sdk.md @@ -35,7 +35,7 @@ The system prompt below covers the rules you need for most tasks. If you hit som - Kotlin and kotlinx.serialization for JSON parsing - Android architecture patterns, state management - Compose rendering optimization and recomposition control -- RTL/LTR handling, accessibility, API compatibility (API 21+) +- RTL/LTR handling, accessibility, API compatibility (API 23+, `minSdk = 23`) ## SDK File Structure ``` @@ -141,7 +141,7 @@ LazyColumn { items(children, key = { it.id }) { child -> RenderNode(child, resol - **Missing variables**: silently return empty string — log a warning - **Video leak**: always release ExoPlayer in `DisposableEffect { onDispose { player.release() } }` - **Background animations**: need separate composable wrappers, not inline modifiers -- **API 21-23**: shadow rendering behaves differently +- **API 23 vs 24+**: shadow elevation rendering behaves differently — test on API 23 emulator - **Circular node references**: will cause infinite recursion — validate before rendering - **Float vs Int in JSON**: `fontSize` is `Float` — use a custom serializer if needed @@ -157,11 +157,14 @@ LazyColumn { items(children, key = { it.id }) { child -> RenderNode(child, resol ## What You Do NOT Do - Modify iOS code → delegate to `ios-sdk` agent +- Modify Flutter plugin Dart code → delegate to `flutter-sdk` agent - Modify sample apps → delegate to `android-sample` agent - Make architectural decisions without user approval - Make breaking API changes without discussion ## Collaboration - Coordinate with `ios-sdk` agent for cross-platform parity +- Coordinate with `flutter-sdk` agent for cross-platform parity — the Flutter plugin replicates Android rendering logic in Dart; if you change rendering behaviour, notify `flutter-sdk` to match - Notify `android-sample` agent of breaking SDK changes - Hand failing tests to `testing` agent for reproduction cases +- Android bridge code inside `flutter/android/` is owned jointly with `flutter-sdk` agent — coordinate on MethodChannel method names and Core SDK selector names diff --git a/.claude/agents/android-sdk/knowledge/gotchas.md b/.claude/agents/android-sdk/knowledge/gotchas.md index 183a22a..7e502db 100644 --- a/.claude/agents/android-sdk/knowledge/gotchas.md +++ b/.claude/agents/android-sdk/knowledge/gotchas.md @@ -157,6 +157,21 @@ Row( ## Dimension Calculation Issues +### Problem: Assuming percent width is respected when aspectRatio is set + +```kotlin +// JSON: {"width": {"value": 80, "unit": "percent"}, "aspectRatio": 1.777} + +// ❌ Wrong assumption: card renders at 80% width +// ✅ Actual: card renders at FULL parent width, height = parentWidth / 1.777 +``` + +**Why**: `applySizing` in `ModifierExtensions.kt` applies `aspectRatio` modifier BEFORE `fillMaxWidth(fraction)`. The AR modifier locks constraints to `{minW=parentWidth, H=parentWidth/ratio}`. The subsequent `fillMaxWidth(0.8f)` receives `minW=parentWidth` and cannot shrink the width — so the 80% has zero effect. + +**Rule**: `aspectRatio` overrides all percent dimensions. Percent is only respected when no AR is set. Fixed (dp/px) dims override AR only when BOTH width AND height are fixed simultaneously. + +See `ModifierExtensions.kt:109-118` for the exact implementation. + ### Problem: Percent Dimension Without Parent Size ```kotlin diff --git a/.claude/agents/android-sdk/knowledge/rendering-pipeline.md b/.claude/agents/android-sdk/knowledge/rendering-pipeline.md index be13630..ec35ffd 100644 --- a/.claude/agents/android-sdk/knowledge/rendering-pipeline.md +++ b/.claude/agents/android-sdk/knowledge/rendering-pipeline.md @@ -173,6 +173,35 @@ val text = node.bindings?.get("text")?.let { template -> **Output**: Compose Modifier with size constraints **Responsibility**: Convert dimensions to actual pixel values +### aspectRatio Precedence Rule (Critical) + +`aspectRatio` is applied as a Compose modifier **before** width and height modifiers (`applySizing` in `ModifierExtensions.kt`): + +```kotlin +// AspectRatio applied FIRST +if (layout.aspectRatio != null && layout.aspectRatio > 0 && !(hasFixedWidth && hasFixedHeight)) { + modifier = modifier.aspectRatio(layout.aspectRatio, matchHeightConstraintsFirst = hasFixedHeight) +} +// Then width +modifier = modifier.fillMaxWidth(width.value / 100f) // for percent +// Then height +``` + +**Effect**: `Modifier.aspectRatio(ratio)` locks constraints to `{W=parentWidth, H=parentWidth/ratio}`. The subsequent `fillMaxWidth(fraction)` receives `minW=parentWidth` and cannot shrink — so **percent width has zero effect when AR is present**. + +### Sizing Resolution Priority + +| Scenario | Result | +|---|---| +| Both width AND height fixed (dp/px) | `aspectRatio` skipped; explicit dims win | +| Only height fixed (dp/px) | width = `fixedHeight × AR` (`matchHeightConstraintsFirst=true`) | +| Only width fixed (dp/px) | height = `fixedWidth / AR` | +| width is percent + AR | **percent ignored**; uses full parent width; height = `parentWidth / AR` | +| height is percent + AR | AR-derived height; percent height ignored | +| No explicit width or height | full parent width; height = `parentWidth / AR` | + +> Note: `hasFixedWidth`/`hasFixedHeight` check `unit in [DP, PX]` — SP and PERCENT are NOT considered "fixed" for the AR skip condition. + ### Dimension Types ```kotlin diff --git a/.claude/agents/flutter-sdk/knowledge/architecture.md b/.claude/agents/flutter-sdk/knowledge/architecture.md index ca168b4..84b2c0f 100644 --- a/.claude/agents/flutter-sdk/knowledge/architecture.md +++ b/.claude/agents/flutter-sdk/knowledge/architecture.md @@ -101,29 +101,23 @@ factory NativeDisplayNode.fromJson(Map json) { ### 3. Rendering Layer (`src/renderer/`) -**NativeDisplayView** — the public entry-point `StatelessWidget`: -```dart -class NativeDisplayView extends StatelessWidget { - final NativeDisplayConfig config; - final NativeDisplayActionListener? actionListener; - - // Pre-resolve styles once — never inside build() - final Map _resolvedStyles; - - NativeDisplayView({super.key, required this.config, this.actionListener}) - : _resolvedStyles = StyleResolver.resolve(config); - - @override - Widget build(BuildContext context) => NativeDisplayRenderer( - node: config.root, - config: config, - resolvedStyles: _resolvedStyles, - actionListener: actionListener, - ); -} +**NativeDisplayView** — the public entry-point `StatelessWidget`. Uses `LayoutBuilder` to get available width, then: +- Pre-resolves all styles once in constructor (never inside `build()`) +- Computes `effectiveRootWidth` and `rootHeight` for percent font/border resolution +- Applies root sizing via `_applyRootSizing` + +**Root sizing rule — aspectRatio takes full width (critical)**: +When `aspectRatio` is set on the root node, `effectiveRootWidth = availableWidth` (full parent width — percent is ignored). Height = `availableWidth / aspectRatio`. The `AspectRatio` Flutter widget in `NativeDisplayRenderer._wrapWithSizing` handles the visual constraint; no explicit `Align+SizedBox` wrapper is added. + +``` +layout.aspectRatio present → full parent width, height = parentWidth / AR +layout.width.percent only → Align + SizedBox(width = parentWidth * pct/100) +neither → no wrapping; child fills slot naturally ``` -**NativeDisplayRenderer** — recursive `_buildNode()` dispatches to container or element renderers. +This matches Android (Compose modifier ordering) and iOS (guard returning parentWidth for percent when AR set). See `rendering-pipeline.md` for the full priority table. + +**NativeDisplayRenderer** — recursive dispatch to container or element renderers. Applies `AspectRatio` widget when `layout.aspectRatio > 0` and not both dimensions fixed. ### 4. Bridge Layer (`src/bridge/`) diff --git a/.claude/agents/flutter-sdk/knowledge/rendering-pipeline.md b/.claude/agents/flutter-sdk/knowledge/rendering-pipeline.md index b03d855..0e80b08 100644 --- a/.claude/agents/flutter-sdk/knowledge/rendering-pipeline.md +++ b/.claude/agents/flutter-sdk/knowledge/rendering-pipeline.md @@ -178,31 +178,64 @@ class DimensionCalculator { if (dim.special == 'match_parent') return parentSize; if (dim.special == 'wrap_content') return null; // null → intrinsic size if (dim.unit == 'percent') return parentSize * dim.value / 100; - if (dim.aspectRatio != null) return null; // caller handles aspect ratio return dim.value; // dp/sp/px — treat as logical pixels } } +``` -// Usage: always wrap in LayoutBuilder when percent dimensions are used -Widget buildConstrainedNode(NativeDisplayNode node, List? children) { - return LayoutBuilder( - builder: (context, constraints) { - final w = DimensionCalculator.resolve(node.layout.width, constraints.maxWidth, rootHeight); - final h = DimensionCalculator.resolve(node.layout.height, constraints.maxHeight, rootHeight); - - // Handle aspect ratio - Widget child = buildContent(node, children); - if (node.layout.width?.aspectRatio != null) { - child = AspectRatio( - aspectRatio: node.layout.width!.aspectRatio!, - child: child, - ); - } - - return SizedBox(width: w, height: h, child: child); - }, - ); +## Root Sizing — aspectRatio Precedence Rule + +**When `aspectRatio` is set on the root, it uses full available parent width and ignores any `width.percent` value.** + +This matches Android (Compose `aspectRatio` modifier locks `{W=parentWidth, H=parentWidth/ratio}` before `fillMaxWidth(fraction)` applies) and iOS (explicit guard returning `parentWidth` for percent when AR set). + +### Implementation in `NativeDisplayView` + +```dart +// _effectiveWidth: AR present → full width, percent ignored +double _effectiveWidth(Layout? layout, double availableWidth) { + final ar = layout?.aspectRatio; + if (ar != null && ar > 0) return availableWidth; // AR wins, ignore percent + final w = layout?.width; + if (w != null && w.special == null && w.unit == DimensionUnit.percent && w.value > 0) { + return availableWidth * w.value / 100.0; + } + return availableWidth; } + +// _computeRootHeight: AR → fullWidth / AR (not percentWidth / AR) +double _computeRootHeight(Layout? layout, double effectiveRootWidth, BoxConstraints constraints, double screenHeight) { + if (layout == null) return screenHeight; + final ar = layout.aspectRatio; + if (ar != null && ar > 0) return effectiveRootWidth / ar; // effectiveRootWidth = fullWidth when AR set + final h = layout.height; + if (h != null && h.special == null && h.value > 0) { + if (h.unit == DimensionUnit.percent) return screenHeight * h.value / 100.0; + return h.value; + } + if (!constraints.maxHeight.isInfinite) return constraints.maxHeight; + return screenHeight; +} + +// _applyRootSizing: AR → return child unchanged; AspectRatio widget handles visual constraint +Widget _applyRootSizing(Widget child, Layout? layout, ...) { + if (layout == null) return child; + final ar = layout.aspectRatio; + if (ar != null && ar > 0) return child; // AR takes full width; no Align+SizedBox wrapper + // ... percent width/height sizing applied only when no AR ... +} +``` + +### aspectRatio sizing priority (all nodes, all platforms) + +| Scenario | Result | +|---|---| +| Both width AND height fixed (dp/sp/px) | `aspectRatio` skipped | +| Only height fixed (dp/sp/px) | width = `fixedHeight × AR` | +| Only width fixed (dp/sp/px) | height = `fixedWidth / AR` | +| width is percent + AR | **percent ignored**; uses full parent width; height = `parentWidth / AR` | +| height is percent + AR | AR-derived height; percent height ignored | +| No explicit width or height | full parent width; height = `parentWidth / AR` | ``` --- diff --git a/.claude/agents/ios-sdk.md b/.claude/agents/ios-sdk.md index 9306e1f..29a1ac9 100644 --- a/.claude/agents/ios-sdk.md +++ b/.claude/agents/ios-sdk.md @@ -193,11 +193,14 @@ When implementing features: ## What You Do NOT Do - Modify Android code → delegate to `android-sdk` agent +- Modify Flutter plugin Dart code → delegate to `flutter-sdk` agent - Modify sample apps → delegate to `ios-sample` agent - Make architectural decisions without user approval - Make breaking API changes without discussion ## Collaboration - Coordinate with `android-sdk` agent for cross-platform parity +- Coordinate with `flutter-sdk` agent for cross-platform parity — the Flutter plugin replicates iOS rendering logic in Dart; if you change rendering behaviour, notify `flutter-sdk` to match - Notify `ios-sample` agent of breaking SDK changes - Hand failing tests to `testing` agent for reproduction cases +- iOS bridge code inside `flutter/ios/` is owned jointly with `flutter-sdk` agent — coordinate on FlutterMethodChannel method names and Core SDK selector names (`record*` vs Android's `push*`) diff --git a/.claude/agents/ios-sdk/knowledge/architecture.md b/.claude/agents/ios-sdk/knowledge/architecture.md index 27b048a..8a8f46a 100644 --- a/.claude/agents/ios-sdk/knowledge/architecture.md +++ b/.claude/agents/ios-sdk/knowledge/architecture.md @@ -38,6 +38,29 @@ struct NativeDisplayConfig: Codable { - **Template Evaluation**: Variable interpolation - **Layout Calculation**: Dimension resolution, RTL support +#### aspectRatio Sizing Priority (critical — matches Android/Flutter) + +When `aspectRatio` is set, width/height percent values are **ignored**. The node uses full available parent width; height = `parentWidth / aspectRatio`. Implementation in `NativeDisplayRenderer.swift`: + +```swift +case .percent: + // Aspect ratio present → percent is ignored, width fills parent. + guard (layout?.aspectRatio ?? 0) <= 0 else { return parentWidth } + return parentWidth * dim.value / 100 +``` + +And for the root: +```swift +} else if let ar = config.root.layout?.aspectRatio, ar > 0 { + // .frame(maxWidth: .infinity) fills parent width + // .aspectRatio(.fit) derives height = width / ar + .aspectRatio(ar, contentMode: .fit) + .frame(maxWidth: .infinity) +} +``` + +`aspectRatio` is skipped only when **both** width AND height are fixed (non-percent, non-nil special). See `.claude/reference/CLAUDE_CODE_REFERENCE_ACTUAL.md` for the full priority table. + ### 3. UI Rendering (`ios/Sources/Views/`) - `NativeDisplayView` - Main entry point - `ContainerView` - Renders containers diff --git a/.claude/agents/testing/knowledge/json-generation-rules.md b/.claude/agents/testing/knowledge/json-generation-rules.md index e15cf64..957d5ac 100644 --- a/.claude/agents/testing/knowledge/json-generation-rules.md +++ b/.claude/agents/testing/knowledge/json-generation-rules.md @@ -106,17 +106,28 @@ This is the COMPLETE specification for generating valid Native Display JSON conf ### 6. Aspect Ratio -Automatically calculates one dimension from the other: +Automatically calculates one dimension from the other. **When `aspectRatio` is set, percent width is ignored — the node uses full parent width.** ```json { "layout": { - "width": {"value": 100, "unit": "percent"}, - "aspectRatio": 1.5 // width / height + "width": {"value": 80, "unit": "percent"}, + "aspectRatio": 1.777 } } ``` +> ⚠️ The above renders at **full parent width** (not 80%), height = parentWidth / 1.777. +> `width.percent` is overridden by `aspectRatio` on all platforms (Android, iOS, Flutter). +> To intentionally constrain to 80% width without AR, omit `aspectRatio`. + +**aspectRatio priority rules:** +- Both width AND height fixed (dp/sp/px) → AR skipped, explicit dims win +- Only width fixed (dp/sp/px) + AR → height = fixedWidth / AR +- Only height fixed (dp/sp/px) + AR → width = fixedHeight × AR +- Any percent dimension + AR → **percent ignored, full parent width, height = parentWidth / AR** +- No explicit dims + AR → full parent width, height = parentWidth / AR + **Common ratios:** - `1.0` - Square (1:1) - `1.777` - Widescreen (16:9) diff --git a/.claude/reference/CLAUDE_CODE_REFERENCE_ACTUAL.md b/.claude/reference/CLAUDE_CODE_REFERENCE_ACTUAL.md index d669dba..20be6ea 100644 --- a/.claude/reference/CLAUDE_CODE_REFERENCE_ACTUAL.md +++ b/.claude/reference/CLAUDE_CODE_REFERENCE_ACTUAL.md @@ -54,6 +54,7 @@ Layout { offset?: Offset (for positioning in Box/Stack) padding?: Spacing arrangement?: ChildArrangement (for container child spacing) + aspectRatio?: Float // width / height ratio } ``` @@ -68,6 +69,21 @@ Dimension { } ``` +### aspectRatio Sizing Resolution Priority + +`aspectRatio` is applied **before** explicit width/height. Rules in priority order: + +| Scenario | Result | +|---|---| +| Both width AND height fixed (dp/sp/px) | `aspectRatio` skipped; explicit dims win | +| Only height fixed (dp/sp/px) | width = `fixedHeight × aspectRatio` | +| Only width fixed (dp/sp/px) | height = `fixedWidth / aspectRatio` | +| width is percent (any) + aspectRatio | **percent ignored**; uses full parent width; height = `parentWidth / aspectRatio` | +| height is percent + aspectRatio | AR-derived height used; percent height ignored | +| No explicit width or height | uses full parent width; height = `parentWidth / aspectRatio` | + +> **⚠️ `width.percent` is ignored when `aspectRatio` is present.** This is identical on Android (Compose modifier ordering), iOS (guard check), and Flutter (`_effectiveWidth` returns `availableWidth` when AR set). + ### Offset Object (For Positioning) ```kotlin Offset { diff --git a/.claude/reference/JSON_STRUCTURE_REFERENCE.md b/.claude/reference/JSON_STRUCTURE_REFERENCE.md index 3ea0ca9..14c381e 100644 --- a/.claude/reference/JSON_STRUCTURE_REFERENCE.md +++ b/.claude/reference/JSON_STRUCTURE_REFERENCE.md @@ -449,6 +449,12 @@ This means a minimal dimension can be: {"value": 50, "unit": "percent"} ``` +> **⚠️ Dashboard constraint — `percent` and `aspectRatio` only** +> +> The CleverTap dashboard generates JSON using **only** `percent` dimensions and `aspectRatio` for layout sizing. Fixed units (`dp`, `sp`, `px`) and special values (`wrap_content`, `match_parent`) are SDK-only features — they exist to support programmatic JSON creation and backward compatibility, but are **not emitted by the dashboard**. +> +> **Implication for new platform implementations**: All dimension types must still be correctly parsed and rendered (for hand-authored JSON and tests), but in production the renderer will almost exclusively receive `percent` + `aspectRatio`. Design tests accordingly and prioritise these paths. + ### Layout Object ```json @@ -513,9 +519,11 @@ Aspect ratios automatically calculate one dimension based on the other, maintain **How it works:** - `aspectRatio` = width / height -- If you specify `width` + `aspectRatio`, height is calculated automatically -- If you specify `height` + `aspectRatio`, width is calculated automatically -- Common ratios: `1.0` (square), `1.77` (16:9), `0.75` (3:4 portrait) +- If `width` is **fixed (dp/sp/px)** + `aspectRatio`: height = fixedWidth / aspectRatio +- If `height` is **fixed (dp/sp/px)** + `aspectRatio`: width = fixedHeight × aspectRatio +- If `width` is **percent** + `aspectRatio`: **percent is ignored**; node uses full parent width; height = parentWidth / aspectRatio +- If no explicit dimensions + `aspectRatio`: uses full parent width; height = parentWidth / aspectRatio +- Common ratios: `1.0` (square), `1.777` (16:9), `0.75` (3:4 portrait) ### Aspect Ratio Examples @@ -559,12 +567,23 @@ Aspect ratios automatically calculate one dimension based on the other, maintain } ``` -### Aspect Ratio Priority Rules +### Aspect Ratio Sizing Resolution Priority + +`aspectRatio` is applied **before** explicit width/height constraints. In priority order: + +1. **Both width AND height are fixed (dp/sp/px)**: `aspectRatio` is **skipped**; explicit dimensions win. +2. **Only height is fixed (dp/sp/px)**: `aspectRatio` derives width = `fixedHeight × aspectRatio`. +3. **Only width is fixed (dp/sp/px)**: `aspectRatio` derives height = `fixedWidth / aspectRatio`. +4. **Width is percent + aspectRatio**: uses **full available parent width** (percent is ignored); height = `parentWidth / aspectRatio`. +5. **Height is percent + aspectRatio**: AR-derived height is used (percent height is ignored). +6. **No explicit width or height**: uses full parent width; height = `parentWidth / aspectRatio`. -1. **Both dimensions specified**: `aspectRatio` is ignored -2. **Width + aspectRatio**: Height is calculated as `width / aspectRatio` -3. **Height + aspectRatio**: Width is calculated as `height * aspectRatio` -4. **Only aspectRatio**: Depends on parent constraints +> **⚠️ Critical**: A percent width does **not** constrain a node when `aspectRatio` is present. +> `"width": {"value": 80, "unit": "percent"}, "aspectRatio": 1.777` renders at **full parent width**, not 80%. +> +> **Why**: Android applies `aspectRatio` modifier before `fillMaxWidth(fraction)` — the AR modifier locks constraints to `{W=parentWidth, H=parentWidth/ratio}`, making the subsequent `fillMaxWidth` have no effect. iOS has the same behavior (explicit `guard ar <= 0` check returns parentWidth for percent). Flutter matches this: `_effectiveWidth` returns full `availableWidth` when AR is present. +> +> This rule is consistent across Android, iOS, and Flutter. --- diff --git a/CLAUDE.md b/CLAUDE.md index 3f32823..8fb099b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -15,6 +15,11 @@ clevertap-native-ui-kit/ ├── ios/ # iOS SDK (Swift + SwiftUI) │ └── Sources/ # Core SDK source ├── ios-sample/ # iOS sample app +├── flutter/ # Flutter plugin (Dart + platform bridges) +│ ├── lib/ # Pure Dart renderer + public API +│ ├── android/ # Android MethodChannel bridge +│ └── ios/ # iOS FlutterMethodChannel bridge +├── flutter-sample/ # Flutter sample app ├── docs/ # Documentation └── .claude/ ├── agents/ # Subagent definitions @@ -77,6 +82,10 @@ theme (optional) | styleClasses (optional) | variables (optional) | root (requir **Dimension units**: `dp` `sp` `percent` `px` | **Special**: `wrap_content` `match_parent` +> **⚠️ Dashboard constraint — `percent` and `aspectRatio` only**: The CleverTap dashboard generates JSON using only `percent` dimensions and `aspectRatio`. Fixed units (`dp`, `sp`, `px`) and specials (`wrap_content`, `match_parent`) are SDK-only — they exist for programmatic JSON and backward compatibility but are **not emitted by the dashboard**. New platform implementations must support all types correctly, but real dashboard payloads will only ever contain `percent` + `aspectRatio`. + +> **⚠️ aspectRatio overrides percent dimensions**: When `aspectRatio` is set on a node, the node uses **full available parent width** and derives height = `parentWidth / aspectRatio`. Any `width.percent` or `height.percent` value is **ignored**. `aspectRatio` is only skipped when BOTH `width` AND `height` are fixed (dp/sp/px) simultaneously. This is consistent across Android (Compose modifier ordering), iOS (SwiftUI frame), and Flutter (AspectRatio widget). + **Arrangement strategies** (all lowercase in JSON): `spaced` `space_between` `space_evenly` `space_around` `start` `center` `end` > Only `spaced` uses a `spacing` field. All other strategies have no spacing fields. @@ -147,13 +156,16 @@ Specialized subagents for domain-focused work: |-------|--------| | `android-sdk` | Android SDK — Kotlin/Compose implementation | | `ios-sdk` | iOS SDK — Swift/SwiftUI implementation | +| `flutter-sdk` | Flutter plugin — Dart renderer + platform channel bridge | | `android-sample` | Android demo apps (Compose + XML) | | `ios-sample` | iOS demo app (SwiftUI) | +| `flutter-sample` | Flutter demo app | | `testing` | Test JSON generation, Roborazzi screenshots, visual comparison | **Invoking agents explicitly:** ``` "Using the android-sdk agent, implement the GRID container from spec 013" +"Using the flutter-sdk agent, implement the GALLERY container in Dart" "Using the testing agent, generate 25 GALLERY container test variations" ``` @@ -161,7 +173,8 @@ Specialized subagents for domain-focused work: - SDK agents do not touch sample apps — delegate to sample agents - Sample agents do not touch SDK code — delegate to SDK agents - Testing agent does not fix bugs — hands issues to SDK agents -- Cross-platform features need both `android-sdk` AND `ios-sdk` +- Cross-platform features need `android-sdk`, `ios-sdk`, AND `flutter-sdk` +- `flutter-sdk` owns `flutter/lib/` (Dart); platform bridges in `flutter/android/` and `flutter/ios/` are coordinated with `android-sdk` and `ios-sdk` respectively Agent workflows and examples → `.claude/agents/` and `.claude/AGENTS_QUICK_REFERENCE.md` @@ -173,12 +186,16 @@ Agent workflows and examples → `.claude/agents/` and `.claude/AGENTS_QUICK_REF **iOS**: `Codable` structs · SwiftUI rendering · `ios/Sources/` module structure +**Flutter**: `fromJson`/`toJson` Dart models · Flutter widget rendering · `flutter/lib/` package structure + **Commands**: ``` Android build: cd android && ./gradlew build Android test: cd android && ./gradlew test iOS build: cd ios && swift build iOS test: cd ios && swift test +Flutter build: cd flutter && flutter build +Flutter test: cd flutter && flutter test ``` --- diff --git a/docs/BACKEND_PAYLOAD_SPEC.md b/docs/BACKEND_PAYLOAD_SPEC.md index cacae41..0a31abe 100644 --- a/docs/BACKEND_PAYLOAD_SPEC.md +++ b/docs/BACKEND_PAYLOAD_SPEC.md @@ -76,6 +76,41 @@ The value under `native_display_config` (or the platform-specific keys) is a `Na --- +## Layout Dimensions — Dashboard Constraint + +> **⚠️ The dashboard emits only `percent` dimensions and `aspectRatio`.** + +The SDK supports four dimension units (`dp`, `sp`, `px`, `percent`) plus special values (`wrap_content`, `match_parent`). However, the CleverTap dashboard generates JSON using **only** `percent` and `aspectRatio` for all `width` and `height` fields: + +```json +// ✅ What the dashboard emits +"width": {"value": 100, "unit": "percent"} +"aspectRatio": 1.777 + +// ❌ What the dashboard does NOT emit (SDK-only, for programmatic use) +"width": {"value": 300, "unit": "dp"} +"width": {"special": "wrap_content"} +"width": {"special": "match_parent"} +``` + +**Why this matters:** +- **Backend/dashboard teams**: always use `percent` + `aspectRatio` when generating payloads. Avoid emitting `dp`, `px`, `sp`, or `wrap_content`/`match_parent` — these are reserved for SDK-side programmatic JSON. +- **SDK/platform implementers**: all dimension types must be correctly parsed (for test JSON and backward compatibility), but the rendering fast-path for production traffic is exclusively `percent` + `aspectRatio`. Prioritise and stress-test these two cases. + +### aspectRatio overrides percent dimensions + +When `aspectRatio` is set on any node, the `width.percent` value is **ignored** — the node uses the **full available parent width** and derives height = `parentWidth / aspectRatio`. This is consistent across all platforms: + +| Platform | Mechanism | +|---|---| +| Android | `aspectRatio` Compose modifier applied before `fillMaxWidth(fraction)` — AR locks `minW=parentWidth`, so percent has no effect | +| iOS | Explicit guard in `resolveRootWidth`: returns `parentWidth` when `aspectRatio > 0`, regardless of percent | +| Flutter | `_effectiveWidth` returns `availableWidth` (ignoring percent) when `layout.aspectRatio != null && ar > 0` | + +`aspectRatio` is skipped only when **both** `width` AND `height` are explicitly fixed (dp/sp/px). Percent dimensions are never treated as "fixed" for this check. + +--- + ## Payload Samples ### 1. Minimal Payload (shared config, both platforms) diff --git a/docs/JSON_STRUCTURE_REFERENCE.md b/docs/JSON_STRUCTURE_REFERENCE.md index 5a8a377..14a42fb 100644 --- a/docs/JSON_STRUCTURE_REFERENCE.md +++ b/docs/JSON_STRUCTURE_REFERENCE.md @@ -384,6 +384,12 @@ This means a minimal dimension can be: {"value": 50, "unit": "percent"} ``` +> **⚠️ Dashboard constraint — `percent` and `aspectRatio` only** +> +> The CleverTap dashboard generates JSON using **only** `percent` dimensions and `aspectRatio` for layout sizing. Fixed units (`dp`, `sp`, `px`) and special values (`wrap_content`, `match_parent`) are SDK-only features — they exist to support programmatic JSON creation and backward compatibility, but are **not emitted by the dashboard**. +> +> **Implication for new platform implementations**: All dimension types must still be correctly parsed and rendered (for hand-authored JSON and tests), but in production the renderer will almost exclusively receive `percent` + `aspectRatio`. Design tests accordingly and prioritise these paths. + ### Layout Object ```json @@ -448,9 +454,11 @@ Aspect ratios automatically calculate one dimension based on the other, maintain **How it works:** - `aspectRatio` = width / height -- If you specify `width` + `aspectRatio`, height is calculated automatically -- If you specify `height` + `aspectRatio`, width is calculated automatically -- Common ratios: `1.0` (square), `1.77` (16:9), `0.75` (3:4 portrait) +- If `width` is **fixed (dp/sp/px)** + `aspectRatio`: height = fixedWidth / aspectRatio +- If `height` is **fixed (dp/sp/px)** + `aspectRatio`: width = fixedHeight × aspectRatio +- If `width` is **percent** + `aspectRatio`: **percent is ignored**; node uses full parent width; height = parentWidth / aspectRatio +- If no explicit dimensions + `aspectRatio`: uses full parent width; height = parentWidth / aspectRatio +- Common ratios: `1.0` (square), `1.777` (16:9), `0.75` (3:4 portrait) ### Aspect Ratio Examples @@ -494,12 +502,20 @@ Aspect ratios automatically calculate one dimension based on the other, maintain } ``` -### Aspect Ratio Priority Rules +### Aspect Ratio Sizing Resolution Priority + +`aspectRatio` is applied **before** explicit width/height constraints. In priority order: + +1. **Both width AND height are fixed (dp/sp/px)**: `aspectRatio` is **skipped**; explicit dimensions win. +2. **Only height is fixed (dp/sp/px)**: `aspectRatio` derives width = `fixedHeight × aspectRatio`. +3. **Only width is fixed (dp/sp/px)**: `aspectRatio` derives height = `fixedWidth / aspectRatio`. +4. **Width is percent + aspectRatio**: uses **full available parent width** (percent is ignored); height = `parentWidth / aspectRatio`. +5. **Height is percent + aspectRatio**: AR-derived height is used (percent height is ignored). +6. **No explicit width or height**: uses full parent width; height = `parentWidth / aspectRatio`. -1. **Both dimensions specified**: `aspectRatio` is ignored -2. **Width + aspectRatio**: Height is calculated as `width / aspectRatio` -3. **Height + aspectRatio**: Width is calculated as `height * aspectRatio` -4. **Only aspectRatio**: Depends on parent constraints +> **⚠️ Critical**: A percent width does **not** constrain a node when `aspectRatio` is present. +> `"width": {"value": 80, "unit": "percent"}, "aspectRatio": 1.777` renders at **full parent width**, not 80%. +> This is consistent across Android, iOS, and Flutter. --- From 6d39be10d95a887b9d5c25fcb9b19f711f4bae24 Mon Sep 17 00:00:00 2001 From: CTLalit <144685420+CTLalit@users.noreply.github.com> Date: Wed, 27 May 2026 04:25:37 +0530 Subject: [PATCH 11/12] SDK-5837: Move platform channel parsing into Flutter SDK MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add NativeDisplayUnit model (unitId, slotId, config, customExtras, resolvedStyles) - Add NativeDisplayConfigParser with tryParse/parseAll running off main thread via Isolate.run(); falls back to synchronous on Dart < 3.4 - Mirrors 3-strategy extraction from Android NativeDisplayConfigParser.kt: native_display_config key → custom_kv.nd_config string → root key - NativeDisplayView accepts optional pre-resolved resolvedStyles to skip redundant StyleResolver calls when using NativeDisplayConfigParser - Export both new types from the public barrel - Simplify clevertap_integration_screen.dart: remove _deepCast, _extractEntry, _UnitEntry; replace with NativeDisplayConfigParser.parseAll() (4 lines) Co-Authored-By: Claude Sonnet 4.6 --- flutter-sample/.flutter-plugins-dependencies | 2 +- .../screens/clevertap_integration_screen.dart | 95 +++------------ flutter/lib/clevertap_native_display.dart | 2 + .../bridge/native_display_config_parser.dart | 109 ++++++++++++++++++ .../lib/src/models/native_display_unit.dart | 18 +++ .../lib/src/renderer/native_display_view.dart | 98 +++++++++++++--- 6 files changed, 229 insertions(+), 95 deletions(-) create mode 100644 flutter/lib/src/bridge/native_display_config_parser.dart create mode 100644 flutter/lib/src/models/native_display_unit.dart diff --git a/flutter-sample/.flutter-plugins-dependencies b/flutter-sample/.flutter-plugins-dependencies index f4a7a03..1aba744 100644 --- a/flutter-sample/.flutter-plugins-dependencies +++ b/flutter-sample/.flutter-plugins-dependencies @@ -1 +1 @@ -{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"clevertap_native_display","path":"/Users/lalitkumar/StudioProjects/clevertap-native-ui-kit/flutter/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"clevertap_plugin","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/clevertap_plugin-4.0.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_foundation","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.2/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"sqflite_darwin","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/sqflite_darwin-2.4.2/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_ios","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/url_launcher_ios-6.3.4/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"video_player_avfoundation","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/video_player_avfoundation-2.8.4/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"webview_flutter_wkwebview","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.23.0/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"clevertap_native_display","path":"/Users/lalitkumar/StudioProjects/clevertap-native-ui-kit/flutter/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"clevertap_plugin","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/clevertap_plugin-4.0.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_android","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/path_provider_android-2.2.19/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"sqflite_android","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/sqflite_android-2.4.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_android","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/url_launcher_android-6.3.20/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"video_player_android","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/video_player_android-2.8.15/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"webview_flutter_android","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/webview_flutter_android-4.10.1/","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[{"name":"path_provider_foundation","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.2/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"sqflite_darwin","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/sqflite_darwin-2.4.2/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_macos","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/url_launcher_macos-3.2.3/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"video_player_avfoundation","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/video_player_avfoundation-2.8.4/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"webview_flutter_wkwebview","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.23.0/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"linux":[{"name":"path_provider_linux","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_linux","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/url_launcher_linux-3.2.1/","native_build":true,"dependencies":[],"dev_dependency":false}],"windows":[{"name":"path_provider_windows","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/path_provider_windows-2.3.0/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_windows","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/url_launcher_windows-3.1.4/","native_build":true,"dependencies":[],"dev_dependency":false}],"web":[{"name":"clevertap_plugin","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/clevertap_plugin-4.0.0/","dependencies":[],"dev_dependency":false},{"name":"url_launcher_web","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/url_launcher_web-2.4.1/","dependencies":[],"dev_dependency":false},{"name":"video_player_web","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/video_player_web-2.4.0/","dependencies":[],"dev_dependency":false}]},"dependencyGraph":[{"name":"clevertap_native_display","dependencies":["video_player","webview_flutter","url_launcher"]},{"name":"clevertap_plugin","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"sqflite","dependencies":["sqflite_android","sqflite_darwin"]},{"name":"sqflite_android","dependencies":[]},{"name":"sqflite_darwin","dependencies":[]},{"name":"url_launcher","dependencies":["url_launcher_android","url_launcher_ios","url_launcher_linux","url_launcher_macos","url_launcher_web","url_launcher_windows"]},{"name":"url_launcher_android","dependencies":[]},{"name":"url_launcher_ios","dependencies":[]},{"name":"url_launcher_linux","dependencies":[]},{"name":"url_launcher_macos","dependencies":[]},{"name":"url_launcher_web","dependencies":[]},{"name":"url_launcher_windows","dependencies":[]},{"name":"video_player","dependencies":["video_player_android","video_player_avfoundation","video_player_web"]},{"name":"video_player_android","dependencies":[]},{"name":"video_player_avfoundation","dependencies":[]},{"name":"video_player_web","dependencies":[]},{"name":"webview_flutter","dependencies":["webview_flutter_android","webview_flutter_wkwebview"]},{"name":"webview_flutter_android","dependencies":[]},{"name":"webview_flutter_wkwebview","dependencies":[]}],"date_created":"2026-05-27 03:24:35.977293","version":"3.29.0","swift_package_manager_enabled":{"ios":false,"macos":false}} \ No newline at end of file +{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"clevertap_native_display","path":"/Users/lalitkumar/StudioProjects/clevertap-native-ui-kit/flutter/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"clevertap_plugin","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/clevertap_plugin-4.0.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_foundation","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.2/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"sqflite_darwin","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/sqflite_darwin-2.4.2/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_ios","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/url_launcher_ios-6.3.4/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"video_player_avfoundation","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/video_player_avfoundation-2.8.4/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"webview_flutter_wkwebview","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.23.0/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"clevertap_native_display","path":"/Users/lalitkumar/StudioProjects/clevertap-native-ui-kit/flutter/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"clevertap_plugin","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/clevertap_plugin-4.0.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_android","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/path_provider_android-2.2.19/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"sqflite_android","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/sqflite_android-2.4.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_android","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/url_launcher_android-6.3.20/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"video_player_android","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/video_player_android-2.8.15/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"webview_flutter_android","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/webview_flutter_android-4.10.1/","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[{"name":"path_provider_foundation","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.2/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"sqflite_darwin","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/sqflite_darwin-2.4.2/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_macos","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/url_launcher_macos-3.2.3/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"video_player_avfoundation","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/video_player_avfoundation-2.8.4/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"webview_flutter_wkwebview","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.23.0/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"linux":[{"name":"path_provider_linux","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_linux","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/url_launcher_linux-3.2.1/","native_build":true,"dependencies":[],"dev_dependency":false}],"windows":[{"name":"path_provider_windows","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/path_provider_windows-2.3.0/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_windows","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/url_launcher_windows-3.1.4/","native_build":true,"dependencies":[],"dev_dependency":false}],"web":[{"name":"clevertap_plugin","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/clevertap_plugin-4.0.0/","dependencies":[],"dev_dependency":false},{"name":"url_launcher_web","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/url_launcher_web-2.4.1/","dependencies":[],"dev_dependency":false},{"name":"video_player_web","path":"/Users/lalitkumar/.pub-cache/hosted/pub.dev/video_player_web-2.4.0/","dependencies":[],"dev_dependency":false}]},"dependencyGraph":[{"name":"clevertap_native_display","dependencies":["video_player","webview_flutter","url_launcher"]},{"name":"clevertap_plugin","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"sqflite","dependencies":["sqflite_android","sqflite_darwin"]},{"name":"sqflite_android","dependencies":[]},{"name":"sqflite_darwin","dependencies":[]},{"name":"url_launcher","dependencies":["url_launcher_android","url_launcher_ios","url_launcher_linux","url_launcher_macos","url_launcher_web","url_launcher_windows"]},{"name":"url_launcher_android","dependencies":[]},{"name":"url_launcher_ios","dependencies":[]},{"name":"url_launcher_linux","dependencies":[]},{"name":"url_launcher_macos","dependencies":[]},{"name":"url_launcher_web","dependencies":[]},{"name":"url_launcher_windows","dependencies":[]},{"name":"video_player","dependencies":["video_player_android","video_player_avfoundation","video_player_web"]},{"name":"video_player_android","dependencies":[]},{"name":"video_player_avfoundation","dependencies":[]},{"name":"video_player_web","dependencies":[]},{"name":"webview_flutter","dependencies":["webview_flutter_android","webview_flutter_wkwebview"]},{"name":"webview_flutter_android","dependencies":[]},{"name":"webview_flutter_wkwebview","dependencies":[]}],"date_created":"2026-05-27 04:22:11.632942","version":"3.29.0","swift_package_manager_enabled":{"ios":false,"macos":false}} \ No newline at end of file diff --git a/flutter-sample/lib/screens/clevertap_integration_screen.dart b/flutter-sample/lib/screens/clevertap_integration_screen.dart index 6ea6ac0..dc3c10d 100644 --- a/flutter-sample/lib/screens/clevertap_integration_screen.dart +++ b/flutter-sample/lib/screens/clevertap_integration_screen.dart @@ -1,5 +1,3 @@ -import 'dart:convert'; - import 'package:clevertap_plugin/clevertap_plugin.dart'; import 'package:flutter/material.dart'; import 'package:clevertap_native_display/clevertap_native_display.dart'; @@ -14,7 +12,7 @@ class CleverTapIntegrationScreen extends StatefulWidget { class _CleverTapIntegrationScreenState extends State { final _eventController = TextEditingController(); final List _log = []; - final List<_UnitEntry> _units = []; + final List _units = []; // CleverTapPlugin() returns the singleton — needed for instance method registration. final _ct = CleverTapPlugin(); @@ -43,17 +41,13 @@ class _CleverTapIntegrationScreenState extends State } } - void _onUnitsLoaded(List? units) { - if (units == null || units.isEmpty) return; - final parsed = <_UnitEntry>[]; - for (final unit in units) { - final entry = _extractEntry(unit); - if (entry != null) { - parsed.add(entry); - } else { - _addLog('WARN: unit has no recognisable NativeDisplayConfig'); - } - } + Future _onUnitsLoaded(List? rawUnits) async { + if (rawUnits == null || rawUnits.isEmpty) return; + + // Parsing (deep-cast + 3-strategy extraction + style resolution) runs off + // the main thread via Isolate.run() in NativeDisplayConfigParser. + final parsed = await NativeDisplayConfigParser.parseAll(rawUnits); + if (!mounted) return; setState(() { _units @@ -61,61 +55,10 @@ class _CleverTapIntegrationScreenState extends State ..addAll(parsed); _log.add('[${_ts()}] Received ${parsed.length} Native Display unit(s)'); }); - } - /// Platform channel maps arrive as Map at every nesting level. - /// This recursively converts the entire tree to Map. - static Map _deepCast(Map raw) => - raw.map((k, v) => MapEntry(k.toString(), _deepCastValue(v))); - - static dynamic _deepCastValue(dynamic v) { - if (v is Map) return _deepCast(v); - if (v is List) return v.map(_deepCastValue).toList(); - return v; - } - - /// Mirrors the 3-strategy extraction from the old SampleApplication.kt. - _UnitEntry? _extractEntry(dynamic raw) { - if (raw is! Map) return null; - final unit = _deepCast(raw); - final unitId = unit['wzrk_id']?.toString() ?? unit['slot_id']?.toString(); - - // Strategy 1: native_display_config key - final ndRaw = unit['native_display_config']; - if (ndRaw is Map) { - try { - return _UnitEntry(NativeDisplayConfig.fromJson(ndRaw), unitId); - } catch (e) { - _addLog('ERROR parsing native_display_config: $e'); - } + if (parsed.length < rawUnits.length) { + _addLog('WARN: ${rawUnits.length - parsed.length} unit(s) had no recognisable NativeDisplayConfig'); } - - // Strategy 2: custom_kv.nd_config string - final kv = unit['custom_kv']; - if (kv is Map) { - final ndStr = kv['nd_config']; - if (ndStr is String && ndStr.isNotEmpty) { - try { - return _UnitEntry( - NativeDisplayConfig.fromJson(jsonDecode(ndStr) as Map), - unitId, - ); - } catch (e) { - _addLog('ERROR parsing custom_kv.nd_config: $e'); - } - } - } - - // Strategy 3: top-level root key - if (unit.containsKey('root')) { - try { - return _UnitEntry(NativeDisplayConfig.fromJson(unit), unitId); - } catch (e) { - _addLog('ERROR parsing root-level config: $e'); - } - } - - return null; } Future _sendEvent() async { @@ -168,12 +111,6 @@ class _CleverTapIntegrationScreenState extends State } } -class _UnitEntry { - final NativeDisplayConfig config; - final String? unitId; - const _UnitEntry(this.config, this.unitId); -} - // ── Header ──────────────────────────────────────────────────────────────────── class _FireEventHeader extends StatelessWidget { @@ -234,7 +171,7 @@ class _FireEventHeader extends StatelessWidget { // ── Canvas ──────────────────────────────────────────────────────────────────── class _CanvasContent extends StatelessWidget { - final List<_UnitEntry> units; + final List units; final void Function(String) onAction; const _CanvasContent({required this.units, required this.onAction}); @@ -252,16 +189,16 @@ class _CanvasContent extends StatelessWidget { return ListView.builder( itemCount: units.length, itemBuilder: (ctx, i) { - final entry = units[i]; - // DEBUG: outer slot shown in red, inner card in its natural state + final unit = units[i]; return Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: NativeDisplayView( - config: entry.config, + config: unit.config, + resolvedStyles: unit.resolvedStyles, actionListener: (action, nodeId, params) { onAction('ACTION $action on $nodeId'); - if (entry.unitId != null) { - CleverTapPlugin.pushDisplayUnitClickedEvent(entry.unitId!); + if (unit.unitId.isNotEmpty) { + CleverTapPlugin.pushDisplayUnitClickedEvent(unit.unitId); } }, ), diff --git a/flutter/lib/clevertap_native_display.dart b/flutter/lib/clevertap_native_display.dart index 2083012..5eb9f78 100644 --- a/flutter/lib/clevertap_native_display.dart +++ b/flutter/lib/clevertap_native_display.dart @@ -12,5 +12,7 @@ export 'src/models/native_display_node.dart'; export 'src/models/native_display_config.dart'; export 'src/renderer/native_display_view.dart' show NativeDisplayView, NativeDisplayActionListener, NativeDisplayComponentListener; +export 'src/models/native_display_unit.dart'; +export 'src/bridge/native_display_config_parser.dart' show NativeDisplayConfigParser; export 'src/handler/action_handler.dart' show ActionHandler; export 'src/bridge/native_display_bridge.dart' show NativeDisplayBridge; diff --git a/flutter/lib/src/bridge/native_display_config_parser.dart b/flutter/lib/src/bridge/native_display_config_parser.dart new file mode 100644 index 0000000..559b876 --- /dev/null +++ b/flutter/lib/src/bridge/native_display_config_parser.dart @@ -0,0 +1,109 @@ +import 'dart:convert'; +import 'dart:isolate'; + +import '../models/native_display_config.dart'; +import '../models/native_display_unit.dart'; +import '../style/style_resolver.dart'; + +class NativeDisplayConfigParser { + static Future tryParse(dynamic rawUnit) async { + try { + // Dart 3.4+: Isolate.run returns any type when in same isolate group + return await Isolate.run(() => _parseSync(rawUnit)); + } catch (_) { + // Older Dart: fall back to synchronous parse on calling thread + return _parseSync(rawUnit); + } + } + + static Future> parseAll(List rawUnits) async { + try { + return await Isolate.run( + () => rawUnits.map(_parseSync).whereType().toList(), + ); + } catch (_) { + return rawUnits.map(_parseSync).whereType().toList(); + } + } + + // Runs inside isolate — no Flutter/platform dependencies. + static NativeDisplayUnit? _parseSync(dynamic rawUnit) { + if (rawUnit is! Map) return null; + try { + final unit = _deepCast(rawUnit); + + final unitId = unit['wzrk_id']?.toString() ?? unit['slot_id']?.toString() ?? ''; + final slotId = unit['slot_id']?.toString(); + + // Retain non-system keys as custom extras for the client. + final customExtras = Map.from(unit) + ..removeWhere((k, _) => _kSystemKeys.contains(k)); + + NativeDisplayConfig? config; + + // Strategy 1: explicit native_display_config key + final ndRaw = unit['native_display_config']; + if (ndRaw is Map) { + config = NativeDisplayConfig.fromJson(ndRaw); + } + + // Strategy 2: custom_kv.nd_config JSON string + if (config == null) { + final kv = unit['custom_kv']; + if (kv is Map) { + final ndStr = kv['nd_config']; + if (ndStr is String && ndStr.isNotEmpty) { + config = NativeDisplayConfig.fromJson( + jsonDecode(ndStr) as Map, + ); + } + } + } + + // Strategy 3: entire unit is the config (has a 'root' key) + if (config == null && unit.containsKey('root')) { + config = NativeDisplayConfig.fromJson(unit); + } + + if (config == null) return null; + + // Pre-resolve styles once; NativeDisplayView can skip resolution when provided. + final resolvedStyles = StyleResolver().resolveAll( + config.root, + config.theme ?? NDTheme.empty, + config.styleClasses, + ); + + return NativeDisplayUnit( + unitId: unitId, + slotId: slotId, + config: config, + customExtras: customExtras, + resolvedStyles: resolvedStyles, + ); + } catch (_) { + return null; + } + } + + static const _kSystemKeys = { + 'wzrk_id', + 'slot_id', + 'native_display_config', + 'custom_kv', + 'wzrk_ttl', + 'wzrk_acct_id', + 'wzrk_ts', + }; + + // Platform channels deliver Map at every nesting level. + // Recursively convert to Map so fromJson works. + static Map _deepCast(Map raw) => + raw.map((k, v) => MapEntry(k.toString(), _deepCastValue(v))); + + static dynamic _deepCastValue(dynamic v) { + if (v is Map) return _deepCast(v); + if (v is List) return v.map(_deepCastValue).toList(); + return v; + } +} diff --git a/flutter/lib/src/models/native_display_unit.dart b/flutter/lib/src/models/native_display_unit.dart new file mode 100644 index 0000000..fb8b729 --- /dev/null +++ b/flutter/lib/src/models/native_display_unit.dart @@ -0,0 +1,18 @@ +import 'native_display_config.dart'; +import 'style.dart'; + +class NativeDisplayUnit { + final String unitId; + final String? slotId; + final NativeDisplayConfig config; + final Map customExtras; + final Map resolvedStyles; + + const NativeDisplayUnit({ + required this.unitId, + this.slotId, + required this.config, + this.customExtras = const {}, + this.resolvedStyles = const {}, + }); +} diff --git a/flutter/lib/src/renderer/native_display_view.dart b/flutter/lib/src/renderer/native_display_view.dart index 241ed49..309f933 100644 --- a/flutter/lib/src/renderer/native_display_view.dart +++ b/flutter/lib/src/renderer/native_display_view.dart @@ -14,29 +14,85 @@ typedef NativeDisplayActionListener = void Function( typedef NativeDisplayComponentListener = bool Function( String event, String nodeId, Map? params); -class NativeDisplayView extends StatelessWidget { +class NativeDisplayView extends StatefulWidget { final NativeDisplayConfig config; + + /// Pre-resolved styles from [NativeDisplayConfigParser]. When provided, + /// the view skips [StyleResolver] entirely — avoiding redundant computation. + final Map? resolvedStyles; + final NativeDisplayActionListener? actionListener; final NativeDisplayComponentListener? componentListener; - final Map _resolvedStyles; - NativeDisplayView({ + const NativeDisplayView({ super.key, required this.config, + this.resolvedStyles, this.actionListener, this.componentListener, - }) : _resolvedStyles = StyleResolver().resolveAll( - config.root, - config.theme ?? NDTheme.empty, - config.styleClasses, - ); + }); + + @override + State createState() => _NativeDisplayViewState(); +} + +class _NativeDisplayViewState extends State { + late Map _resolvedStyles; + + @override + void initState() { + super.initState(); + _resolvedStyles = _resolve(); + } + + @override + void didUpdateWidget(NativeDisplayView old) { + super.didUpdateWidget(old); + if (!identical(old.config, widget.config) || + !identical(old.resolvedStyles, widget.resolvedStyles)) { + _resolvedStyles = _resolve(); + } + } + + Map _resolve() => + widget.resolvedStyles ?? + StyleResolver().resolveAll( + widget.config.root, + widget.config.theme ?? NDTheme.empty, + widget.config.styleClasses, + ); @override Widget build(BuildContext context) { - final root = config.root; + final root = widget.config.root; if (root == null) return const SizedBox.shrink(); - final evaluator = VariableEvaluator(config.variables); + final evaluator = VariableEvaluator(widget.config.variables); + final layout = root.layout; + + // Optimization: when root has only fixed (dp) dimensions and no aspectRatio, + // skip LayoutBuilder — constraints are already known from the JSON values. + if (_hasOnlyFixedDimensions(layout)) { + final fixedWidth = layout!.width!.value; + final fixedHeight = layout.height!.value; + return SizedBox( + width: fixedWidth, + height: fixedHeight, + child: RootHeightScope( + rootHeight: fixedHeight, + child: ResolvedStylesScope( + styles: _resolvedStyles, + child: NativeDisplayRenderer( + node: root, + evaluator: evaluator, + actionListener: widget.actionListener, + componentListener: widget.componentListener, + ), + ), + ), + ); + } + final screenHeight = MediaQuery.sizeOf(context).height; return LayoutBuilder( @@ -44,9 +100,9 @@ class NativeDisplayView extends StatelessWidget { final layouterWidth = constraints.maxWidth.isInfinite ? screenHeight : constraints.maxWidth; - final effectiveRootWidth = _effectiveWidth(root.layout, layouterWidth); + final effectiveRootWidth = _effectiveWidth(layout, layouterWidth); final rootHeight = - _computeRootHeight(root.layout, effectiveRootWidth, constraints, screenHeight); + _computeRootHeight(layout, effectiveRootWidth, constraints, screenHeight); Widget child = RootHeightScope( rootHeight: rootHeight, @@ -55,19 +111,31 @@ class NativeDisplayView extends StatelessWidget { child: NativeDisplayRenderer( node: root, evaluator: evaluator, - actionListener: actionListener, - componentListener: componentListener, + actionListener: widget.actionListener, + componentListener: widget.componentListener, ), ), ); - child = _applyRootSizing(child, root.layout, effectiveRootWidth, rootHeight, screenHeight); + child = _applyRootSizing(child, layout, effectiveRootWidth, rootHeight, screenHeight); return child; }, ); } + bool _hasOnlyFixedDimensions(Layout? layout) { + if (layout == null) return false; + final ar = layout.aspectRatio; + if (ar != null && ar > 0) return false; + final w = layout.width; + final h = layout.height; + if (w == null || h == null) return false; + if (w.special != null || h.special != null) return false; + if (w.unit == DimensionUnit.percent || h.unit == DimensionUnit.percent) return false; + return w.value > 0 && h.value > 0; + } + Widget _applyRootSizing( Widget child, Layout? layout, From 507575c78b7fa89978602fd989000f72f0b96791 Mon Sep 17 00:00:00 2001 From: CTLalit <144685420+CTLalit@users.noreply.github.com> Date: Wed, 27 May 2026 04:25:49 +0530 Subject: [PATCH 12/12] SDK-5838: Production-grade Flutter renderer performance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - NativeDisplayView: StatelessWidget → StatefulWidget; StyleResolver.resolveAll() now runs once in initState/didUpdateWidget, not on every parent rebuild; added fixed-dimension shortcut that bypasses LayoutBuilder when root has only dp dimensions (no percent, no aspectRatio) - style_applier.dart: for solid backgrounds, bake opacity into Color.withValues() instead of wrapping the whole subtree in Opacity widget (avoids saveLayer) - image_element.dart: replace Image.network() with CachedNetworkImage for static images (GIFs keep Image.network for animation fidelity); wrap in RepaintBoundary - video_element.dart: wrap in RepaintBoundary to isolate frame repaints - gallery_renderer.dart: wrap each page item in RepaintBoundary Co-Authored-By: Claude Sonnet 4.6 --- .../renderer/containers/gallery_renderer.dart | 2 +- .../src/renderer/elements/image_element.dart | 27 ++++---- .../src/renderer/elements/video_element.dart | 12 ++-- flutter/lib/src/renderer/style_applier.dart | 64 +++++++++++++++---- 4 files changed, 74 insertions(+), 31 deletions(-) diff --git a/flutter/lib/src/renderer/containers/gallery_renderer.dart b/flutter/lib/src/renderer/containers/gallery_renderer.dart index 5261286..b981459 100644 --- a/flutter/lib/src/renderer/containers/gallery_renderer.dart +++ b/flutter/lib/src/renderer/containers/gallery_renderer.dart @@ -121,7 +121,7 @@ class _GalleryRendererState extends State { child: item, ); } - return item; + return RepaintBoundary(child: item); }, ), ); diff --git a/flutter/lib/src/renderer/elements/image_element.dart b/flutter/lib/src/renderer/elements/image_element.dart index c49f229..214cb20 100644 --- a/flutter/lib/src/renderer/elements/image_element.dart +++ b/flutter/lib/src/renderer/elements/image_element.dart @@ -1,3 +1,4 @@ +import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/widgets.dart'; import '../../models/enums.dart'; @@ -42,27 +43,31 @@ class ImageElement extends StatelessWidget { ), ); } else if (isGif) { - // Flutter natively handles animated GIFs via Image.network + // Flutter's image codec handles animated GIFs natively via Image.network. + // CachedNetworkImage may not preserve frame timing for animated GIFs. imageWidget = Image.network( url, fit: fit, - loadingBuilder: (ctx, child, progress) => progress == null ? child : const SizedBox.shrink(), + loadingBuilder: (ctx, child, progress) => + progress == null ? child : const SizedBox.shrink(), errorBuilder: (ctx, err, stack) => const SizedBox.shrink(), ); } else { - imageWidget = Image.network( - url, + imageWidget = CachedNetworkImage( + imageUrl: url, fit: fit, - loadingBuilder: (ctx, child, progress) => progress == null ? child : const SizedBox.shrink(), - errorBuilder: (ctx, err, stack) => const SizedBox.shrink(), + placeholder: (ctx, url) => const SizedBox.shrink(), + errorWidget: (ctx, url, err) => const SizedBox.shrink(), ); } - return StyleApplier.apply( - imageWidget, - style, - rootHeight: rootHeight, - padding: node.layout?.padding, + return RepaintBoundary( + child: StyleApplier.apply( + imageWidget, + style, + rootHeight: rootHeight, + padding: node.layout?.padding, + ), ); } diff --git a/flutter/lib/src/renderer/elements/video_element.dart b/flutter/lib/src/renderer/elements/video_element.dart index be224be..77e3b8f 100644 --- a/flutter/lib/src/renderer/elements/video_element.dart +++ b/flutter/lib/src/renderer/elements/video_element.dart @@ -95,11 +95,13 @@ class _VideoElementState extends State { content = video; } - return StyleApplier.apply( - content, - widget.style, - rootHeight: rootHeight, - padding: widget.node.layout?.padding, + return RepaintBoundary( + child: StyleApplier.apply( + content, + widget.style, + rootHeight: rootHeight, + padding: widget.node.layout?.padding, + ), ); } } diff --git a/flutter/lib/src/renderer/style_applier.dart b/flutter/lib/src/renderer/style_applier.dart index 9f7de15..389b3e3 100644 --- a/flutter/lib/src/renderer/style_applier.dart +++ b/flutter/lib/src/renderer/style_applier.dart @@ -23,18 +23,36 @@ class StyleApplier { } final radius = _resolveBorderRadius(style.borderRadius, rootHeight); + final opacity = style.opacity; - if (radius != null) { - result = ClipRRect(borderRadius: BorderRadius.circular(radius), child: result); - } + // For solid backgrounds, bake opacity directly into the color to avoid a + // saveLayer call. The Opacity widget is only used for complex content + // (gradients, images, no background) where full-subtree alpha blending is needed. + final bg = style.background; + final hasSolidBg = + (bg == null && style.backgroundColor != null) || bg is SolidBackground; + final bakedOpacity = + (hasSolidBg && opacity != null && opacity < 1.0) ? opacity : null; - final decoration = _buildDecoration(style, rootHeight, radius); + final decoration = _buildDecoration(style, rootHeight, radius, bakedOpacity); if (decoration != null) { - result = DecoratedBox(decoration: decoration, child: result); + // Correct order: DecoratedBox (outside) paints background/border/shadow. + // ClipRRect (inside) clips children to the same border radius. + if (radius != null) { + result = DecoratedBox( + decoration: decoration, + child: ClipRRect( + borderRadius: BorderRadius.circular(radius), + child: result, + ), + ); + } else { + result = DecoratedBox(decoration: decoration, child: result); + } } - final opacity = style.opacity; - if (opacity != null && opacity < 1.0) { + // Use the Opacity widget only when we couldn't bake alpha into a solid color. + if (opacity != null && opacity < 1.0 && !hasSolidBg) { result = Opacity(opacity: opacity.clamp(0.0, 1.0), child: result); } @@ -51,8 +69,13 @@ class StyleApplier { ); } - static BoxDecoration? _buildDecoration(Style style, double rootHeight, double? radius) { - final bgResult = _resolveBackground(style, rootHeight); + static BoxDecoration? _buildDecoration( + Style style, + double rootHeight, + double? radius, + double? bakedOpacity, + ) { + final bgResult = _resolveBackground(style, rootHeight, bakedOpacity); final border = _resolveBorder(style, rootHeight); final shadows = _resolveShadow(style); @@ -62,13 +85,20 @@ class StyleApplier { if (bgResult is Color) { bgColor = bgResult; - } else if (bgResult is LinearGradient || bgResult is RadialGradient || bgResult is SweepGradient) { + } else if (bgResult is LinearGradient || + bgResult is RadialGradient || + bgResult is SweepGradient) { bgGradient = bgResult as Gradient; } else if (bgResult is DecorationImage) { bgImage = bgResult; } - if (bgColor == null && bgGradient == null && bgImage == null && border == null && shadows == null && radius == null) { + if (bgColor == null && + bgGradient == null && + bgImage == null && + border == null && + shadows == null && + radius == null) { return null; } @@ -82,13 +112,19 @@ class StyleApplier { ); } - static dynamic _resolveBackground(Style style, double rootHeight) { + static dynamic _resolveBackground(Style style, double rootHeight, double? bakedOpacity) { final bg = style.background; if (bg == null) { - return ColorParser.parse(style.backgroundColor); + final color = ColorParser.parse(style.backgroundColor); + if (color == null || bakedOpacity == null) return color; + return color.withValues(alpha: bakedOpacity.clamp(0.0, 1.0)); } return switch (bg) { - SolidBackground s => ColorParser.parse(s.color), + SolidBackground s => () { + final color = ColorParser.parse(s.color); + if (color == null || bakedOpacity == null) return color; + return color.withValues(alpha: bakedOpacity.clamp(0.0, 1.0)); + }(), LinearGradientBackground lg => _buildLinearGradient(lg), RadialGradientBackground rg => _buildRadialGradient(rg), SweepGradientBackground sg => _buildSweepGradient(sg),