diff --git a/.claude/agents/android-sdk.md b/.claude/agents/android-sdk.md index b020f3c2..a46d99a1 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 183a22a2..7e502db3 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 be136304..ec35ffd5 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-sample.md b/.claude/agents/flutter-sample.md new file mode 100644 index 00000000..7e29b0a1 --- /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 00000000..d7f21172 --- /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 00000000..65819d1d --- /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 00000000..f126c916 --- /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 00000000..84b2c0f4 --- /dev/null +++ b/.claude/agents/flutter-sdk/knowledge/architecture.md @@ -0,0 +1,192 @@ +# 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`. 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 +``` + +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/`) + +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 00000000..fd76eb6d --- /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 00000000..cf26678d --- /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 00000000..0e80b081 --- /dev/null +++ b/.claude/agents/flutter-sdk/knowledge/rendering-pipeline.md @@ -0,0 +1,362 @@ +# 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; + return dim.value; // dp/sp/px — treat as logical pixels + } +} +``` + +## 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` | +``` + +--- + +## 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/.claude/agents/ios-sdk.md b/.claude/agents/ios-sdk.md index 9306e1fc..29a1ac99 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 27b048a7..8a8f46af 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 e15cf643..957d5ac8 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 d669dba2..20be6ea7 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 3ea0ca9e..14c381ea 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 3f328237..8fb099bb 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 cacae414..0a31abe6 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 5a8a3774..14a42fba 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. --- diff --git a/flutter-sample/.flutter-plugins b/flutter-sample/.flutter-plugins new file mode 100644 index 00000000..0452d8dd --- /dev/null +++ b/flutter-sample/.flutter-plugins @@ -0,0 +1,25 @@ +# 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/ +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 00000000..1aba744d --- /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":"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/.gitignore b/flutter-sample/.gitignore new file mode 100644 index 00000000..79c113f9 --- /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/.metadata b/flutter-sample/.metadata new file mode 100644 index 00000000..9a674c61 --- /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 00000000..eb0fc7bf --- /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/analysis_options.yaml b/flutter-sample/analysis_options.yaml new file mode 100644 index 00000000..0d290213 --- /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 00000000..be3943c9 --- /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 00000000..91774726 --- /dev/null +++ b/flutter-sample/android/app/build.gradle.kts @@ -0,0 +1,47 @@ +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 = 36 + 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 { +} 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 00000000..399f6981 --- /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 00000000..91469060 --- /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 00000000..1144a922 --- /dev/null +++ b/flutter-sample/android/app/src/main/kotlin/com/clevertap/flutter/clevertap_native_display_sample/MainActivity.kt @@ -0,0 +1,5 @@ +package com.clevertap.flutter.clevertap_native_display_sample + +import io.flutter.embedding.android.FlutterActivity + +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 new file mode 100644 index 00000000..99ee61a6 --- /dev/null +++ b/flutter-sample/android/app/src/main/kotlin/com/clevertap/flutter/clevertap_native_display_sample/SampleApplication.kt @@ -0,0 +1,14 @@ +package com.clevertap.flutter.clevertap_native_display_sample + +import android.app.Application +import com.clevertap.android.sdk.ActivityLifecycleCallback +import com.clevertap.android.sdk.CleverTapAPI +import com.clevertap.android.sdk.CleverTapAPI.LogLevel.VERBOSE + +class SampleApplication : Application() { + override fun onCreate() { + CleverTapAPI.setDebugLevel(VERBOSE) + ActivityLifecycleCallback.register(this) + super.onCreate() + } +} 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 00000000..f74085f3 --- /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 00000000..304732f8 --- /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 00000000..db77bb4b Binary files /dev/null and b/flutter-sample/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ 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 00000000..17987b79 Binary files /dev/null and b/flutter-sample/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/flutter-sample/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/flutter-sample/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000..09d43914 Binary files /dev/null and b/flutter-sample/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ 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 00000000..d5f1c8d3 Binary files /dev/null and b/flutter-sample/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ 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 00000000..4d6372ee Binary files /dev/null and b/flutter-sample/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ 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 00000000..06952be7 --- /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 00000000..cb1ef880 --- /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 00000000..399f6981 --- /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 00000000..89176ef4 --- /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 00000000..f018a618 --- /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 00000000..afa1e8eb --- /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 00000000..a439442c --- /dev/null +++ b/flutter-sample/android/settings.gradle.kts @@ -0,0 +1,25 @@ +pluginManagement { + val flutterSdkPath = run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.7.0" apply false + id("org.jetbrains.kotlin.android") version "1.8.22" apply false +} + +include(":app") 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 00000000..b6ce722c --- /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 00000000..fd3e6069 --- /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 00000000..1295beac --- /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 00000000..c8e99658 --- /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 00000000..70b041a1 --- /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 00000000..ebedaf66 --- /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 00000000..6d284281 --- /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 00000000..a818b6fe --- /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 00000000..90ff965b --- /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 00000000..0e0a653a --- /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 00000000..0b9b8bd7 --- /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 00000000..114c9a85 --- /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 00000000..c11b1b92 --- /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 00000000..8220e211 --- /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 00000000..a500160f --- /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 00000000..7f7bce93 --- /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 00000000..759e3dcf --- /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 00000000..3c9838a5 --- /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 00000000..c062a455 --- /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 00000000..0f764cea --- /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 00000000..f8d81dee --- /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 00000000..89cc3abd --- /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/assets/configs/test_simple.json b/flutter-sample/assets/configs/test_simple.json new file mode 100644 index 00000000..4640f9be --- /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 00000000..bd3bf8a5 --- /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 00000000..2dfad70a --- /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 00000000..8008f207 --- /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 00000000..86b222d5 --- /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 00000000..9d7312fe --- /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 00000000..3a7936ca --- /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 00000000..ab08aaea --- /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 00000000..9b64c5da --- /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 00000000..28780e4d --- /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 00000000..b9df54d6 --- /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 00000000..570a73e5 --- /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 00000000..5cab89c7 --- /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 00000000..4005e774 --- /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 00000000..a7f0f58d --- /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 00000000..6617ec2e --- /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 00000000..ec8cd072 --- /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 00000000..57ff034a --- /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 00000000..e2cb99b6 --- /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 00000000..a49bc916 --- /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 00000000..1e8e47e0 --- /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 00000000..21435e79 --- /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 00000000..080143c3 --- /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 00000000..fd534493 --- /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 00000000..93f2d149 --- /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 00000000..082ff78d --- /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 00000000..e763d7a2 --- /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 00000000..8f6c7d96 --- /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 00000000..32b423f0 --- /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 00000000..2c7fcf50 --- /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 00000000..3efbf464 --- /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 00000000..90a97a22 --- /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 00000000..88d17633 --- /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 00000000..d7b5e5ce --- /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 00000000..1c9bc2c0 --- /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 00000000..5dfbc7a0 --- /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 00000000..5867845c --- /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 00000000..da4e5c1a --- /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 00000000..3256ec80 --- /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 00000000..73602b58 --- /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 00000000..394ae046 --- /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 00000000..7a5f8a86 --- /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 00000000..6b9dd015 --- /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 00000000..5426f745 --- /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 00000000..37152c4b --- /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 00000000..043093d5 --- /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 00000000..1dc9bd27 --- /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 00000000..15b95164 --- /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 00000000..fa04cec7 --- /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 00000000..9e91d575 --- /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 00000000..a9b3e4ea --- /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 00000000..f15f5f0f --- /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 00000000..2d714b16 --- /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 00000000..0170b76f --- /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 00000000..7170310c --- /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 00000000..daf4a72e --- /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 00000000..5879a22e --- /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 00000000..11cefb1a --- /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 00000000..121a9b6d --- /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 00000000..939674d5 --- /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 00000000..77b79b54 --- /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 00000000..be6093ae --- /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 00000000..781b5b42 --- /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 00000000..edd7d84f --- /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 00000000..573c60ee --- /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 00000000..8215845e --- /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 00000000..248248ef --- /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 00000000..35ca3d8c --- /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 00000000..a70459f5 --- /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 00000000..bce37c22 --- /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 00000000..0e39e95c --- /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 00000000..a9db7b1f --- /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 00000000..31f76b46 --- /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 00000000..c47cf478 --- /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 00000000..a33b7051 --- /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 00000000..e0c77e28 --- /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 00000000..d99dd1d7 --- /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 00000000..8271d7ed --- /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 00000000..9d6833df --- /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 00000000..fcba4893 --- /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 00000000..eac7e322 --- /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 00000000..08fa60bc --- /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 00000000..4cc3b46e --- /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 00000000..d1127f58 --- /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 00000000..8c18c7ae --- /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 00000000..a1f5f12d --- /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 00000000..52d49a5b --- /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 00000000..b83c404e --- /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 00000000..f14c5332 --- /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 00000000..205bbce3 --- /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 00000000..eb06b714 --- /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 00000000..a02b924f --- /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 00000000..1245505b --- /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 00000000..a9198729 --- /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 00000000..405cbdd5 --- /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 00000000..52ff2303 --- /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 00000000..22b7bdf3 --- /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 00000000..87d7a231 --- /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 00000000..c694f8a6 --- /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 00000000..b46d2c19 --- /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 00000000..55e83bf5 --- /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 00000000..8cc8fe5f --- /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 00000000..bbd9ec36 --- /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 00000000..bd2b4497 --- /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 00000000..e7df4820 --- /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 00000000..7bd23935 --- /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 00000000..2f0c5461 --- /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 00000000..14624142 --- /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 00000000..1c5dc65c --- /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 00000000..356becb7 --- /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 00000000..3809fece --- /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 00000000..af54148f --- /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 00000000..508b083e --- /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 00000000..9e69b2c6 --- /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 00000000..f2da596b --- /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 00000000..3cbf86b6 --- /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 00000000..486debcb --- /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 00000000..4032d727 --- /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 00000000..0e3ef610 --- /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 00000000..9eeab9f6 --- /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 00000000..634cc037 --- /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 00000000..06bc79d5 --- /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 00000000..0cbc4877 --- /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 00000000..d2e81961 --- /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 00000000..44c7be02 --- /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 00000000..739af3e3 --- /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 00000000..f95449ca --- /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 00000000..c775b08c --- /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 00000000..9710ae9e --- /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 00000000..333c14b6 --- /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 00000000..31777290 --- /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 00000000..7771c3cb --- /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 00000000..26ff52ba --- /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 00000000..99d2dcfb --- /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 00000000..5002deba --- /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 00000000..62981709 --- /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 00000000..04583b16 --- /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 00000000..89c5716b --- /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 00000000..c5f932d0 --- /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 00000000..950becc8 --- /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 00000000..bd6e17ef --- /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 00000000..942fa23d --- /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 00000000..9d272663 --- /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 00000000..cf320931 --- /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 00000000..87a79dcb --- /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 00000000..f3e6b756 --- /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 00000000..7db149d6 --- /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 00000000..a786863f --- /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 00000000..bebd26c0 --- /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 00000000..8c05068a --- /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 00000000..90aee166 --- /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 00000000..748c5914 --- /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 00000000..e775fb18 --- /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 00000000..cc441500 --- /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 00000000..7adf4c64 --- /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 00000000..a7b1efdb --- /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 00000000..6138b64d --- /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 00000000..820b2f74 --- /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 00000000..490bca78 --- /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 00000000..5d85b0f4 --- /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 00000000..9ea6bae2 --- /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 00000000..ebc26a9f --- /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 00000000..4c29b9ac --- /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 00000000..bb770a1c --- /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 00000000..fdf88fa0 --- /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 00000000..66e31ef4 --- /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 00000000..e674ed20 --- /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 00000000..322647a7 --- /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 00000000..97ae7c0a --- /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 00000000..89ce3740 --- /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 00000000..d7d5f783 --- /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 00000000..11659e68 --- /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 00000000..807d8fac --- /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 00000000..afc4c10c --- /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 00000000..1e4839a7 --- /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 00000000..61785e9f --- /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 00000000..ba8a5d13 --- /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 00000000..501394c1 --- /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 00000000..c1482b69 --- /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 00000000..c11a6f5c --- /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 00000000..65af3e03 --- /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 00000000..827f611a --- /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 00000000..e88c0e1a --- /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 00000000..806c774b --- /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/ios/.gitignore b/flutter-sample/ios/.gitignore new file mode 100644 index 00000000..7a7f9873 --- /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 00000000..7c569640 --- /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 00000000..ec97fc6f --- /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 00000000..c4855bfe --- /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 00000000..352cc4e4 --- /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 00000000..1877aee3 --- /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 00000000..15cada48 --- /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 00000000..9efc57b9 --- /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 00000000..d36b1fab --- /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 00000000..dc9ada47 Binary files /dev/null and b/flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 00000000..7353c41e Binary files /dev/null and b/flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ 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 00000000..797d452e Binary files /dev/null and b/flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ 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 00000000..6ed2d933 Binary files /dev/null and b/flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ 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 00000000..4cd7b009 Binary files /dev/null and b/flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ 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 00000000..fe730945 Binary files /dev/null and b/flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 00000000..321773cd Binary files /dev/null and b/flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 00000000..797d452e Binary files /dev/null and b/flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ 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 00000000..502f463a Binary files /dev/null and b/flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 00000000..0ec30343 Binary files /dev/null and b/flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ 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 00000000..0ec30343 Binary files /dev/null and b/flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ 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 00000000..e9f5fea2 Binary files /dev/null and b/flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 00000000..84ac32ae Binary files /dev/null and b/flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 00000000..8953cba0 Binary files /dev/null and b/flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ 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 00000000..0467bf12 Binary files /dev/null and b/flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ 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 00000000..0bedcf2f --- /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 00000000..9da19eac Binary files /dev/null and b/flutter-sample/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ 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 00000000..9da19eac Binary files /dev/null and b/flutter-sample/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ 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 00000000..9da19eac Binary files /dev/null and b/flutter-sample/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ 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 00000000..89c2725b --- /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 00000000..f2e259c7 --- /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 00000000..f3c28516 --- /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 00000000..fbe43ac2 --- /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 00000000..308a2a56 --- /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 00000000..86a7c3b1 --- /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 new file mode 100644 index 00000000..13640479 --- /dev/null +++ b/flutter-sample/lib/app.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; + +import 'screens/clevertap_integration_screen.dart'; +import 'screens/slot_demo_screen.dart'; +import 'screens/test_browser_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; + + @override + Widget build(BuildContext context) { + return Scaffold( + body: IndexedStack( + index: _selectedTab, + children: const [ + CleverTapIntegrationScreen(), + SlotDemoScreen(), + TestBrowserScreen(), + MoreMenuScreen(), + ], + ), + bottomNavigationBar: NavigationBar( + selectedIndex: _selectedTab, + onDestinationSelected: (i) => setState(() => _selectedTab = i), + destinations: const [ + 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.more_horiz), label: 'More'), + ], + ), + ); + } +} diff --git a/flutter-sample/lib/main.dart b/flutter-sample/lib/main.dart new file mode 100644 index 00000000..6652d1e2 --- /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 00000000..90c087bb --- /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 00000000..62157e8e --- /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 00000000..4f423721 --- /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 00000000..b599e7dc --- /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 00000000..1e609ebe --- /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), + 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), + const _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: const SelectableText( + snippet, + style: TextStyle( + fontFamily: 'monospace', + fontSize: 12, + color: Color(0xFF1565C0), + height: 1.5, + ), + ), + ), + ], + ), + ), + ); + } +} 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 00000000..dc3c10da --- /dev/null +++ b/flutter-sample/lib/screens/clevertap_integration_screen.dart @@ -0,0 +1,308 @@ +import 'package:clevertap_plugin/clevertap_plugin.dart'; +import 'package:flutter/material.dart'; +import 'package:clevertap_native_display/clevertap_native_display.dart'; + +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 = []; + + // CleverTapPlugin() returns the singleton — needed for instance method registration. + final _ct = CleverTapPlugin(); + + @override + void initState() { + super.initState(); + _ct.setCleverTapDisplayUnitsLoadedHandler(_onUnitsLoaded); + _fetchCachedUnits(); + } + + @override + void dispose() { + _eventController.dispose(); + super.dispose(); + } + + 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'); + } + } + + 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 + ..clear() + ..addAll(parsed); + _log.add('[${_ts()}] Received ${parsed.length} Native Display unit(s)'); + }); + + if (parsed.length < rawUnits.length) { + _addLog('WARN: ${rawUnits.length - parsed.length} unit(s) had no recognisable NativeDisplayConfig'); + } + } + + Future _sendEvent() async { + final name = _eventController.text.trim(); + if (name.isEmpty) return; + 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( + 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, + onChanged: () => setState(() {}), + ), + Expanded( + child: _CanvasContent( + units: _units, + onAction: _addLog, + ), + ), + _EventLogFooter( + messages: _log, + onClear: () => setState(() => _log.clear()), + ), + ], + ), + ); + } +} + +// ── Header ──────────────────────────────────────────────────────────────────── + +class _FireEventHeader extends StatelessWidget { + final TextEditingController controller; + final VoidCallback onSend; + final VoidCallback onChanged; + + const _FireEventHeader({ + required this.controller, + required this.onSend, + 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: controller.text.isNotEmpty ? 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), + ), + ), + ], + ), + ); + } +} + +// ── Canvas ──────────────────────────────────────────────────────────────────── + +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) { + final unit = units[i]; + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: NativeDisplayView( + config: unit.config, + resolvedStyles: unit.resolvedStyles, + actionListener: (action, nodeId, params) { + onAction('ACTION $action on $nodeId'); + if (unit.unitId.isNotEmpty) { + CleverTapPlugin.pushDisplayUnitClickedEvent(unit.unitId); + } + }, + ), + ); + }, + ); + } +} + +// ── Event Log ───────────────────────────────────────────────────────────────── + +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 _scroll = ScrollController(); + + @override + void didUpdateWidget(_EventLogFooter old) { + super.didUpdateWidget(old); + if (widget.messages.length != old.messages.length) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (_scroll.hasClients) { + _scroll.animateTo( + _scroll.position.maxScrollExtent, + duration: const Duration(milliseconds: 200), + curve: Curves.easeOut, + ); + } + }); + } + } + + @override + void dispose() { + _scroll.dispose(); + super.dispose(); + } + + Color _color(String msg) { + if (msg.contains('Fired')) return const Color(0xFFFFD54F); + if (msg.contains('ACTION')) return const Color(0xFF81D4FA); + if (msg.contains('ERROR') || msg.contains('WARN')) 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: _scroll, + itemCount: widget.messages.length, + itemBuilder: (ctx, i) => Text( + widget.messages[i], + style: TextStyle( + fontFamily: 'monospace', + fontSize: 11, + color: _color(widget.messages[i]), + height: 1.4, + ), + ), + ), + ), + const SizedBox(height: 8), + ], + ), + ); + } +} 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 00000000..2088e35a --- /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 00000000..170d4b24 --- /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 00000000..6f8eaab0 --- /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 '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.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()), + 'Other Demos' => const _FullScreenShell(title: 'Other Demos', child: OtherDemosScreen()), + _ => 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 00000000..76a8aed2 --- /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 const DefaultTabController( + length: 5, + child: Column( + children: [ + TabBar( + isScrollable: true, + tabAlignment: TabAlignment.start, + tabs: [ + Tab(text: 'Home Screen'), + Tab(text: 'Gallery'), + Tab(text: 'E-commerce'), + Tab(text: 'Social'), + Tab(text: 'Dashboard'), + ], + ), + 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 00000000..95fea911 --- /dev/null +++ b/flutter-sample/lib/screens/slot_demo_screen.dart @@ -0,0 +1,262 @@ +import 'package:flutter/material.dart'; + +// --- Data models --- + +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}); + + @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( + 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.symmetric(horizontal: 16, vertical: 12), + itemCount: _feed.length + 1, // +1 for header + itemBuilder: (ctx, i) { + if (i == 0) return _Header(onFetch: _fetchSlotData); + final item = _feed[i - 1]; + return Padding( + padding: const EdgeInsets.only(bottom: 12), + child: switch (item) { + _SlotItem s => _SlotPlaceholder(slotId: s.slotId), + _ContentItem c => _AppContentCard(item: c.item), + }, + ); + }, + ), + ); + } +} + +// --- Header --- + +class _Header extends StatelessWidget { + final VoidCallback onFetch; + const _Header({required this.onFetch}); + + @override + Widget build(BuildContext context) { + 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: [ + 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)), + ), + ), + ), + 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/lib/screens/test_browser_screen.dart b/flutter-sample/lib/screens/test_browser_screen.dart new file mode 100644 index 00000000..eb4fb925 --- /dev/null +++ b/flutter-sample/lib/screens/test_browser_screen.dart @@ -0,0 +1,348 @@ +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', + 'test-parity-80pct-ar.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 00000000..6929f320 --- /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 00000000..b934aaa4 --- /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 00000000..35434e36 --- /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/linux/.gitignore b/flutter-sample/linux/.gitignore new file mode 100644 index 00000000..d3896c98 --- /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 00000000..32d90761 --- /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 00000000..d5bd0164 --- /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 00000000..f6f23bfe --- /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 00000000..e0f0a47b --- /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 00000000..f16b4c34 --- /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 00000000..e97dabc7 --- /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 00000000..e7c5c543 --- /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 00000000..3d940d9e --- /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 00000000..72271d5e --- /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 00000000..746adbb6 --- /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 00000000..4b81f9b2 --- /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 00000000..5caa9d15 --- /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 00000000..f1dbe4cb --- /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 00000000..29c8eb32 --- /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 00000000..6f8a8733 --- /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 00000000..b956b50f --- /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 00000000..b3c17614 --- /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 00000000..a2ec33f1 --- /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 00000000..82b6f9d9 Binary files /dev/null and b/flutter-sample/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ 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 00000000..13b35eba Binary files /dev/null and b/flutter-sample/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ 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 00000000..0a3f5fa4 Binary files /dev/null and b/flutter-sample/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/flutter-sample/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/flutter-sample/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 00000000..bdb57226 Binary files /dev/null and b/flutter-sample/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/flutter-sample/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/flutter-sample/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 00000000..f083318e Binary files /dev/null and b/flutter-sample/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/flutter-sample/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/flutter-sample/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 00000000..326c0e72 Binary files /dev/null and b/flutter-sample/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ 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 00000000..2f1632cf Binary files /dev/null and b/flutter-sample/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/flutter-sample/macos/Runner/Base.lproj/MainMenu.xib b/flutter-sample/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 00000000..80e867a4 --- /dev/null +++ b/flutter-sample/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/flutter-sample/macos/Runner/Configs/AppInfo.xcconfig b/flutter-sample/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 00000000..9fe7ad6a --- /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 00000000..36b0fd94 --- /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 00000000..dff4f495 --- /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 00000000..42bcbf47 --- /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 00000000..dddb8a30 --- /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 00000000..4789daa6 --- /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 00000000..3cc05eb2 --- /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 00000000..852fa1a4 --- /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 00000000..61f3bd1f --- /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 new file mode 100644 index 00000000..d3d79d5e --- /dev/null +++ b/flutter-sample/pubspec.lock @@ -0,0 +1,617 @@ +# 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" + 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: + 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 00000000..a12dcd19 --- /dev/null +++ b/flutter-sample/pubspec.yaml @@ -0,0 +1,27 @@ +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 + clevertap_plugin: ^4.0.0 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^4.0.0 + +flutter: + uses-material-design: true + assets: + - assets/banners/ + - assets/configs/ + - assets/test-configs/ diff --git a/flutter-sample/test/widget_test.dart b/flutter-sample/test/widget_test.dart new file mode 100644 index 00000000..053fa186 --- /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-sample/web/favicon.png b/flutter-sample/web/favicon.png new file mode 100644 index 00000000..8aaa46ac Binary files /dev/null and b/flutter-sample/web/favicon.png differ diff --git a/flutter-sample/web/icons/Icon-192.png b/flutter-sample/web/icons/Icon-192.png new file mode 100644 index 00000000..b749bfef Binary files /dev/null and b/flutter-sample/web/icons/Icon-192.png differ diff --git a/flutter-sample/web/icons/Icon-512.png b/flutter-sample/web/icons/Icon-512.png new file mode 100644 index 00000000..88cfd48d Binary files /dev/null and b/flutter-sample/web/icons/Icon-512.png differ 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 00000000..eb9b4d76 Binary files /dev/null and b/flutter-sample/web/icons/Icon-maskable-192.png differ 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 00000000..d69c5669 Binary files /dev/null and b/flutter-sample/web/icons/Icon-maskable-512.png differ diff --git a/flutter-sample/web/index.html b/flutter-sample/web/index.html new file mode 100644 index 00000000..30351755 --- /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 00000000..30c3c181 --- /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 00000000..d492d0d9 --- /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 00000000..597dab51 --- /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 00000000..903f4899 --- /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 00000000..4f788487 --- /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 00000000..dc139d85 --- /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 00000000..88b22e5c --- /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 00000000..394917c0 --- /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 00000000..476cc139 --- /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 00000000..955ee303 --- /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 00000000..6da0652f --- /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 00000000..926510fc --- /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 00000000..66a65d1e --- /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 00000000..c04e20ca Binary files /dev/null and b/flutter-sample/windows/runner/resources/app_icon.ico differ diff --git a/flutter-sample/windows/runner/runner.exe.manifest b/flutter-sample/windows/runner/runner.exe.manifest new file mode 100644 index 00000000..153653e8 --- /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 00000000..3a0b4651 --- /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 00000000..3879d547 --- /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 00000000..60608d0f --- /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 00000000..e901dde6 --- /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/android/build.gradle b/flutter/android/build.gradle new file mode 100644 index 00000000..b1c6a0fa --- /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 00000000..94cbbcfc --- /dev/null +++ b/flutter/android/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + 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 00000000..22947c34 --- /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 00000000..29da409d --- /dev/null +++ b/flutter/ios/Classes/CleverTapNativeDisplayPlugin.swift @@ -0,0 +1,63 @@ +#if canImport(Flutter) +import Flutter +#elseif canImport(FlutterMacOS) +import FlutterMacOS +#endif + +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 4d24cac5..5eb9f78f 100644 --- a/flutter/lib/clevertap_native_display.dart +++ b/flutter/lib/clevertap_native_display.dart @@ -12,3 +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_bridge.dart b/flutter/lib/src/bridge/native_display_bridge.dart new file mode 100644 index 00000000..768265a5 --- /dev/null +++ b/flutter/lib/src/bridge/native_display_bridge.dart @@ -0,0 +1,66 @@ +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'); + 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. + 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}'); + } + } + + // 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/bridge/native_display_config_parser.dart b/flutter/lib/src/bridge/native_display_config_parser.dart new file mode 100644 index 00000000..559b8761 --- /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/handler/action_handler.dart b/flutter/lib/src/handler/action_handler.dart new file mode 100644 index 00000000..a49eb782 --- /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/lib/src/models/native_display_unit.dart b/flutter/lib/src/models/native_display_unit.dart new file mode 100644 index 00000000..fb8b729a --- /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/animation_modifier.dart b/flutter/lib/src/renderer/animation_modifier.dart new file mode 100644 index 00000000..aece5ae1 --- /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/containers/box_container.dart b/flutter/lib/src/renderer/containers/box_container.dart index 352b6de1..1641491a 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/gallery_renderer.dart b/flutter/lib/src/renderer/containers/gallery_renderer.dart index 4d2dd075..b9814591 100644 --- a/flutter/lib/src/renderer/containers/gallery_renderer.dart +++ b/flutter/lib/src/renderer/containers/gallery_renderer.dart @@ -1,10 +1,18 @@ -import 'package:flutter/widgets.dart'; +import 'dart:async'; + +import 'package:flutter/widgets.dart' hide Orientation; + import '../../evaluator/variable_evaluator.dart'; +import '../../models/enums.dart'; +import '../../models/gallery_config.dart'; import '../../models/native_display_node.dart'; import '../../models/style.dart'; +import '../../utils/color_parser.dart'; +import '../native_display_renderer.dart'; +import '../root_height_scope.dart'; +import '../style_applier.dart'; -// v1 stub — full GALLERY implementation in Step 9 -class GalleryRenderer extends StatelessWidget { +class GalleryRenderer extends StatefulWidget { final NativeDisplayContainer node; final Style style; final VariableEvaluator evaluator; @@ -20,9 +28,155 @@ class GalleryRenderer extends StatelessWidget { this.componentListener, }); + @override + State createState() => _GalleryRendererState(); +} + +class _GalleryRendererState extends State { + late PageController _pageController; + Timer? _autoScrollTimer; + late int _currentPage; + late int _childCount; + + GalleryConfig get _config => widget.node.galleryConfig ?? const GalleryConfig(); + + @override + void initState() { + super.initState(); + _childCount = widget.node.children.length; + _currentPage = _config.initialPage.clamp(0, _childCount == 0 ? 0 : _childCount - 1); + _pageController = PageController( + initialPage: _currentPage, + viewportFraction: _viewportFraction, + ); + _startAutoScroll(); + } + + double get _viewportFraction { + final items = _config.effectiveItemsPerView; + if (items <= 0) return 1.0; + return 1.0 / items; + } + + void _startAutoScroll() { + final interval = _config.autoScrollInterval; + if (interval <= 0 || _childCount <= 1) return; + _autoScrollTimer = Timer.periodic(Duration(milliseconds: interval), (_) { + if (!mounted) return; + final nextPage = _config.infiniteScroll + ? (_currentPage + 1) % _childCount + : (_currentPage + 1 < _childCount ? _currentPage + 1 : _currentPage); + if (nextPage != _currentPage) { + _pageController.animateToPage( + nextPage, + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + ); + } + }); + } + + @override + void dispose() { + _autoScrollTimer?.cancel(); + _pageController.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { - debugPrint('[NativeDisplay] GalleryRenderer: full implementation in Step 9 — rendering stub'); - return const SizedBox.shrink(); + final rootHeight = RootHeightScope.of(context); + final config = _config; + final children = widget.node.children; + + // 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 RepaintBoundary(child: item); + }, + ), + ); + }, + ); + + if (config.showIndicators && children.isNotEmpty) { + final style = config.indicatorStyle ?? const IndicatorStyle(); + gallery = Stack( + alignment: _indicatorAlignment(style.position), + children: [ + gallery, + _buildIndicators(style, children.length), + ], + ); + } + + return StyleApplier.apply( + gallery, + widget.style, + rootHeight: rootHeight, + padding: widget.node.layout?.padding, + ); + } + + Alignment _indicatorAlignment(String position) => switch (position) { + 'top' => Alignment.topCenter, + 'left' => Alignment.centerLeft, + 'right' => Alignment.centerRight, + _ => Alignment.bottomCenter, + }; + + Widget _buildIndicators(IndicatorStyle style, int count) { + final activeColor = ColorParser.parse(style.activeColor) ?? const Color(0xFF2196F3); + final inactiveColor = ColorParser.parse(style.inactiveColor) ?? const Color(0xFFBDBDBD); + final isVertical = _config.orientation == Orientation.vertical; + + final dots = List.generate(count, (i) { + final isActive = i == _currentPage % count; + return Container( + width: style.size, + height: style.size, + margin: EdgeInsets.all(style.spacing / 2), + decoration: BoxDecoration( + color: isActive ? activeColor : inactiveColor, + shape: style.shape == 'circle' ? BoxShape.circle : BoxShape.rectangle, + ), + ); + }); + + return Padding( + padding: const EdgeInsets.all(8), + child: isVertical + ? Column(mainAxisSize: MainAxisSize.min, children: dots) + : Row(mainAxisSize: MainAxisSize.min, children: dots), + ); } } diff --git a/flutter/lib/src/renderer/containers/horizontal_container.dart b/flutter/lib/src/renderer/containers/horizontal_container.dart index d15e3cb4..0fefe55c 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 ea03583a..ced9e089 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 new file mode 100644 index 00000000..eebea5c8 --- /dev/null +++ b/flutter/lib/src/renderer/elements/button_element.dart @@ -0,0 +1,84 @@ +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 '../../utils/color_parser.dart'; +import '../root_height_scope.dart'; +import '../style_applier.dart'; + +class ButtonElement extends StatelessWidget { + final NativeDisplayElement node; + final Style style; + final VariableEvaluator evaluator; + final void Function(String, String, Map?)? actionListener; + + const ButtonElement({ + super.key, + required this.node, + required this.style, + required this.evaluator, + this.actionListener, + }); + + @override + Widget build(BuildContext context) { + final rootHeight = RootHeightScope.of(context); + final text = evaluator.evaluateString(node.bindings['text'] ?? ''); + + return GestureDetector( + onTap: _handleClick, + child: StyleApplier.apply( + 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, + ), + ); + } + + TextStyle _buildTextStyle(double rootHeight) { + final fontSize = style.fontSize?.resolve(rootHeight) ?? 14.0; + final lineHeight = style.lineHeight?.resolve(rootHeight); + return TextStyle( + color: ColorParser.parse(style.textColor), + fontSize: fontSize, + fontFamily: style.fontFamily, + 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, + NDFontWeight.medium => FontWeight.w500, + NDFontWeight.bold => FontWeight.w700, + null => FontWeight.w400, + }; + + void _handleClick() { + final action = node.actions?['onClick']; + if (action != null) { + actionListener?.call('action', node.id, null); + } + } +} diff --git a/flutter/lib/src/renderer/elements/divider_element.dart b/flutter/lib/src/renderer/elements/divider_element.dart new file mode 100644 index 00000000..34af9826 --- /dev/null +++ b/flutter/lib/src/renderer/elements/divider_element.dart @@ -0,0 +1,33 @@ +import 'package:flutter/widgets.dart' hide Orientation; + +import '../../models/enums.dart'; +import '../../models/native_display_node.dart'; +import '../../models/style.dart'; +import '../../utils/color_parser.dart'; + +class DividerElement extends StatelessWidget { + final NativeDisplayElement node; + final Style style; + + const DividerElement({super.key, required this.node, required this.style}); + + @override + Widget build(BuildContext context) { + final config = node.dividerConfig; + final isHorizontal = config?.orientation != Orientation.vertical; + final thickness = config?.thickness ?? 1.0; + final color = ColorParser.parse(config?.color) ?? const Color(0xFFE0E0E0); + + return isHorizontal + ? SizedBox( + width: double.infinity, + height: thickness, + child: ColoredBox(color: color), + ) + : SizedBox( + width: thickness, + height: double.infinity, + child: ColoredBox(color: color), + ); + } +} diff --git a/flutter/lib/src/renderer/elements/html_element.dart b/flutter/lib/src/renderer/elements/html_element.dart new file mode 100644 index 00000000..5ba534cf --- /dev/null +++ b/flutter/lib/src/renderer/elements/html_element.dart @@ -0,0 +1,100 @@ +import 'package:flutter/widgets.dart'; +import 'package:webview_flutter/webview_flutter.dart'; + +import '../../evaluator/variable_evaluator.dart'; +import '../../models/enums.dart'; +import '../../models/native_display_node.dart'; +import '../../models/style.dart'; +import '../../utils/dimension_calculator.dart'; +import '../root_height_scope.dart'; +import '../style_applier.dart'; + +class HtmlElement extends StatefulWidget { + final NativeDisplayElement node; + final Style style; + final VariableEvaluator evaluator; + + const HtmlElement({ + super.key, + required this.node, + required this.style, + required this.evaluator, + }); + + @override + State createState() => _HtmlElementState(); +} + +class _HtmlElementState extends State { + late final WebViewController _controller; + + @override + void initState() { + super.initState(); + final config = widget.node.htmlConfig; + _controller = WebViewController() + ..setJavaScriptMode( + config?.javascriptEnabled == true + ? JavaScriptMode.unrestricted + : JavaScriptMode.disabled, + ) + ..setBackgroundColor( + config?.transparentBackground != false + ? const Color(0x00000000) + : const Color(0xFFFFFFFF), + ); + + _loadContent(); + } + + void _loadContent() { + final htmlBinding = widget.evaluator.evaluateString( + widget.node.bindings['html'] ?? '', + ); + if (htmlBinding.isNotEmpty) { + _controller.loadHtmlString( + htmlBinding, + baseUrl: widget.node.htmlConfig?.baseUrl, + ); + return; + } + final urlBinding = widget.evaluator.evaluateString( + widget.node.bindings['url'] ?? '', + ); + if (urlBinding.isNotEmpty) { + _controller.loadRequest(Uri.parse(urlBinding)); + } + } + + @override + Widget build(BuildContext context) { + final rootHeight = RootHeightScope.of(context); + final layout = widget.node.layout; + + // HTML requires an explicit height — wrap_content is unsupported by WebView. + // Resolve height from layout; fall back to 200dp for wrap_content or unspecified. + final rawHeight = DimensionCalculator.resolve(layout?.height, parentSize: rootHeight); + final isWrapContent = layout?.height?.special == SpecialDimension.wrapContent; + final isMatchParent = layout?.height?.special == SpecialDimension.matchParent; + if (isWrapContent) { + debugPrint('[NativeDisplay] HtmlElement: wrap_content height unsupported; using 200dp fallback'); + } + final double? height = isMatchParent + ? null + : (rawHeight ?? 200); + + Widget webView = WebViewWidget(controller: _controller); + if (height != null) { + webView = SizedBox(height: height, child: webView); + } else { + webView = Expanded(child: webView); + } + + return StyleApplier.apply( + webView, + widget.style, + rootHeight: rootHeight, + padding: layout?.padding, + ); + } +} diff --git a/flutter/lib/src/renderer/elements/image_element.dart b/flutter/lib/src/renderer/elements/image_element.dart new file mode 100644 index 00000000..214cb203 --- /dev/null +++ b/flutter/lib/src/renderer/elements/image_element.dart @@ -0,0 +1,91 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/widgets.dart'; + +import '../../models/enums.dart'; +import '../../models/native_display_node.dart'; +import '../../models/node_config.dart'; +import '../../models/style.dart'; +import '../root_height_scope.dart'; +import '../style_applier.dart'; + +class ImageElement extends StatelessWidget { + final NativeDisplayElement node; + final Style style; + + const ImageElement({super.key, required this.node, required this.style}); + + @override + Widget build(BuildContext context) { + final rootHeight = RootHeightScope.of(context); + final url = node.bindings['url'] ?? ''; + if (url.isEmpty) { + return StyleApplier.apply( + const SizedBox.shrink(), + style, + rootHeight: rootHeight, + padding: node.layout?.padding, + ); + } + + final isGif = _isGif(url, node.imageConfig); + final imageFit = node.imageConfig?.fit ?? ImageFit.crop; + final isTile = imageFit == ImageFit.tile; + final fit = _resolveBoxFit(imageFit); + + Widget imageWidget; + if (isTile) { + imageWidget = Container( + decoration: BoxDecoration( + image: DecorationImage( + image: NetworkImage(url), + repeat: ImageRepeat.repeat, + ), + ), + ); + } else if (isGif) { + // 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(), + errorBuilder: (ctx, err, stack) => const SizedBox.shrink(), + ); + } else { + imageWidget = CachedNetworkImage( + imageUrl: url, + fit: fit, + placeholder: (ctx, url) => const SizedBox.shrink(), + errorWidget: (ctx, url, err) => const SizedBox.shrink(), + ); + } + + return RepaintBoundary( + child: StyleApplier.apply( + imageWidget, + style, + rootHeight: rootHeight, + padding: node.layout?.padding, + ), + ); + } + + bool _isGif(String url, ImageConfig? config) { + if (config?.animated == true) return true; + if (config?.animated == false) return false; + final lower = url.toLowerCase(); + if (lower.endsWith('.gif')) return true; + for (final host in ['giphy.com', 'tenor.com', 'gfycat.com', 'imgur.com']) { + if (lower.contains(host)) return true; + } + return false; + } + + BoxFit _resolveBoxFit(ImageFit fit) => switch (fit) { + ImageFit.crop => BoxFit.cover, + ImageFit.contain => BoxFit.contain, + ImageFit.fill => BoxFit.fill, + ImageFit.tile => BoxFit.none, + }; +} diff --git a/flutter/lib/src/renderer/elements/spacer_element.dart b/flutter/lib/src/renderer/elements/spacer_element.dart new file mode 100644 index 00000000..5f7db914 --- /dev/null +++ b/flutter/lib/src/renderer/elements/spacer_element.dart @@ -0,0 +1,23 @@ +import 'package:flutter/widgets.dart'; + +import '../../models/enums.dart'; +import '../../models/native_display_node.dart'; +import '../../models/style.dart'; + +class SpacerElement extends StatelessWidget { + final NativeDisplayElement node; + final Style style; + + const SpacerElement({super.key, required this.node, required this.style}); + + @override + Widget build(BuildContext context) { + final layout = node.layout; + final isFlexible = layout?.width?.special == SpecialDimension.matchParent || + layout?.height?.special == SpecialDimension.matchParent; + if (isFlexible) return const Spacer(); + final w = layout?.width?.special == null ? layout?.width?.value : null; + final h = layout?.height?.special == null ? layout?.height?.value : null; + return SizedBox(width: w, height: h); + } +} diff --git a/flutter/lib/src/renderer/elements/text_element.dart b/flutter/lib/src/renderer/elements/text_element.dart new file mode 100644 index 00000000..982d5884 --- /dev/null +++ b/flutter/lib/src/renderer/elements/text_element.dart @@ -0,0 +1,136 @@ +import 'dart:math'; + +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 '../../models/text_dimension.dart'; +import '../../utils/color_parser.dart'; +import '../root_height_scope.dart'; +import '../style_applier.dart'; + +class TextElement extends StatelessWidget { + final NativeDisplayElement node; + final Style style; + final VariableEvaluator evaluator; + + const TextElement({ + super.key, + required this.node, + required this.style, + required this.evaluator, + }); + + @override + Widget build(BuildContext context) { + final rootHeight = RootHeightScope.of(context); + final rawText = node.bindings['text'] ?? ''; + final text = evaluator.evaluateString(rawText); + + final fontSize = _resolveTextDimension(style.fontSize, rootHeight) ?? 14.0; + final lineHeight = _resolveTextDimension(style.lineHeight, rootHeight); + final heightMultiplier = lineHeight != null ? lineHeight / fontSize : null; + + final textStyle = TextStyle( + color: ColorParser.parse(style.textColor), + fontSize: fontSize, + fontFamily: style.fontFamily, + fontWeight: _resolveFontWeight(style.fontWeight), + fontStyle: style.fontStyle == NDFontStyle.italic ? FontStyle.italic : FontStyle.normal, + height: heightMultiplier, + letterSpacing: style.letterSpacing, + decoration: _resolveDecoration(style.textDecoration), + shadows: _resolveShadows(style), + ); + + Widget textWidget = Text( + text, + style: textStyle, + textAlign: _resolveTextAlign(style.textAlign), + maxLines: style.maxLines, + overflow: _resolveOverflow(style.overflow), + ); + + if (style.textGradient != null) { + textWidget = ShaderMask( + shaderCallback: (bounds) => _buildGradient(style.textGradient!).createShader(bounds), + blendMode: BlendMode.srcIn, + child: textWidget, + ); + } + + return StyleApplier.apply( + textWidget, + style, + rootHeight: rootHeight, + padding: node.layout?.padding, + ); + } + + double? _resolveTextDimension(TextDimension? dim, double rootHeight) { + if (dim == null) return null; + return dim.resolve(rootHeight); + } + + FontWeight _resolveFontWeight(NDFontWeight? w) => switch (w) { + NDFontWeight.light => FontWeight.w300, + NDFontWeight.normal => FontWeight.w400, + NDFontWeight.medium => FontWeight.w500, + NDFontWeight.bold => FontWeight.w700, + null => FontWeight.w400, + }; + + TextDecoration _resolveDecoration(NDTextDecoration? d) => switch (d) { + NDTextDecoration.underline => TextDecoration.underline, + NDTextDecoration.strikethrough => TextDecoration.lineThrough, + _ => TextDecoration.none, + }; + + TextAlign _resolveTextAlign(String? align) => switch (align) { + 'center' => TextAlign.center, + 'right' => TextAlign.right, + 'justify' => TextAlign.justify, + _ => TextAlign.left, + }; + + TextOverflow? _resolveOverflow(NDTextOverflow? overflow) => switch (overflow) { + NDTextOverflow.clip => TextOverflow.clip, + NDTextOverflow.ellipsis => TextOverflow.ellipsis, + NDTextOverflow.visible => null, + null => null, + }; + + List? _resolveShadows(Style style) { + if (style.textShadow == null) return null; + final ts = style.textShadow!; + return [ + Shadow( + color: ColorParser.parse(ts.color) ?? const Color(0xFF000000), + offset: Offset(ts.offsetX, ts.offsetY), + blurRadius: ts.blur, + ) + ]; + } + + Gradient _buildGradient(TextGradient tg) { + final colors = tg.colors + .map((c) => ColorParser.parse(c) ?? const Color(0xFF000000)) + .toList(); + final stops = tg.stops; + if (tg.type == 'radial') { + return RadialGradient(colors: colors, stops: stops); + } + // Default: linear + final radians = (tg.angle - 90) * pi / 180; + final dx = cos(radians); + final dy = sin(radians); + return LinearGradient( + begin: Alignment(-dx, -dy), + end: Alignment(dx, dy), + colors: colors, + stops: stops, + ); + } +} diff --git a/flutter/lib/src/renderer/elements/video_element.dart b/flutter/lib/src/renderer/elements/video_element.dart new file mode 100644 index 00000000..77e3b8fe --- /dev/null +++ b/flutter/lib/src/renderer/elements/video_element.dart @@ -0,0 +1,107 @@ +import 'package:flutter/widgets.dart'; +import 'package:video_player/video_player.dart'; + +import '../../evaluator/variable_evaluator.dart'; +import '../../models/native_display_node.dart'; +import '../../models/style.dart'; +import '../root_height_scope.dart'; +import '../style_applier.dart'; + +class VideoElement extends StatefulWidget { + final NativeDisplayElement node; + final Style style; + final VariableEvaluator evaluator; + + const VideoElement({ + super.key, + required this.node, + required this.style, + required this.evaluator, + }); + + @override + State createState() => _VideoElementState(); +} + +class _VideoElementState extends State { + VideoPlayerController? _controller; + bool _initialized = false; + + String get _url => widget.evaluator.evaluateString(widget.node.bindings['url'] ?? ''); + bool get _autoPlay => widget.node.bindings['autoPlay'] == 'true'; + bool get _loop => widget.node.bindings['loop'] == 'true'; + bool get _muted => widget.node.bindings['muted'] == 'true'; + bool get _showControls => widget.node.bindings['showControls'] != 'false'; + + @override + void initState() { + super.initState(); + _initController(); + } + + Future _initController() async { + final url = _url; + if (url.isEmpty) return; + final controller = VideoPlayerController.networkUrl(Uri.parse(url)); + _controller = controller; + await controller.initialize(); + if (!mounted) return; + controller.setLooping(_loop); + controller.setVolume(_muted ? 0.0 : 1.0); + setState(() => _initialized = true); + if (_autoPlay) controller.play(); + } + + @override + void dispose() { + _controller?.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final rootHeight = RootHeightScope.of(context); + + Widget content; + if (!_initialized || _controller == null) { + content = const SizedBox.shrink(); + } else { + Widget video = AspectRatio( + aspectRatio: _controller!.value.aspectRatio, + child: VideoPlayer(_controller!), + ); + + if (_showControls) { + video = Stack( + alignment: Alignment.center, + children: [ + video, + GestureDetector( + onTap: () { + setState(() { + _controller!.value.isPlaying + ? _controller!.pause() + : _controller!.play(); + }); + }, + child: _controller!.value.isPlaying + ? const SizedBox.shrink() + : const Icon(IconData(0xe037, fontFamily: 'MaterialIcons'), + size: 48, color: Color(0xCCFFFFFF)), + ), + ], + ); + } + content = video; + } + + return RepaintBoundary( + child: StyleApplier.apply( + content, + widget.style, + rootHeight: rootHeight, + padding: widget.node.layout?.padding, + ), + ); + } +} diff --git a/flutter/lib/src/renderer/native_display_renderer.dart b/flutter/lib/src/renderer/native_display_renderer.dart index a0797768..359e92c6 100644 --- a/flutter/lib/src/renderer/native_display_renderer.dart +++ b/flutter/lib/src/renderer/native_display_renderer.dart @@ -1,13 +1,24 @@ 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'; import 'containers/box_container.dart'; import 'containers/gallery_renderer.dart'; import 'containers/horizontal_container.dart'; import 'containers/vertical_container.dart'; +import 'elements/button_element.dart'; +import 'elements/divider_element.dart'; +import 'elements/html_element.dart'; +import 'elements/image_element.dart'; +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; @@ -29,11 +40,33 @@ class NativeDisplayRenderer extends StatelessWidget { final resolvedStyles = ResolvedStylesScope.of(context); final style = resolvedStyles[node.id] ?? Style.empty; + final rootHeight = RootHeightScope.of(context); - return switch (node) { + 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); + } + return built; } Widget _buildContainer(NativeDisplayContainer node, Style style) { @@ -70,7 +103,58 @@ class NativeDisplayRenderer extends StatelessWidget { } Widget _buildElement(BuildContext context, NativeDisplayElement node, Style style) { - // Stub — full implementation in Steps 6-12 - return const SizedBox.shrink(); + return switch (node.elementType) { + ElementType.text => TextElement(node: node, style: style, evaluator: evaluator), + ElementType.image => ImageElement(node: node, style: style), + ElementType.button => ButtonElement( + node: node, + style: style, + evaluator: evaluator, + actionListener: actionListener, + ), + ElementType.spacer => SpacerElement(node: node, style: style), + ElementType.divider => DividerElement(node: node, style: style), + ElementType.video => VideoElement(node: node, style: style, evaluator: evaluator), + 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 94e985ec..309f9339 100644 --- a/flutter/lib/src/renderer/native_display_view.dart +++ b/flutter/lib/src/renderer/native_display_view.dart @@ -14,77 +14,199 @@ 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); - - Widget buildContent(double availableWidth, double availableHeight) { - return RootHeightScope( - rootHeight: availableHeight, - child: ResolvedStylesScope( - styles: _resolvedStyles, - child: NativeDisplayRenderer( - node: root, - evaluator: evaluator, - actionListener: actionListener, - componentListener: componentListener, + 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 needsLayout = _needsLayoutBuilder(root.layout); + final screenHeight = MediaQuery.sizeOf(context).height; - if (needsLayout) { - return LayoutBuilder( - builder: (context, constraints) => buildContent( - constraints.maxWidth, - constraints.maxHeight.isInfinite ? 0 : constraints.maxHeight, - ), - ); - } + return LayoutBuilder( + builder: (context, constraints) { + final layouterWidth = + constraints.maxWidth.isInfinite ? screenHeight : constraints.maxWidth; + + final effectiveRootWidth = _effectiveWidth(layout, layouterWidth); + final rootHeight = + _computeRootHeight(layout, effectiveRootWidth, constraints, screenHeight); + + Widget child = RootHeightScope( + rootHeight: rootHeight, + child: ResolvedStylesScope( + styles: _resolvedStyles, + child: NativeDisplayRenderer( + node: root, + evaluator: evaluator, + actionListener: widget.actionListener, + componentListener: widget.componentListener, + ), + ), + ); + + child = _applyRootSizing(child, layout, effectiveRootWidth, rootHeight, screenHeight); - 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), + return child; + }, ); } - bool _needsLayoutBuilder(Layout? layout) { + bool _hasOnlyFixedDimensions(Layout? layout) { if (layout == null) return false; - if (_isPercent(layout.width) || _isPercent(layout.height)) return true; - if (layout.aspectRatio != null) return true; - 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, + 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; + + // 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; + + 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), + ); + } + + 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 _isPercent(Dimension? dim) => - dim != null && dim.special == null && dim.unit == DimensionUnit.percent; + 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; + } + + 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 c9bb0e80..389b3e3e 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), @@ -151,14 +187,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) { diff --git a/flutter/pubspec.lock b/flutter/pubspec.lock index 84758377..48835635 100644 --- a/flutter/pubspec.lock +++ b/flutter/pubspec.lock @@ -73,6 +73,14 @@ packages: 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: @@ -131,6 +139,19 @@ packages: 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: @@ -400,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: @@ -416,6 +501,46 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + video_player: + dependency: "direct main" + 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: @@ -432,6 +557,38 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" + webview_flutter: + dependency: "direct main" + 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: diff --git a/flutter/pubspec.yaml b/flutter/pubspec.yaml index 81cbc234..11466792 100644 --- a/flutter/pubspec.yaml +++ b/flutter/pubspec.yaml @@ -11,6 +11,9 @@ dependencies: flutter: sdk: flutter 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/bridge/native_display_bridge_test.dart b/flutter/test/bridge/native_display_bridge_test.dart new file mode 100644 index 00000000..85f11be0 --- /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); + }); + }); +} diff --git a/flutter/test/handler/action_handler_test.dart b/flutter/test/handler/action_handler_test.dart new file mode 100644 index 00000000..96b6d1ae --- /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); + }); + }); +} diff --git a/flutter/test/renderer/animation_modifier_test.dart b/flutter/test/renderer/animation_modifier_test.dart new file mode 100644 index 00000000..e3f55e44 --- /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); + }); + }); +} diff --git a/flutter/test/renderer/button_spacer_divider_test.dart b/flutter/test/renderer/button_spacer_divider_test.dart new file mode 100644 index 00000000..235a8d05 --- /dev/null +++ b/flutter/test/renderer/button_spacer_divider_test.dart @@ -0,0 +1,198 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:clevertap_native_display/clevertap_native_display.dart'; +import 'package:clevertap_native_display/src/renderer/elements/button_element.dart'; +import 'package:clevertap_native_display/src/renderer/elements/spacer_element.dart'; +import 'package:clevertap_native_display/src/renderer/elements/divider_element.dart'; +import 'package:clevertap_native_display/src/evaluator/variable_evaluator.dart'; +import 'package:clevertap_native_display/src/renderer/root_height_scope.dart'; +import 'package:clevertap_native_display/src/renderer/resolved_styles_scope.dart'; + +Widget wrap(Widget w) => Directionality( + textDirection: TextDirection.ltr, + child: RootHeightScope( + rootHeight: 200, + child: ResolvedStylesScope( + styles: const {}, + child: w, + ), + ), + ); + +void main() { + group('ButtonElement', () { + testWidgets('renders GestureDetector wrapping content', (tester) async { + final node = NativeDisplayElement( + id: 'btn', + elementType: ElementType.button, + bindings: {'text': 'Click Me'}, + ); + + await tester.pumpWidget(wrap(ButtonElement( + node: node, + style: Style.empty, + evaluator: VariableEvaluator({}), + ))); + + expect(find.byType(GestureDetector), findsOneWidget); + expect(find.text('Click Me'), findsOneWidget); + }); + + testWidgets('fires actionListener on tap', (tester) async { + String? capturedAction; + String? capturedNodeId; + + final action = NDAction.fromJson({'type': 'custom', 'key': 'test'}); + final node = NativeDisplayElement( + id: 'btn', + elementType: ElementType.button, + bindings: {'text': 'Tap'}, + actions: {'onClick': action}, + ); + + await tester.pumpWidget(wrap(ButtonElement( + node: node, + style: Style.empty, + evaluator: VariableEvaluator({}), + actionListener: (action, nodeId, params) { + capturedAction = action; + capturedNodeId = nodeId; + }, + ))); + + await tester.tap(find.byType(GestureDetector)); + await tester.pump(); + + expect(capturedAction, 'action'); + expect(capturedNodeId, 'btn'); + }); + + testWidgets('no actionListener called when no onClick action', (tester) async { + bool called = false; + final node = NativeDisplayElement( + id: 'btn', + elementType: ElementType.button, + bindings: {'text': 'Tap'}, + ); + + await tester.pumpWidget(wrap(ButtonElement( + node: node, + style: Style.empty, + evaluator: VariableEvaluator({}), + actionListener: (_, __, ___) => called = true, + ))); + + await tester.tap(find.byType(GestureDetector)); + await tester.pump(); + + expect(called, false); + }); + }); + + group('SpacerElement', () { + testWidgets('match_parent width returns Spacer widget', (tester) async { + final node = NativeDisplayElement( + id: 'spacer', + elementType: ElementType.spacer, + layout: Layout.fromJson({ + 'width': {'special': 'match_parent'}, + }), + ); + + await tester.pumpWidget(wrap(Row(children: [ + SpacerElement(node: node, style: Style.empty), + ]))); + + expect(find.byType(Spacer), findsOneWidget); + }); + + testWidgets('match_parent height returns Spacer widget', (tester) async { + final node = NativeDisplayElement( + id: 'spacer', + elementType: ElementType.spacer, + layout: Layout.fromJson({ + 'height': {'special': 'match_parent'}, + }), + ); + + await tester.pumpWidget(wrap(Column(children: [ + SpacerElement(node: node, style: Style.empty), + ]))); + + expect(find.byType(Spacer), findsOneWidget); + }); + + testWidgets('fixed dimensions return SizedBox', (tester) async { + final node = NativeDisplayElement( + id: 'spacer', + elementType: ElementType.spacer, + layout: Layout.fromJson({ + 'width': {'value': 16, 'unit': 'dp'}, + 'height': {'value': 8, 'unit': 'dp'}, + }), + ); + + await tester.pumpWidget(wrap(SpacerElement(node: node, style: Style.empty))); + + final sizedBox = tester.widget(find.byType(SizedBox)); + expect(sizedBox.width, 16); + expect(sizedBox.height, 8); + }); + }); + + group('DividerElement', () { + testWidgets('horizontal divider has infinite width and configured thickness', (tester) async { + final node = NativeDisplayElement( + id: 'divider', + elementType: ElementType.divider, + dividerConfig: DividerConfig.fromJson({ + 'orientation': 'horizontal', + 'thickness': 2.0, + 'color': '#FF0000', + }), + ); + + await tester.pumpWidget(wrap( + SizedBox(width: 200, child: DividerElement(node: node, style: Style.empty)), + )); + + final sizedBox = tester.widget(find.byType(SizedBox).last); + expect(sizedBox.height, 2.0); + expect(sizedBox.width, double.infinity); + }); + + testWidgets('vertical divider has infinite height and configured thickness', (tester) async { + final node = NativeDisplayElement( + id: 'divider', + elementType: ElementType.divider, + dividerConfig: DividerConfig.fromJson({ + 'orientation': 'vertical', + 'thickness': 3.0, + 'color': '#0000FF', + }), + ); + + await tester.pumpWidget(wrap( + SizedBox(height: 200, child: DividerElement(node: node, style: Style.empty)), + )); + + final sizedBox = tester.widget(find.byType(SizedBox).last); + expect(sizedBox.width, 3.0); + expect(sizedBox.height, double.infinity); + }); + + testWidgets('default divider uses 1.0 thickness and #E0E0E0 color', (tester) async { + final node = NativeDisplayElement( + id: 'divider', + elementType: ElementType.divider, + ); + + await tester.pumpWidget(wrap( + SizedBox(width: 200, child: DividerElement(node: node, style: Style.empty)), + )); + + final sizedBox = tester.widget(find.byType(SizedBox).last); + expect(sizedBox.height, 1.0); + }); + }); +} diff --git a/flutter/test/renderer/container_test.dart b/flutter/test/renderer/container_test.dart index 7390d510..f213f54c 100644 --- a/flutter/test/renderer/container_test.dart +++ b/flutter/test/renderer/container_test.dart @@ -6,6 +6,8 @@ import 'package:clevertap_native_display/src/renderer/containers/vertical_contai import 'package:clevertap_native_display/src/renderer/containers/horizontal_container.dart'; import 'package:clevertap_native_display/src/renderer/containers/gallery_renderer.dart'; import 'package:clevertap_native_display/src/evaluator/variable_evaluator.dart'; +import 'package:clevertap_native_display/src/renderer/root_height_scope.dart'; +import 'package:clevertap_native_display/src/renderer/resolved_styles_scope.dart'; NativeDisplayContainer makeContainer({ String id = 'c', @@ -28,7 +30,13 @@ void main() { Widget wrap(Widget w) => Directionality( textDirection: TextDirection.ltr, - child: w, + child: RootHeightScope( + rootHeight: 200, + child: ResolvedStylesScope( + styles: const {}, + child: w, + ), + ), ); group('BoxContainer', () { @@ -124,21 +132,95 @@ void main() { }); }); - group('GalleryRenderer stub', () { - testWidgets('renders without crash', (tester) async { + group('GalleryRenderer', () { + testWidgets('renders PageView', (tester) async { final node = makeContainer( type: ContainerType.gallery, - children: [makeElement(id: 'a')], + children: [makeElement(id: 'a'), makeElement(id: 'b')], ); - await tester.pumpWidget(wrap(GalleryRenderer( - node: node, - style: Style.empty, - evaluator: evaluator, + await tester.pumpWidget(wrap(SizedBox( + width: 300, + height: 200, + child: GalleryRenderer( + node: node, + style: Style.empty, + evaluator: evaluator, + ), + ))); + + expect(find.byType(PageView), findsOneWidget); + }); + + testWidgets('renders correct number of children', (tester) async { + final node = NativeDisplayContainer( + id: 'gallery', + containerType: ContainerType.gallery, + children: [ + NativeDisplayElement(id: 'a', elementType: ElementType.text, bindings: {'text': 'Page 1'}), + NativeDisplayElement(id: 'b', elementType: ElementType.text, bindings: {'text': 'Page 2'}), + NativeDisplayElement(id: 'c', elementType: ElementType.text, bindings: {'text': 'Page 3'}), + ], + ); + + await tester.pumpWidget(wrap(SizedBox( + width: 300, + height: 200, + child: GalleryRenderer( + node: node, + style: Style.empty, + evaluator: evaluator, + ), + ))); + await tester.pump(); + + expect(find.byType(PageView), findsOneWidget); + }); + + testWidgets('renders indicators when showIndicators is true', (tester) async { + final node = NativeDisplayContainer( + id: 'gallery', + containerType: ContainerType.gallery, + children: [ + NativeDisplayElement(id: 'a', elementType: ElementType.text, bindings: {'text': 'P1'}), + NativeDisplayElement(id: 'b', elementType: ElementType.text, bindings: {'text': 'P2'}), + ], + galleryConfig: GalleryConfig.fromJson({ + 'showIndicators': true, + }), + ); + + await tester.pumpWidget(wrap(SizedBox( + width: 300, + height: 200, + child: GalleryRenderer( + node: node, + style: Style.empty, + evaluator: evaluator, + ), + ))); + + // Indicators are rendered as Container dots inside a Stack + expect(find.byType(Stack), findsOneWidget); + }); + + testWidgets('empty children renders empty PageView without crash', (tester) async { + final node = makeContainer( + type: ContainerType.gallery, + children: const [], + ); + + await tester.pumpWidget(wrap(SizedBox( + width: 300, + height: 200, + child: GalleryRenderer( + node: node, + style: Style.empty, + evaluator: evaluator, + ), ))); - // No crash is the test - expect(find.byType(SizedBox), findsOneWidget); + expect(find.byType(PageView), findsOneWidget); }); }); } diff --git a/flutter/test/renderer/html_element_test.dart b/flutter/test/renderer/html_element_test.dart new file mode 100644 index 00000000..81663dff --- /dev/null +++ b/flutter/test/renderer/html_element_test.dart @@ -0,0 +1,65 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:clevertap_native_display/clevertap_native_display.dart'; + +// HtmlElement uses WebViewController which requires platform channels. +// Tests here validate configuration logic without constructing the widget. + +void main() { + group('HtmlElement configuration logic', () { + test('html binding takes priority over url binding', () { + const bindings = {'html': 'hello', 'url': 'http://example.com'}; + final htmlVal = bindings['html'] ?? ''; + final urlVal = bindings['url'] ?? ''; + // html non-empty → use html, ignore url + expect(htmlVal.isNotEmpty, true); + expect(urlVal.isNotEmpty, true); // both present, but html wins + }); + + test('url binding used when html is absent', () { + const bindings = {'url': 'http://example.com/page.html'}; + final htmlVal = bindings['html'] ?? ''; + final urlVal = bindings['url'] ?? ''; + expect(htmlVal.isEmpty, true); + expect(urlVal, 'http://example.com/page.html'); + }); + + test('neither binding falls through gracefully', () { + const bindings = {}; + final htmlVal = bindings['html'] ?? ''; + final urlVal = bindings['url'] ?? ''; + expect(htmlVal.isEmpty, true); + expect(urlVal.isEmpty, true); + }); + + test('HtmlConfig defaults: JS disabled, scroll disabled, transparent', () { + final node = NativeDisplayElement( + id: 'html', + elementType: ElementType.html, + bindings: const {}, + ); + // Default htmlConfig is null → defaults apply in HtmlElement + expect(node.htmlConfig, isNull); + }); + + test('HtmlConfig fromJson parses all fields', () { + final config = HtmlConfig.fromJson({ + 'javascriptEnabled': true, + 'scrollEnabled': true, + 'baseUrl': 'https://cdn.example.com', + 'transparentBackground': false, + }); + expect(config.javascriptEnabled, true); + expect(config.scrollEnabled, true); + expect(config.baseUrl, 'https://cdn.example.com'); + expect(config.transparentBackground, false); + }); + + test('HtmlConfig defaults when json is empty', () { + final config = HtmlConfig.fromJson({}); + expect(config.javascriptEnabled, false); + expect(config.scrollEnabled, false); + expect(config.baseUrl, isNull); + expect(config.transparentBackground, true); + }); + }); +} diff --git a/flutter/test/renderer/image_element_test.dart b/flutter/test/renderer/image_element_test.dart new file mode 100644 index 00000000..222cb555 --- /dev/null +++ b/flutter/test/renderer/image_element_test.dart @@ -0,0 +1,82 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:clevertap_native_display/clevertap_native_display.dart'; + +// GIF detection unit tests — pure logic, no widgets +void main() { + group('ImageElement GIF detection logic', () { + test('explicit animated=true overrides all', () { + expect(_testIsGif('https://example.com/photo.png', ImageConfig(animated: true)), true); + }); + + test('explicit animated=false overrides .gif extension', () { + expect(_testIsGif('https://example.com/image.gif', ImageConfig(animated: false)), false); + }); + + test('.gif extension detected', () { + expect(_testIsGif('https://example.com/image.gif', null), true); + }); + + test('giphy.com host detected', () { + expect(_testIsGif('https://media.giphy.com/abc', null), true); + }); + + test('tenor.com host detected', () { + expect(_testIsGif('https://tenor.com/view/abc', null), true); + }); + + test('gfycat.com host detected', () { + expect(_testIsGif('https://gfycat.com/abc', null), true); + }); + + test('imgur.com host detected', () { + expect(_testIsGif('https://imgur.com/abc.mp4', null), true); + }); + + test('regular jpg not detected as GIF', () { + expect(_testIsGif('https://example.com/photo.jpg', null), false); + }); + + test('uppercase .GIF extension detected', () { + expect(_testIsGif('https://example.com/IMAGE.GIF', null), true); + }); + }); + + group('ImageElement BoxFit mapping logic', () { + test('ImageFit.crop → BoxFit.cover', () { + expect(_testBoxFit(ImageFit.crop), BoxFit.cover); + }); + + test('ImageFit.contain → BoxFit.contain', () { + expect(_testBoxFit(ImageFit.contain), BoxFit.contain); + }); + + test('ImageFit.fill → BoxFit.fill', () { + expect(_testBoxFit(ImageFit.fill), BoxFit.fill); + }); + + test('ImageFit.tile → BoxFit.none', () { + expect(_testBoxFit(ImageFit.tile), BoxFit.none); + }); + }); +} + +// Replicates ImageElement._isGif logic for unit testing +bool _testIsGif(String url, ImageConfig? config) { + if (config?.animated == true) return true; + if (config?.animated == false) return false; + final lower = url.toLowerCase(); + if (lower.endsWith('.gif')) return true; + for (final host in ['giphy.com', 'tenor.com', 'gfycat.com', 'imgur.com']) { + if (lower.contains(host)) return true; + } + return false; +} + +// Replicates ImageElement._resolveBoxFit logic +BoxFit _testBoxFit(ImageFit fit) => switch (fit) { + ImageFit.crop => BoxFit.cover, + ImageFit.contain => BoxFit.contain, + ImageFit.fill => BoxFit.fill, + ImageFit.tile => BoxFit.none, + }; diff --git a/flutter/test/renderer/text_element_test.dart b/flutter/test/renderer/text_element_test.dart new file mode 100644 index 00000000..4974aff0 --- /dev/null +++ b/flutter/test/renderer/text_element_test.dart @@ -0,0 +1,121 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:clevertap_native_display/clevertap_native_display.dart'; +import 'package:clevertap_native_display/src/renderer/elements/text_element.dart'; +import 'package:clevertap_native_display/src/evaluator/variable_evaluator.dart'; +import 'package:clevertap_native_display/src/renderer/root_height_scope.dart'; +import 'package:clevertap_native_display/src/renderer/resolved_styles_scope.dart'; + +void main() { + Widget wrap(Widget w) => Directionality( + textDirection: TextDirection.ltr, + child: RootHeightScope( + rootHeight: 200, + child: ResolvedStylesScope( + styles: const {}, + child: w, + ), + ), + ); + + NativeDisplayElement makeTextElement({ + String id = 'text1', + String text = 'Hello', + Style? style, + }) { + return NativeDisplayElement( + id: id, + elementType: ElementType.text, + bindings: {'text': text}, + style: style, + ); + } + + group('TextElement', () { + testWidgets('renders Text widget with text from bindings', (tester) async { + final node = makeTextElement(text: 'Hello World'); + await tester.pumpWidget(wrap( + TextElement(node: node, style: Style.empty, evaluator: VariableEvaluator({})), + )); + + expect(find.text('Hello World'), findsOneWidget); + }); + + testWidgets('evaluates variable template in text', (tester) async { + final node = makeTextElement(text: 'Hi {{name}}'); + await tester.pumpWidget(wrap( + TextElement( + node: node, + style: Style.empty, + evaluator: VariableEvaluator({'name': 'Alice'}), + ), + )); + + expect(find.text('Hi Alice'), findsOneWidget); + }); + + testWidgets('applies fontWeight bold', (tester) async { + final style = Style.fromJson({'fontWeight': 'bold'}); + final node = makeTextElement(); + + await tester.pumpWidget(wrap( + TextElement(node: node, style: style, evaluator: VariableEvaluator({})), + )); + + final text = tester.widget(find.byType(Text)); + expect(text.style?.fontWeight, FontWeight.w700); + }); + + testWidgets('applies fontWeight light', (tester) async { + final style = Style.fromJson({'fontWeight': 'light'}); + final node = makeTextElement(); + + await tester.pumpWidget(wrap( + TextElement(node: node, style: style, evaluator: VariableEvaluator({})), + )); + + final text = tester.widget(find.byType(Text)); + expect(text.style?.fontWeight, FontWeight.w300); + }); + + testWidgets('wraps with ShaderMask when textGradient set', (tester) async { + final style = Style.fromJson({ + 'textGradient': { + 'colors': ['#FF0000', '#0000FF'], + 'angle': 90.0, + }, + }); + final node = makeTextElement(); + + await tester.pumpWidget(wrap( + TextElement(node: node, style: style, evaluator: VariableEvaluator({})), + )); + + expect(find.byType(ShaderMask), findsOneWidget); + }); + + testWidgets('textAlign center maps correctly', (tester) async { + final style = Style.fromJson({'textAlign': 'center'}); + final node = makeTextElement(); + + await tester.pumpWidget(wrap( + TextElement(node: node, style: style, evaluator: VariableEvaluator({})), + )); + + final text = tester.widget(find.byType(Text)); + expect(text.textAlign, TextAlign.center); + }); + + testWidgets('textColor applied to TextStyle', (tester) async { + final style = Style.fromJson({'textColor': '#FF0000'}); + final node = makeTextElement(); + + await tester.pumpWidget(wrap( + TextElement(node: node, style: style, evaluator: VariableEvaluator({})), + )); + + final text = tester.widget(find.byType(Text)); + expect(text.style?.color?.toARGB32(), 0xFFFF0000); + }); + }); +} diff --git a/flutter/test/renderer/video_element_test.dart b/flutter/test/renderer/video_element_test.dart new file mode 100644 index 00000000..4f7d6fc8 --- /dev/null +++ b/flutter/test/renderer/video_element_test.dart @@ -0,0 +1,88 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:clevertap_native_display/clevertap_native_display.dart'; +import 'package:clevertap_native_display/src/renderer/elements/video_element.dart'; +import 'package:clevertap_native_display/src/evaluator/variable_evaluator.dart'; +import 'package:clevertap_native_display/src/renderer/root_height_scope.dart'; +import 'package:clevertap_native_display/src/renderer/resolved_styles_scope.dart'; + +Widget wrap(Widget w) => Directionality( + textDirection: TextDirection.ltr, + child: RootHeightScope( + rootHeight: 200, + child: ResolvedStylesScope( + styles: const {}, + child: w, + ), + ), + ); + +// Helper replicating VideoElement boolean binding logic for unit tests +bool _boolBinding(Map bindings, String key, {bool defaultValue = false}) { + final val = bindings[key]; + if (defaultValue) return val != 'false'; + return val == 'true'; +} + +void main() { + group('VideoElement boolean bindings', () { + test('autoPlay defaults to false', () { + expect(_boolBinding({'url': 'http://example.com/video.mp4'}, 'autoPlay'), false); + }); + + test('autoPlay=true is true', () { + expect(_boolBinding({'autoPlay': 'true'}, 'autoPlay'), true); + }); + + test('loop defaults to false', () { + expect(_boolBinding({}, 'loop'), false); + }); + + test('muted defaults to false', () { + expect(_boolBinding({}, 'muted'), false); + }); + + test('showControls defaults to true (inverse logic)', () { + expect(_boolBinding({}, 'showControls', defaultValue: true), true); + }); + + test('showControls=false is false', () { + expect(_boolBinding({'showControls': 'false'}, 'showControls', defaultValue: true), false); + }); + }); + + group('VideoElement widget', () { + testWidgets('renders without crash when url is empty', (tester) async { + final node = NativeDisplayElement( + id: 'video', + elementType: ElementType.video, + bindings: const {}, + ); + + await tester.pumpWidget(wrap(VideoElement( + node: node, + style: Style.empty, + evaluator: VariableEvaluator({}), + ))); + + // No crash and no controller initialized (empty url) + expect(find.byType(VideoElement), findsOneWidget); + }); + + testWidgets('renders SizedBox.shrink when url is empty and not initialized', (tester) async { + final node = NativeDisplayElement( + id: 'video', + elementType: ElementType.video, + bindings: const {'url': ''}, + ); + + await tester.pumpWidget(wrap(VideoElement( + node: node, + style: Style.empty, + evaluator: VariableEvaluator({}), + ))); + + expect(find.byType(SizedBox), findsAtLeastNWidgets(1)); + }); + }); +}