Use low-level divkit layout implementation

a10e00d445772539a30404f68a1143c3591602bc
This commit is contained in:
man-y
2024-09-02 14:02:41 +03:00
parent 3e2d6146ab
commit 8bb129bd15
262 changed files with 1414 additions and 887 deletions
+2 -1
View File
@@ -10175,6 +10175,7 @@
"client/flutter/divkit/lib/src/core/widgets/image/div_image_widget.dart":"divkit/public/client/flutter/divkit/lib/src/core/widgets/image/div_image_widget.dart",
"client/flutter/divkit/lib/src/core/widgets/input/div_input_model.dart":"divkit/public/client/flutter/divkit/lib/src/core/widgets/input/div_input_model.dart",
"client/flutter/divkit/lib/src/core/widgets/input/div_input_widget.dart":"divkit/public/client/flutter/divkit/lib/src/core/widgets/input/div_input_widget.dart",
"client/flutter/divkit/lib/src/core/widgets/layout/div_layout.dart":"divkit/public/client/flutter/divkit/lib/src/core/widgets/layout/div_layout.dart",
"client/flutter/divkit/lib/src/core/widgets/pager/div_pager_model.dart":"divkit/public/client/flutter/divkit/lib/src/core/widgets/pager/div_pager_model.dart",
"client/flutter/divkit/lib/src/core/widgets/pager/div_pager_widget.dart":"divkit/public/client/flutter/divkit/lib/src/core/widgets/pager/div_pager_widget.dart",
"client/flutter/divkit/lib/src/core/widgets/root/div_root_model.dart":"divkit/public/client/flutter/divkit/lib/src/core/widgets/root/div_root_model.dart",
@@ -10337,6 +10338,7 @@
"client/flutter/divkit/lib/src/schema/url_value.dart":"divkit/public/client/flutter/divkit/lib/src/schema/url_value.dart",
"client/flutter/divkit/lib/src/schema/url_variable.dart":"divkit/public/client/flutter/divkit/lib/src/schema/url_variable.dart",
"client/flutter/divkit/lib/src/utils/clockwork.dart":"divkit/public/client/flutter/divkit/lib/src/utils/clockwork.dart",
"client/flutter/divkit/lib/src/utils/configuration.dart":"divkit/public/client/flutter/divkit/lib/src/utils/configuration.dart",
"client/flutter/divkit/lib/src/utils/content_alignment_converters.dart":"divkit/public/client/flutter/divkit/lib/src/utils/content_alignment_converters.dart",
"client/flutter/divkit/lib/src/utils/converters.dart":"divkit/public/client/flutter/divkit/lib/src/utils/converters.dart",
"client/flutter/divkit/lib/src/utils/div_focus_node.dart":"divkit/public/client/flutter/divkit/lib/src/utils/div_focus_node.dart",
@@ -10344,7 +10346,6 @@
"client/flutter/divkit/lib/src/utils/duration_helper.dart":"divkit/public/client/flutter/divkit/lib/src/utils/duration_helper.dart",
"client/flutter/divkit/lib/src/utils/parsing_utils.dart":"divkit/public/client/flutter/divkit/lib/src/utils/parsing_utils.dart",
"client/flutter/divkit/lib/src/utils/provider.dart":"divkit/public/client/flutter/divkit/lib/src/utils/provider.dart",
"client/flutter/divkit/lib/src/utils/size_converters.dart":"divkit/public/client/flutter/divkit/lib/src/utils/size_converters.dart",
"client/flutter/divkit/lib/src/utils/tap_builder.dart":"divkit/public/client/flutter/divkit/lib/src/utils/tap_builder.dart",
"client/flutter/divkit/lib/src/utils/trace.dart":"divkit/public/client/flutter/divkit/lib/src/utils/trace.dart",
"client/flutter/divkit/pubspec.yaml":"divkit/public/client/flutter/divkit/pubspec.yaml",
@@ -292,6 +292,11 @@ class DartGenerator(Generator):
result += f' _index = {i};'
result += EMPTY
# Checker declaration
for (i, n) in enumerate(sorted(entity_enumeration.entity_names)):
result += f' bool get is{utils.capitalize_camel_case(n)} => _index == {i};'
result += EMPTY
# Preload declaration
result += ' Future<void> preload(Map<String, dynamic> context) => value.preload(context);'
@@ -356,6 +361,12 @@ class DartGenerator(Generator):
result += EMPTY
result += f' const {full_name}(this.value);'
# Checker declaration
for i in range(cases_len):
variant = allowed_name(utils.lower_camel_case(string_enumeration.cases[i][0]))
result += f' bool get is{utils.capitalize_camel_case(variant)} => this == {variant};'
result += EMPTY
result += EMPTY
result += ' T map<T>({'
for i in range(cases_len):
@@ -293,6 +293,44 @@ class Entity extends Preloadable with EquatableMixin {
) : value = obj,
_index = 18;
bool get isEntityWithArray => _index == 0;
bool get isEntityWithArrayOfEnums => _index == 1;
bool get isEntityWithArrayOfExpressions => _index == 2;
bool get isEntityWithArrayOfNestedItems => _index == 3;
bool get isEntityWithArrayWithTransform => _index == 4;
bool get isEntityWithComplexProperty => _index == 5;
bool get isEntityWithComplexPropertyWithDefaultValue => _index == 6;
bool get isEntityWithEntityProperty => _index == 7;
bool get isEntityWithOptionalComplexProperty => _index == 8;
bool get isEntityWithOptionalProperty => _index == 9;
bool get isEntityWithOptionalStringEnumProperty => _index == 10;
bool get isEntityWithPropertyWithDefaultValue => _index == 11;
bool get isEntityWithRawArray => _index == 12;
bool get isEntityWithRequiredProperty => _index == 13;
bool get isEntityWithSimpleProperties => _index == 14;
bool get isEntityWithStringArrayProperty => _index == 15;
bool get isEntityWithStringEnumProperty => _index == 16;
bool get isEntityWithStringEnumPropertyWithDefaultValue => _index == 17;
bool get isEntityWithoutProperties => _index == 18;
Future<void> preload(Map<String, dynamic> context) => value.preload(context);
static Entity? fromJson(Map<String, dynamic>? json,) {
@@ -66,6 +66,10 @@ enum EntityWithArrayOfEnumsItem implements Preloadable {
final String value;
const EntityWithArrayOfEnumsItem(this.value);
bool get isFirst => this == first;
bool get isSecond => this == second;
T map<T>({
required T Function() first,
@@ -66,6 +66,10 @@ enum EntityWithOptionalStringEnumPropertyProperty implements Preloadable {
final String value;
const EntityWithOptionalStringEnumPropertyProperty(this.value);
bool get isFirst => this == first;
bool get isSecond => this == second;
T map<T>({
required T Function() first,
@@ -66,6 +66,10 @@ enum EntityWithStringEnumPropertyProperty implements Preloadable {
final String value;
const EntityWithStringEnumPropertyProperty(this.value);
bool get isFirst => this == first;
bool get isSecond => this == second;
T map<T>({
required T Function() first,
@@ -67,6 +67,12 @@ enum EntityWithStringEnumPropertyWithDefaultValueValue implements Preloadable {
final String value;
const EntityWithStringEnumPropertyWithDefaultValueValue(this.value);
bool get isFirst => this == first;
bool get isSecond => this == second;
bool get isThird => this == third;
T map<T>({
required T Function() first,
@@ -55,6 +55,10 @@ class EnumWithDefaultType extends Preloadable with EquatableMixin {
) : value = obj,
_index = 1;
bool get isWithDefault => _index == 0;
bool get isWithoutDefault => _index == 1;
Future<void> preload(Map<String, dynamic> context) => value.preload(context);
static EnumWithDefaultType? fromJson(Map<String, dynamic>? json,) {
+11 -1
View File
@@ -1,10 +1,20 @@
## 0.3.1-rc.1
## 0.4.0-rc.1
* Use low-level divkit layout implementation: DivLayout
* Optimize pre-calculation of states when switching
* Fix sticky switching of states
* Fix inner objects in templates breaks rendering
* Add support div-text features: font_family, letter_spacing font_weight_value, text_shadow
* Add scenario list to testing page
* Add handling url in playground editor
* Added feature logging management
## Migration 0.3 → 0.4
* No changes in the public API!
* Due to the change in the layout system, we cannot guarantee full compliance with the rendering
of the previous version, now we use a lower-level, rather than a composition of standard components.
## 0.3.0
@@ -9,7 +9,6 @@ linter:
rules:
- always_declare_return_types
- always_use_package_imports
- prefer_expression_function_bodies
- require_trailing_commas
- prefer_const_constructors
- directives_ordering
+5 -1
View File
@@ -1,6 +1,6 @@
import 'package:divkit/divkit.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'src/app.dart';
@@ -11,5 +11,9 @@ void main() {
..keepLog = kDebugMode
..onLog = print;
debugPrintDivKitViewLifecycle = true;
debugPrintDivExpressionResolve = true;
debugPrintDivPerformLayout = true;
runApp(const ProviderScope(child: PlaygroundApp()));
}
@@ -74,10 +74,12 @@ class _ShowPageState extends ConsumerState<ShowPage> {
future: load(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return DivKitView(
key: ObjectKey(reloadN),
data: snapshot.requireData,
customHandler: PlaygroundAppCustomHandler(),
return SingleChildScrollView(
child: DivKitView(
key: ObjectKey(reloadN),
data: snapshot.requireData,
customHandler: PlaygroundAppCustomHandler(),
),
);
}
return const Center(child: CircularProgressIndicator());
@@ -1,16 +1,23 @@
PODS:
- FlutterMacOS (1.0.0)
- FMDB (2.7.10):
- FMDB/standard (= 2.7.10)
- FMDB/standard (2.7.10)
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- sqflite (0.0.3):
- Flutter
- sqflite (0.0.2):
- FlutterMacOS
- FMDB (>= 2.7.5)
DEPENDENCIES:
- FlutterMacOS (from `Flutter/ephemeral`)
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
- sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/darwin`)
- sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/macos`)
SPEC REPOS:
trunk:
- FMDB
EXTERNAL SOURCES:
FlutterMacOS:
@@ -18,12 +25,13 @@ EXTERNAL SOURCES:
path_provider_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
sqflite:
:path: Flutter/ephemeral/.symlinks/plugins/sqflite/darwin
:path: Flutter/ephemeral/.symlinks/plugins/sqflite/macos
SPEC CHECKSUMS:
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
FMDB: eae540775bf7d0c87a5af926ae37af69effe5a19
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
sqflite: d0307f984e859ce2bd39b230d672a448ea3f47b4
PODFILE CHECKSUM: 353c8bcc5d5b0994e508d035b5431cfe18c1dea7
+54 -46
View File
@@ -1,14 +1,6 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
archive:
dependency: transitive
description:
name: archive
sha256: "80e5141fafcb3361653ce308776cfd7d45e6e9fbb429e14eec571382c0c5fecb"
url: "https://pub.dev"
source: hosted
version: "3.3.2"
args:
dependency: transitive
description:
@@ -21,10 +13,10 @@ packages:
dependency: transitive
description:
name: async
sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
url: "https://pub.dev"
source: hosted
version: "2.10.0"
version: "2.11.0"
boolean_selector:
dependency: transitive
description:
@@ -61,10 +53,10 @@ packages:
dependency: transitive
description:
name: characters
sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
url: "https://pub.dev"
source: hosted
version: "1.2.1"
version: "1.3.0"
clock:
dependency: transitive
description:
@@ -77,10 +69,10 @@ packages:
dependency: transitive
description:
name: collection
sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
url: "https://pub.dev"
source: hosted
version: "1.17.0"
version: "1.18.0"
crypto:
dependency: transitive
description:
@@ -110,7 +102,7 @@ packages:
path: ".."
relative: true
source: path
version: "0.3.1-rc.1"
version: "0.4.0-rc.1"
equatable:
dependency: "direct main"
description:
@@ -139,10 +131,10 @@ packages:
dependency: transitive
description:
name: file
sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d"
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
url: "https://pub.dev"
source: hosted
version: "6.1.4"
version: "7.0.0"
flutter:
dependency: "direct main"
description: flutter
@@ -224,14 +216,30 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
js:
leak_tracker:
dependency: transitive
description:
name: js
sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7"
name: leak_tracker
sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa"
url: "https://pub.dev"
source: hosted
version: "0.6.5"
version: "10.0.0"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0
url: "https://pub.dev"
source: hosted
version: "2.0.1"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47
url: "https://pub.dev"
source: hosted
version: "2.0.1"
lints:
dependency: transitive
description:
@@ -252,26 +260,26 @@ packages:
dependency: transitive
description:
name: matcher
sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72"
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
url: "https://pub.dev"
source: hosted
version: "0.12.13"
version: "0.12.16+1"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
url: "https://pub.dev"
source: hosted
version: "0.2.0"
version: "0.8.0"
meta:
dependency: transitive
description:
name: meta
sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42"
sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
url: "https://pub.dev"
source: hosted
version: "1.8.0"
version: "1.11.0"
octo_image:
dependency: transitive
description:
@@ -284,10 +292,10 @@ packages:
dependency: transitive
description:
name: path
sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
url: "https://pub.dev"
source: hosted
version: "1.8.2"
version: "1.9.0"
path_parsing:
dependency: transitive
description:
@@ -356,10 +364,10 @@ packages:
dependency: transitive
description:
name: platform
sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76"
sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
url: "https://pub.dev"
source: hosted
version: "3.1.0"
version: "3.1.4"
plugin_platform_interface:
dependency: transitive
description:
@@ -372,10 +380,10 @@ packages:
dependency: transitive
description:
name: process
sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09"
sha256: "21e54fd2faf1b5bdd5102afd25012184a6793927648ea81eea80552ac9405b32"
url: "https://pub.dev"
source: hosted
version: "4.2.4"
version: "5.0.2"
riverpod:
dependency: transitive
description:
@@ -401,10 +409,10 @@ packages:
dependency: transitive
description:
name: source_span
sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
url: "https://pub.dev"
source: hosted
version: "1.9.1"
version: "1.10.0"
sprintf:
dependency: transitive
description:
@@ -433,10 +441,10 @@ packages:
dependency: transitive
description:
name: stack_trace
sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
url: "https://pub.dev"
source: hosted
version: "1.11.0"
version: "1.11.1"
state_notifier:
dependency: transitive
description:
@@ -449,10 +457,10 @@ packages:
dependency: transitive
description:
name: stream_channel
sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
url: "https://pub.dev"
source: hosted
version: "2.1.1"
version: "2.1.2"
string_scanner:
dependency: transitive
description:
@@ -489,10 +497,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
url: "https://pub.dev"
source: hosted
version: "0.4.16"
version: "0.6.1"
typed_data:
dependency: transitive
description:
@@ -553,18 +561,18 @@ packages:
dependency: transitive
description:
name: vm_service
sha256: e7fb6c2282f7631712b69c19d1bff82f3767eea33a2321c14fa59ad67ea391c7
sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957
url: "https://pub.dev"
source: hosted
version: "9.4.0"
version: "13.0.0"
webdriver:
dependency: transitive
description:
name: webdriver
sha256: ef67178f0cc7e32c1494645b11639dd1335f1d18814aa8435113a92e9ef9d841
sha256: "003d7da9519e1e5f329422b36c4dcdf18d7d2978d1ba099ea4e45ba490ed845e"
url: "https://pub.dev"
source: hosted
version: "3.0.1"
version: "3.0.3"
win32:
dependency: transitive
description:
@@ -590,5 +598,5 @@ packages:
source: hosted
version: "6.2.2"
sdks:
dart: ">=2.19.0 <3.0.0"
dart: ">=3.2.0-0 <4.0.0"
flutter: ">=3.7.0"
-1
View File
@@ -19,4 +19,3 @@ export 'src/core/widgets/widgets.dart';
export 'src/schema/schema.dart';
export 'src/utils/converters.dart';
export 'src/utils/parsing_utils.dart';
export 'src/utils/size_converters.dart';
+11 -5
View File
@@ -2,6 +2,7 @@ import 'dart:ui';
import 'package:divkit/divkit.dart';
import 'package:divkit/src/core/widgets/root/div_root_widget.dart';
import 'package:divkit/src/utils/configuration.dart';
import 'package:divkit/src/utils/div_scaling_model.dart';
import 'package:divkit/src/utils/provider.dart';
import 'package:divkit/src/utils/trace.dart';
@@ -95,9 +96,14 @@ class DivKitView extends StatelessWidget {
Widget build(BuildContext context) => Directionality(
textDirection: textDirection ?? Directionality.of(context),
child: provide(
DivScalingModel(textScale: textScale, viewScale: viewScale),
DivScalingModel(
textScale: textScale,
viewScale: viewScale,
),
child: provide(
ShowUnsupportedDivs(showUnsupportedDivs),
DivConfiguration(
showUnsupportedDivs: showUnsupportedDivs,
),
child: provide(
cacheManager,
child: FocusScope(
@@ -139,9 +145,6 @@ class _DivKitViewState extends State<_DivKitView> {
DivContext get divContext => divRootContext!;
Widget get loader =>
widget.loadingBuilder?.call(context) ?? const SizedBox.shrink();
void updateContext() {
if (widget.data.preloaded) {
divRootContext = DivRootContext.initSync(
@@ -193,6 +196,9 @@ class _DivKitViewState extends State<_DivKitView> {
}
}
Widget get loader =>
widget.loadingBuilder?.call(context) ?? const SizedBox.shrink();
@override
Widget build(BuildContext context) => divRootContext != null
? DivKitProvider(
@@ -4,6 +4,15 @@ import 'package:divkit/src/core/protocol/div_logger.dart';
import 'package:divkit/src/core/protocol/div_variable.dart';
import 'package:flutter/services.dart';
/// Print logs with information about expression calculations.
bool debugPrintDivExpressionResolve = false;
void _log(String message) {
if (debugPrintDivExpressionResolve) {
loggerUse(const DefaultDivLoggerContext('onResolve')).debug(message);
}
}
final exprResolver = DefaultDivExpressionResolver();
abstract class DivExpressionResolver {
@@ -43,7 +52,7 @@ class DefaultDivExpressionResolver implements DivExpressionResolver {
result = expr.parse!(result);
}
logger.debug(
_log(
"Expr ${expr.source} with args ${context.current}"
"and result $result [${result.runtimeType}]",
);
@@ -9,6 +9,15 @@ import 'package:divkit/src/core/variable/variable_manager.dart';
import 'package:divkit/src/utils/div_focus_node.dart';
import 'package:flutter/widgets.dart';
/// Print logs with information about DivKitView lifecycle.
bool debugPrintDivKitViewLifecycle = false;
void _log(DivLoggerContext? loggerContext, String message) {
if (debugPrintDivKitViewLifecycle) {
loggerUse(loggerContext).debug(message);
}
}
abstract class DivContext {
const DivContext();
@@ -47,7 +56,7 @@ abstract class DivContext {
}
class DivRootContext extends DivContext {
BuildContext? _buildContext;
final BuildContext? _buildContext;
@override
BuildContext get buildContext => _buildContext!;
@@ -140,7 +149,7 @@ class DivRootContext extends DivContext {
final divContext = DivRootContext(context);
final loggerContext = DefaultDivLoggerContext(source.logId);
loggerUse(loggerContext).debug('Init [Async] ${divContext.hashCode}');
_log(loggerContext, 'Async init #${divContext.hashCode}');
// Main initialization
divContext
@@ -171,7 +180,7 @@ class DivRootContext extends DivContext {
.toList(growable: false),
);
loggerUse(loggerContext).debug('Prepared ${divContext.hashCode}');
_log(loggerContext, 'Prepared #${divContext.hashCode}');
return divContext;
}
@@ -194,8 +203,9 @@ class DivRootContext extends DivContext {
final divContext = DivRootContext(context);
final loggerContext = DefaultDivLoggerContext(source.logId);
loggerUse(loggerContext).debug('Init [Sync] ${divContext.hashCode}');
loggerUse(loggerContext).debug('Instant rendering is enabled!');
_log(loggerContext, 'Sync init #${divContext.hashCode}');
_log(loggerContext,
'Instant rendering is enabled! #${divContext.hashCode}');
// Main initialization
divContext
@@ -226,7 +236,7 @@ class DivRootContext extends DivContext {
.toList(growable: false),
);
loggerUse(loggerContext).debug('Prepared ${divContext.hashCode}');
_log(loggerContext, 'Prepared #${divContext.hashCode}');
return divContext;
}
@@ -242,7 +252,7 @@ class DivRootContext extends DivContext {
}
Future<void> dispose() async {
loggerUse(_loggerContext).debug("Dispose $hashCode");
_log(_loggerContext, "Dispose #$hashCode");
_stateManager?.dispose();
_visibilityActionManager?.dispose();
@@ -6,9 +6,11 @@ import 'package:flutter/widgets.dart';
class DivBaseModel with EquatableMixin {
final double opacity;
final bool isGone;
final DivAlignment alignment;
final PassDivSize width;
final PassDivSize height;
final DivSizeValue width;
final DivSizeValue height;
final double? aspect;
final EdgeInsetsGeometry? padding;
final EdgeInsetsGeometry? margin;
final DivDecoration? decoration;
@@ -23,18 +25,21 @@ class DivBaseModel with EquatableMixin {
required this.alignment,
required this.focusDecoration,
required this.divVisibility,
required this.isGone,
this.opacity = 1.0,
this.visibilityActions = const [],
this.padding,
this.margin,
this.aspect,
this.decoration,
this.divId,
});
static DivBaseModel? value(
BuildContext context,
DivBase data,
) {
DivBase data, [
Expression<double>? aspect,
]) {
final divScalingModel = read<DivScalingModel>(context);
final viewScale = divScalingModel?.viewScale ?? 1;
@@ -56,17 +61,16 @@ class DivBaseModel with EquatableMixin {
}
return DivBaseModel(
isGone: data.visibility.requireValue.isGone,
opacity: data.valueOpacity().clamp(0.0, 1.0),
alignment: PassDivAlignment(
data.alignmentVertical,
data.alignmentHorizontal,
).valueAlignment(),
width: data.valueWidth(
extension: margin.horizontal,
viewScale: viewScale,
),
height: data.valueHeight(
extension: margin.vertical,
viewScale: viewScale,
),
visibilityActions: visibilityActionsList,
@@ -74,6 +78,7 @@ class DivBaseModel with EquatableMixin {
viewScale: viewScale,
),
margin: margin,
aspect: aspect?.requireValue,
decoration: data.valueBoxDecoration(
viewScale: viewScale,
),
@@ -95,8 +100,9 @@ class DivBaseModel with EquatableMixin {
static Stream<DivBaseModel> from(
BuildContext context,
DivBase data,
) {
DivBase data, [
Expression<double>? aspect,
]) {
final variables = watch<DivContext>(context)!.variableManager;
final divScalingModel = watch<DivScalingModel>(context);
final viewScale = divScalingModel?.viewScale ?? 1;
@@ -125,6 +131,7 @@ class DivBaseModel with EquatableMixin {
}
return DivBaseModel(
isGone: (await data.visibility.resolveValue(context: context)).isGone,
opacity: (await data.resolveOpacity(
context: context,
))
@@ -137,12 +144,10 @@ class DivBaseModel with EquatableMixin {
),
width: await data.resolveWidth(
context: context,
extension: margin.horizontal,
viewScale: viewScale,
),
height: await data.resolveHeight(
context: context,
extension: margin.vertical,
viewScale: viewScale,
),
visibilityActions: visibilityActionsList,
@@ -151,6 +156,9 @@ class DivBaseModel with EquatableMixin {
viewScale: viewScale,
),
margin: margin,
aspect: await aspect?.resolveValue(
context: context,
),
decoration: await data.resolveBoxDecoration(
context: context,
viewScale: viewScale,
@@ -174,6 +182,7 @@ class DivBaseModel with EquatableMixin {
alignment,
width,
height,
aspect,
padding,
margin,
decoration,
@@ -335,78 +344,50 @@ extension PassDivBase on DivBase {
Future<double> resolveOpacity({
required DivVariableContext context,
}) async {
final opacity = (await visibility.resolveValue(context: context)).asOpacity;
if (opacity != 1) {
final visibility = (await this.visibility.resolveValue(context: context));
if (!visibility.isVisible) {
return 0;
}
return await alpha.resolveValue(context: context);
}
double valueOpacity() {
final opacity = visibility.value!.asOpacity;
if (opacity != 1) {
final visibility = this.visibility.requireValue;
if (!visibility.isVisible) {
return 0;
}
return alpha.value!;
return alpha.requireValue;
}
Future<PassDivSize> resolveWidth({
Future<DivSizeValue> resolveWidth({
required DivVariableContext context,
required double viewScale,
double extension = 0,
}) async {
if ((await visibility.resolveValue(context: context)).isGone) {
return const FixedDivSize(0);
} else {
return await width.resolve(
}) async =>
await width.resolve(
context: context,
extension: extension,
viewScale: viewScale,
);
}
}
PassDivSize valueWidth({
DivSizeValue valueWidth({
required double viewScale,
double extension = 0,
}) {
if (visibility.value!.isGone) {
return const FixedDivSize(0);
} else {
return width.passValue(
extension: extension,
}) =>
width.passValue(
viewScale: viewScale,
);
}
}
Future<PassDivSize> resolveHeight({
Future<DivSizeValue> resolveHeight({
required DivVariableContext context,
required double viewScale,
double extension = 0,
}) async {
if ((await visibility.resolveValue(context: context)).isGone) {
return const FixedDivSize(0);
} else {
return await height.resolve(
}) async =>
await height.resolve(
context: context,
extension: extension,
viewScale: viewScale,
);
}
}
PassDivSize valueHeight({
DivSizeValue valueHeight({
required double viewScale,
double extension = 0,
}) {
if (visibility.value!.isGone) {
return const FixedDivSize(0);
} else {
return height.passValue(
extension: extension,
}) =>
height.passValue(
viewScale: viewScale,
);
}
}
}
@@ -1,9 +1,7 @@
import 'package:divkit/divkit.dart';
import 'package:divkit/src/core/widgets/base/div_base_model.dart';
import 'package:divkit/src/core/widgets/div_visibility_emitter.dart';
import 'package:divkit/src/utils/content_alignment_converters.dart';
import 'package:divkit/src/utils/div_focus_node.dart';
import 'package:divkit/src/utils/provider.dart';
import 'package:flutter/material.dart';
class DivBaseWidget extends StatefulWidget {
@@ -11,6 +9,8 @@ class DivBaseWidget extends StatefulWidget {
final DivTapActionData? tapActionData;
final Expression<double>? aspect;
final Widget child;
const DivBaseWidget({
@@ -18,6 +18,7 @@ class DivBaseWidget extends StatefulWidget {
super.key,
required this.child,
this.tapActionData,
this.aspect,
});
@override
@@ -34,14 +35,14 @@ class _DivBaseWidgetState extends State<DivBaseWidget> {
@override
void initState() {
super.initState();
value = DivBaseModel.value(context, widget.data);
value = DivBaseModel.value(context, widget.data, widget.aspect);
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
stream ??= DivBaseModel.from(context, widget.data);
stream ??= DivBaseModel.from(context, widget.data, widget.aspect);
}
@override
@@ -49,8 +50,8 @@ class _DivBaseWidgetState extends State<DivBaseWidget> {
super.didUpdateWidget(oldWidget);
if (widget.data != oldWidget.data) {
value = DivBaseModel.value(context, widget.data);
stream = DivBaseModel.from(context, widget.data);
value = DivBaseModel.value(context, widget.data, widget.aspect);
stream = DivBaseModel.from(context, widget.data, widget.aspect);
}
}
@@ -62,15 +63,17 @@ class _DivBaseWidgetState extends State<DivBaseWidget> {
if (snapshot.hasData) {
final model = snapshot.requireData;
final focusNode = FocusScope.of(context).getById(model.divId);
final parent = watch<DivParentData>(context);
final contentAlignment = watch<ContentAlignment>(context);
return DivSizeWrapper(
parent: parent,
contentAlignment: contentAlignment,
if (model.isGone) {
return const SizedBox.shrink();
}
return DivLayout(
height: model.height,
width: model.width,
alignment: model.alignment,
margin: model.margin,
aspect: model.aspect,
alignment: model.alignment.asAlignment,
child: DivVisibilityEmitter(
visibilityActions: model.visibilityActions,
divVisibility: model.divVisibility,
@@ -85,23 +88,17 @@ class _DivBaseWidgetState extends State<DivBaseWidget> {
builder: (_, __) => _DecoratedBox(
key: key,
padding: model.padding,
margin: model.margin,
decoration: focusNode.hasFocus
? model.focusDecoration
: model.decoration,
child: RepaintBoundary(
child: widget.child,
),
child: widget.child,
),
)
: _DecoratedBox(
key: key,
padding: model.padding,
margin: model.margin,
decoration: model.decoration,
child: RepaintBoundary(
child: widget.child,
),
child: widget.child,
),
),
),
@@ -121,15 +118,98 @@ class _DivBaseWidgetState extends State<DivBaseWidget> {
}
}
extension PassDivSizeImpl on DivSize {
Future<DivSizeValue> resolve({
required DivVariableContext context,
required double viewScale,
}) async =>
map(
divFixedSize: (fixed) async => DivFixed(
await fixed.resolveDimension(
context: context,
viewScale: viewScale,
),
),
divMatchParentSize: (flex) async => DivMatchParent(
(await flex.weight?.resolveValue(context: context))?.toInt(),
),
divWrapContentSize: (wrapped) async {
final constrained = await wrapped.constrained?.resolveValue(
context: context,
) ??
false;
if (!constrained) {
return DivWrapContent(
min: await wrapped.minSize?.resolveDimension(
context: context,
viewScale: viewScale,
),
max: await wrapped.maxSize?.resolveDimension(
context: context,
viewScale: viewScale,
),
);
}
return DivConstrained(
min: await wrapped.minSize?.resolveDimension(
context: context,
viewScale: viewScale,
),
max: await wrapped.maxSize?.resolveDimension(
context: context,
viewScale: viewScale,
),
);
},
);
DivSizeValue passValue({
required double viewScale,
}) =>
map(
divFixedSize: (fixed) => DivFixed(
fixed.valueDimension(
viewScale: viewScale,
),
),
divMatchParentSize: (flex) => DivMatchParent(
flex.weight?.requireValue.toInt(),
),
divWrapContentSize: (wrapped) {
final constrained = wrapped.constrained?.requireValue ?? false;
if (!constrained) {
return DivWrapContent(
min: wrapped.minSize?.valueDimension(
viewScale: viewScale,
),
max: wrapped.maxSize?.valueDimension(
viewScale: viewScale,
),
);
}
return DivConstrained(
min: wrapped.minSize?.valueDimension(
viewScale: viewScale,
),
max: wrapped.maxSize?.valueDimension(
viewScale: viewScale,
),
);
},
);
}
class _DecoratedBox extends StatelessWidget {
final DivDecoration? decoration;
final Widget child;
final EdgeInsetsGeometry? padding;
final EdgeInsetsGeometry? margin;
const _DecoratedBox({
required this.child,
this.margin,
this.padding,
this.decoration,
super.key,
@@ -138,36 +218,33 @@ class _DecoratedBox extends StatelessWidget {
@override
Widget build(BuildContext context) {
final backgroundWidgets = decoration?.backgroundWidgets ?? <Widget>[];
return Container(
margin: margin,
child: CustomPaint(
painter: _OuterShadowPainter(
outerShadow: decoration?.outerShadow,
borderRadiusCustom: decoration?.customBorderRadius,
),
child: ClipRRect(
borderRadius: decoration?.customBorderRadius.toBorderRadius() ??
BorderRadius.zero,
child: Stack(
fit: StackFit.passthrough,
children: [
...backgroundWidgets
.map(
(widget) => Positioned.fill(
child: widget,
),
)
.toList(),
Container(
decoration: BoxDecoration(
borderRadius: decoration?.customBorderRadius.toBorderRadius(),
border: decoration?.boxDecoration.border,
),
padding: padding,
child: child,
return CustomPaint(
painter: _OuterShadowPainter(
outerShadow: decoration?.outerShadow,
borderRadiusCustom: decoration?.customBorderRadius,
),
child: ClipRRect(
borderRadius: decoration?.customBorderRadius.toBorderRadius() ??
BorderRadius.zero,
child: Stack(
fit: StackFit.passthrough,
children: [
...backgroundWidgets
.map(
(widget) => Positioned.fill(
child: widget,
),
)
.toList(),
Container(
decoration: BoxDecoration(
borderRadius: decoration?.customBorderRadius.toBorderRadius(),
border: decoration?.boxDecoration.border,
),
],
),
padding: padding,
child: child,
),
],
),
),
);
@@ -7,12 +7,10 @@ import 'package:flutter/widgets.dart';
class DivContainerModel with EquatableMixin {
final List<Widget> children;
final ContentAlignment contentAlignment;
final double? aspectRatio;
const DivContainerModel({
required this.contentAlignment,
this.children = const [],
this.aspectRatio,
});
static DivContainerModel? value(
@@ -29,7 +27,6 @@ class DivContainerModel with EquatableMixin {
return DivContainerModel(
contentAlignment: contentAlignment,
aspectRatio: data.aspect?.ratio.requireValue,
children: data.items
?.map(
(e) => DivWidget(e),
@@ -65,9 +62,6 @@ class DivContainerModel with EquatableMixin {
return DivContainerModel(
contentAlignment: contentAlignment,
aspectRatio: await data.aspect?.ratio.resolveValue(
context: context,
),
children: data.items
?.map(
(e) => DivWidget(e),
@@ -82,6 +76,5 @@ class DivContainerModel with EquatableMixin {
List<Object?> get props => [
children,
contentAlignment,
aspectRatio,
];
}
@@ -45,6 +45,7 @@ class _DivContainerWidgetState extends State<DivContainerWidget> {
@override
Widget build(BuildContext context) => DivBaseWidget(
data: widget.data,
aspect: widget.data.aspect?.ratio,
tapActionData: DivTapActionData(
action: widget.data.action,
actions: widget.data.actions,
@@ -57,46 +58,38 @@ class _DivContainerWidgetState extends State<DivContainerWidget> {
builder: (context, snapshot) {
if (snapshot.hasData) {
final model = snapshot.requireData;
final mainWidget = provide(
model.contentAlignment,
child: model.contentAlignment.map(
flex: (data) => provide(
DivParentData.flex,
child: Flex(
direction: data.direction,
mainAxisAlignment: data.mainAxisAlignment,
crossAxisAlignment: data.crossAxisAlignment,
children: model.children,
),
final mainWidget = model.contentAlignment.map(
flex: (data) => provide(
data.direction == Axis.vertical
? DivParentData.column
: DivParentData.row,
child: Flex(
mainAxisSize: MainAxisSize.min,
direction: data.direction,
mainAxisAlignment: data.mainAxisAlignment,
crossAxisAlignment: data.crossAxisAlignment,
children: model.children,
),
wrap: (data) => provide(
DivParentData.wrap,
child: Wrap(
direction: data.direction,
alignment: data.wrapAlignment,
runAlignment: data.runAlignment,
children: model.children,
),
),
wrap: (data) => provide(
DivParentData.wrap,
child: Wrap(
direction: data.direction,
alignment: data.wrapAlignment,
runAlignment: data.runAlignment,
children: model.children,
),
stack: (data) => provide(
DivParentData.stack,
child: Stack(
alignment: data.contentAlignment ??
AlignmentDirectional.topStart,
children: model.children,
),
),
stack: (data) => provide(
DivParentData.stack,
child: Stack(
alignment:
data.contentAlignment ?? AlignmentDirectional.topStart,
children: model.children,
),
),
);
final aspect = model.aspectRatio;
if (aspect != null) {
return AspectRatio(
aspectRatio: aspect,
child: mainWidget,
);
}
return mainWidget;
}
@@ -1,15 +1,9 @@
import 'package:divkit/divkit.dart';
import 'package:divkit/src/utils/configuration.dart';
import 'package:divkit/src/utils/provider.dart';
import 'package:flutter/material.dart';
/// Configuration model.
class ShowUnsupportedDivs {
final bool enabled;
const ShowUnsupportedDivs(this.enabled);
}
class DivErrorWidget extends StatelessWidget {
static const errorColor = Color(0xFFFF0000);
@@ -24,7 +18,7 @@ class DivErrorWidget extends StatelessWidget {
@override
Widget build(BuildContext context) =>
watch<ShowUnsupportedDivs>(context)?.enabled ?? false
watch<DivConfiguration>(context)?.showUnsupportedDivs ?? false
? DivBaseWidget(
data: data,
child: Placeholder(
@@ -22,6 +22,7 @@ class DivVisibilityEmitter extends StatelessWidget {
final isVisible = divVisibility == DivVisibility.visible;
return Visibility(
visible: isVisible,
replacement: IgnorePointer(child: child),
child: IgnorePointer(
ignoring: !isVisible,
child: _DivVisibilityActionEmitter(
@@ -98,9 +99,7 @@ class _DivVisibilityActionEmitterState
}
return VisibilityDetector(
key: ValueKey(
widget.id,
),
key: ValueKey(widget.id),
child: widget.child,
onVisibilityChanged: (VisibilityInfo info) {
divContext.visibilityActionManager.updateActionsStateIfNeed(
@@ -72,19 +72,15 @@ class _DivGalleryWidgetState extends State<DivGalleryWidget> {
return SingleChildScrollView(
scrollDirection: model.orientation,
child: isHorizontal
? provide(
DivParentData.flex,
child: Row(
children: childrenWithSpacing,
),
)
: provide(
DivParentData.flex,
child: Column(
children: childrenWithSpacing,
),
),
child: provide(
isHorizontal ? DivParentData.row : DivParentData.column,
child: Flex(
crossAxisAlignment: CrossAxisAlignment.start,
direction: isHorizontal ? Axis.horizontal : Axis.vertical,
mainAxisSize: MainAxisSize.min,
children: childrenWithSpacing,
),
),
);
}
@@ -10,7 +10,6 @@ class DivImageModel with EquatableMixin {
final Color? color;
final BlendMode? colorBlendMode;
final AlignmentGeometry contentAlignment;
final double? aspectRatio;
final double? blurRadius;
final bool rtlMirror;
@@ -21,7 +20,6 @@ class DivImageModel with EquatableMixin {
required this.rtlMirror,
this.color,
this.colorBlendMode,
this.aspectRatio,
this.blurRadius,
});
@@ -50,7 +48,6 @@ class DivImageModel with EquatableMixin {
color: data.tintColor?.value!,
colorBlendMode: data.tintMode.value!.asBlendMode,
contentAlignment: alignment,
aspectRatio: data.aspect?.ratio.requireValue,
blurRadius: filters.blurRadius == null
? null
: (filters.blurRadius ?? 0) * viewScale,
@@ -107,9 +104,6 @@ class DivImageModel with EquatableMixin {
context: context,
) ??
Alignment.center,
aspectRatio: await data.aspect?.ratio.resolveValue(
context: context,
),
blurRadius: filters.blurRadius == null
? null
: (filters.blurRadius ?? 0) * viewScale,
@@ -125,7 +119,6 @@ class DivImageModel with EquatableMixin {
color,
colorBlendMode,
contentAlignment,
aspectRatio,
blurRadius,
rtlMirror,
];
@@ -54,6 +54,7 @@ class _DivImageWidgetState extends State<DivImageWidget> {
@override
Widget build(BuildContext context) => DivBaseWidget(
data: widget.data,
aspect: widget.data.aspect?.ratio,
tapActionData: DivTapActionData(
action: widget.data.action,
actions: widget.data.actions,
@@ -120,8 +121,6 @@ class _DivImageWidgetState extends State<DivImageWidget> {
}
}
final aspect = model.aspectRatio;
final Widget blurredImage;
final blurRadius = model.blurRadius;
if (blurRadius != null) {
@@ -147,13 +146,6 @@ class _DivImageWidgetState extends State<DivImageWidget> {
mirroredImage = blurredImage;
}
if (aspect != null) {
return AspectRatio(
aspectRatio: aspect,
child: mirroredImage,
);
}
return mirroredImage;
}
@@ -0,0 +1,523 @@
import 'package:divkit/src/core/protocol/div_logger.dart';
import 'package:divkit/src/utils/provider.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
/// Print logs with information about constraints and div sizes.
bool debugPrintDivPerformLayout = false;
void _log(String message) {
if (debugPrintDivPerformLayout) {
loggerUse(const DefaultDivLoggerContext('onLayout')).debug(message);
}
}
/// Layout size parameters.
class DivLayoutParam {
final DivSizeValue width;
final DivSizeValue height;
const DivLayoutParam({
this.width = const DivMatchParent(),
this.height = const DivWrapContent(),
});
DivLayoutParam copyWith({
DivSizeValue? width,
DivSizeValue? height,
}) =>
DivLayoutParam(
width: width ?? this.width,
height: height ?? this.height,
);
@override
String toString() => 'DivLayoutParam($width, $height)';
}
/// The type of the parent container.
enum DivParentData {
none,
wrap,
column,
row,
stack,
pager;
bool get isWrap => this == DivParentData.wrap;
bool get isColumn => this == DivParentData.column;
bool get isRow => this == DivParentData.row;
bool get isStack => this == DivParentData.stack;
bool get isPager => this == DivParentData.pager;
}
/// Generalization of divkit sizes.
abstract class DivSizeValue {
const DivSizeValue();
T map<T>({
required Function(double value) fixed,
required Function(double? min, double? max) wrapContent,
required Function(int weight) matchParent,
required Function(double? min, double? max) constrained,
});
bool get isFixed => false;
bool get isWrapContent => false;
bool get isMatchParent => false;
bool get isConstrained => false;
}
/// Force setting the size.
class DivFixed extends DivSizeValue {
final double value;
const DivFixed(this.value);
@override
T map<T>({
required Function(double value) fixed,
required Function(double? min, double? max) wrapContent,
required Function(int weight) matchParent,
required Function(double? min, double? max) constrained,
}) =>
fixed(value);
@override
bool get isFixed => true;
@override
String toString() => 'fixed($value)';
}
/// Allow the child to be the size it wants and
/// specify the constants within which expects it.
/// Unlike [DivConstrained] allows overflows!
class DivWrapContent extends DivSizeValue {
final double? min;
final double? max;
const DivWrapContent({
this.min,
this.max,
});
@override
T map<T>({
required Function(double value) fixed,
required Function(double? min, double? max) wrapContent,
required Function(int weight) matchParent,
required Function(double? min, double? max) constrained,
}) =>
wrapContent(min, max);
@override
bool get isWrapContent => true;
@override
String toString() => 'wrapContent';
}
/// Force the maximum size of the parent for the child,
/// but we can still specify the weight in [Flex].
class DivMatchParent extends DivSizeValue {
final int? weight;
const DivMatchParent([this.weight = 1]);
@override
T map<T>({
required Function(double value) fixed,
required Function(double? min, double? max) wrapContent,
required Function(int weight) matchParent,
required Function(double? min, double? max) constrained,
}) =>
matchParent(weight ?? 1);
@override
bool get isMatchParent => true;
@override
String toString() => 'matchParent(${weight ?? 1})';
}
/// Like that [DivWrapContent], but doesn't allow overflowing at all.
class DivConstrained extends DivSizeValue {
final double? min;
final double? max;
const DivConstrained({
this.min,
this.max,
});
@override
T map<T>({
required Function(double value) fixed,
required Function(double? min, double? max) wrapContent,
required Function(int weight) matchParent,
required Function(double? min, double? max) constrained,
}) =>
constrained(min, max);
@override
bool get isConstrained => true;
@override
String toString() => 'constrained($min, $max))';
}
/// A unit of a divkit layout system that tries to integrate into an existing one in Flutter.
///
/// All the necessary data about the parent and child are collected here
/// and it can forcibly change the standard [performLayout].
///
/// Note: to debug the divkit layout system, enable logging [debugPrintDivPerformLayout].
class DivLayout extends StatelessWidget {
final DivSizeValue width;
final DivSizeValue height;
final double? aspect;
final EdgeInsetsGeometry? margin;
final AlignmentGeometry? alignment;
final Widget child;
const DivLayout({
super.key,
required this.child,
this.width = const DivMatchParent(),
this.height = const DivWrapContent(),
this.alignment,
this.margin,
this.aspect,
});
DivLayoutParam modifySize(DivParentData? parent, DivLayoutParam? parentSize) {
final parentWidth = parentSize?.width;
final parentHeight = parentSize?.height;
var res = DivLayoutParam(
width: width,
height: height,
);
// When the wrapContent/constrained is specified for the parent, then we force it on children.
if (width.isMatchParent && (parentHeight?.isWrapContent ?? false) ||
(parentHeight?.isConstrained ?? false)) {
res = res.copyWith(
width: parentWidth,
);
}
// When the wrapContent/constrained is specified for the parent, then we force it on children.
if (height.isMatchParent && (parentHeight?.isWrapContent ?? false) ||
(parentHeight?.isConstrained ?? false)) {
res = res.copyWith(
height: parentHeight,
);
}
return res;
}
@override
Widget build(BuildContext context) {
final parent = watch<DivParentData>(context);
final parentSize = watch<DivLayoutParam>(context);
final modified = modifySize(parent, parentSize);
final biggerParent = (parentSize?.height.isMatchParent ?? false) ||
(parentSize?.width.isMatchParent ?? false);
Widget it = Padding(
padding: margin ?? EdgeInsets.zero,
child: DivSizeLayout(
width: modified.width,
height: modified.height,
aspect: aspect,
child: provide(
modified,
child: child,
),
),
);
if (parent == DivParentData.stack && alignment != null && biggerParent) {
return Positioned.fill(
child: Align(
widthFactor: 1,
heightFactor: 1,
alignment: alignment!,
child: it,
),
);
}
if (alignment != null) {
it = Align(
alignment: alignment!,
child: it,
);
}
if (parent == DivParentData.column && modified.height is DivMatchParent) {
final weight = (modified.height as DivMatchParent).weight ?? 1;
return Flexible(flex: weight, child: it);
} else if (parent == DivParentData.row &&
modified.width is DivMatchParent) {
final weight = (modified.width as DivMatchParent).weight ?? 1;
return Flexible(flex: weight, child: it);
}
return it;
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(
DiagnosticsProperty<AlignmentGeometry?>(
'alignment',
alignment,
defaultValue: null,
),
);
properties.add(
DiagnosticsProperty<DivSizeValue>(
'width',
width,
defaultValue: const DivMatchParent(),
),
);
properties.add(
DiagnosticsProperty<DivSizeValue>(
'height',
height,
defaultValue: const DivWrapContent(),
),
);
properties.add(
DiagnosticsProperty<double?>(
'aspect',
aspect,
defaultValue: null,
),
);
}
}
class DivSizeLayout extends SingleChildRenderObjectWidget {
final DivSizeValue width;
final DivSizeValue height;
final double? aspect;
const DivSizeLayout({
super.key,
required Widget child,
this.width = const DivWrapContent(),
this.height = const DivWrapContent(),
this.aspect,
}) : super(child: child);
@override
RenderObject createRenderObject(BuildContext context) => DivSizeLayoutRender(
width,
height,
aspect,
);
@override
void updateRenderObject(
BuildContext context,
covariant DivSizeLayoutRender renderObject,
) =>
renderObject
..width = width
..height = height
..aspect = aspect;
}
class DivSizeLayoutRender extends RenderBox
with RenderObjectWithChildMixin<RenderBox> {
DivSizeValue _width;
set width(DivSizeValue value) {
if (value != _width) {
_width = value;
markNeedsLayout();
}
}
DivSizeValue _height;
set height(DivSizeValue value) {
if (value != _height) {
_height = value;
markNeedsLayout();
}
}
double? _aspect;
set aspect(double? value) {
if (value != _aspect) {
_aspect = value;
markNeedsLayout();
}
}
DivSizeLayoutRender(
DivSizeValue width,
DivSizeValue height,
double? aspect,
) : _width = width,
_height = height,
_aspect = aspect;
@override
bool hitTestSelf(Offset position) => true;
@override
bool hitTestChildren(
BoxHitTestResult result, {
required Offset position,
}) {
if (child != null) {
return child!.hitTest(
result,
position: position,
);
}
return false;
}
@override
void performLayout() {
if (child == null) {
size = constraints.smallest;
return;
}
_log("[begin #$hashCode]");
var it = constraints;
_width.map(
fixed: (value) {
_log("width.fixed($value)");
it = it.copyWith(
minWidth: value,
maxWidth: value,
);
},
wrapContent: (min, max) {
_log("width.wrapContent($min, $max)");
it = it.copyWith(minWidth: min ?? 0, maxWidth: max);
},
matchParent: (weight) {
_log("width.matchParent($weight)");
it = it.copyWith(
minWidth: constraints.hasBoundedWidth ? constraints.maxWidth : 0.0,
maxWidth: constraints.maxWidth,
);
},
constrained: (min, max) {
_log("width.constrained($min, $max)");
it = it.copyWith(
minWidth: constraints.constrainWidth(min ?? constraints.minWidth),
maxWidth: constraints.constrainWidth(max ?? constraints.maxWidth),
);
},
);
if (_aspect == null) {
_height.map(
fixed: (value) {
_log("height.fixed($value)");
it = it.copyWith(
minHeight: value,
maxHeight: value,
);
},
wrapContent: (min, max) {
_log("height.wrapContent($min, $max)");
it = it.copyWith(minHeight: min ?? 0, maxHeight: max);
},
matchParent: (weight) {
_log("height.matchParent($weight)");
it = it.copyWith(
minHeight:
constraints.hasBoundedHeight ? constraints.maxHeight : 0.0,
maxHeight: constraints.maxHeight,
);
},
constrained: (min, max) {
_log("height.constrained($min, $max)");
it = it.copyWith(
minHeight:
constraints.constrainHeight(min ?? constraints.minHeight),
maxHeight:
constraints.constrainHeight(max ?? constraints.maxHeight),
);
},
);
} else {
// This layout passage is needed in order to calculate the width.
child!.layout(it, parentUsesSize: true);
final value = child!.size.width / _aspect!;
_log("Aspect size: $value");
it = it.copyWith(
minHeight: value,
maxHeight: value,
);
}
_log(
"[constraints]\n"
"in: $constraints\n"
"out: $it",
);
child!.layout(it, parentUsesSize: true);
final childWidth = child!.size.width;
final calcWidth = _width is DivMatchParent
? constraints.hasBoundedWidth
? constraints.maxWidth
: childWidth
: _width is DivWrapContent
? childWidth
: constraints.constrainWidth(childWidth);
final childHeight = child!.size.height;
final calcHeight = _height is DivMatchParent
? constraints.hasBoundedHeight
? constraints.maxHeight
: childHeight
: _height is DivWrapContent
? childHeight
: constraints.constrainHeight(childHeight);
_log(
"[size]\n"
"in: ${child!.size}\n"
"out: ${constraints.constrain(Size(calcWidth, calcHeight))}",
);
size = constraints.constrain(Size(calcWidth, calcHeight));
_log("[end #$hashCode]");
}
@override
void paint(PaintingContext context, Offset offset) {
if (child != null) {
context.paintChild(child!, offset);
}
}
}
@@ -43,8 +43,7 @@ class DivPagerModel with EquatableMixin {
PageController controller,
ValueGetter<int> currentPage,
) {
final variables =
DivKitProvider.watch<DivContext>(context)!.variableManager;
final variables = watch<DivContext>(context)!.variableManager;
final id = data.id;
if (id != null && variables.context.current[id] != currentPage()) {
variables.putVariable(id, currentPage());
@@ -25,12 +25,12 @@ class _DivPagerWidgetState extends State<DivPagerWidget> {
@override
void initState() {
super.initState();
value = DivPagerModel.value(context, widget.data);
currentPage = widget.data.defaultItem.value ?? 0;
controller = PageController(
initialPage: currentPage,
);
super.initState();
}
@override
@@ -70,11 +70,14 @@ class _DivPagerWidgetState extends State<DivPagerWidget> {
if (snapshot.hasData) {
final model = snapshot.requireData;
return PageView(
scrollDirection: model.orientation,
controller: controller,
onPageChanged: (value) => onPageChanged(value),
children: model.children,
return provide(
DivParentData.pager,
child: PageView(
scrollDirection: model.orientation,
controller: controller,
onPageChanged: (value) => onPageChanged(value),
children: model.children,
),
);
}
@@ -87,7 +90,7 @@ class _DivPagerWidgetState extends State<DivPagerWidget> {
currentPage = value;
final id = widget.data.id;
if (id != null) {
final divContext = DivKitProvider.watch<DivContext>(context)!;
final divContext = watch<DivContext>(context)!;
divContext.variableManager.updateVariable(id, currentPage);
}
}
@@ -95,6 +98,7 @@ class _DivPagerWidgetState extends State<DivPagerWidget> {
@override
void dispose() {
controller.dispose();
value = null;
stream = null;
super.dispose();
}
@@ -32,13 +32,16 @@ class DivRootModel with EquatableMixin {
final variables = watch<DivContext>(context)!.variableManager;
final state = watch<DivContext>(context)!.stateManager;
return state.watch<DivRootModel>((states) async {
// To avoid errors in the first frame due to outdated variable
// context data, needs to preload all the variables used in div.
await data.preload(variables.context.current);
return DivRootModel(
final model = DivRootModel(
stateId: states['root']!,
states: data.states,
);
// To avoid errors in the first frame due to outdated variable
// context data, needs to preload all the variables used in state.
await model.state.preload(variables.context.current);
return model;
}).distinct();
}
@@ -78,10 +78,7 @@ class DivStateModel with EquatableMixin {
final state = watch<DivContext>(context)!.stateManager;
return state.watch<DivStateModel>((states) async {
// To avoid errors in the first frame due to outdated variable
// context data, needs to preload all the variables used in div.
await data.preload(variables.context.current);
return DivStateModel(
final model = DivStateModel(
divId: divId,
stateId: states[divId],
defaultStateId: await data.defaultStateId?.resolveValue(
@@ -89,6 +86,12 @@ class DivStateModel with EquatableMixin {
),
states: data.states,
);
// To avoid errors in the first frame due to outdated variable
// context data, needs to preload all the variables used in state.
await model.state?.preload(variables.context.current);
return model;
}).distinct();
}
@@ -54,11 +54,13 @@ class _DivStateWidgetState extends State<DivStateWidget> {
if (snapshot.hasData) {
final model = snapshot.requireData;
return provide(
DivStateId(model.divId),
child: DivWidget(
// The unique identifier of the state subtree
key: ValueKey(model.path),
model.state?.div,
DivParentData.none,
child: provide(
DivStateId(model.divId),
child: DivWidget(
// The unique identifier of the state subtree
key: ValueKey(model.path), model.state?.div,
),
),
);
}
@@ -7,6 +7,7 @@ export 'div_widget.dart';
export 'gallery/div_gallery_widget.dart';
export 'image/div_image_widget.dart';
export 'input/div_input_widget.dart';
export 'layout/div_layout.dart';
export 'pager/div_pager_widget.dart';
export 'state/div_state_widget.dart';
export 'text/div_text_widget.dart';
@@ -332,6 +332,38 @@ class Div extends Preloadable with EquatableMixin {
) : value = obj,
_index = 15;
bool get isDivContainer => _index == 0;
bool get isDivCustom => _index == 1;
bool get isDivGallery => _index == 2;
bool get isDivGifImage => _index == 3;
bool get isDivGrid => _index == 4;
bool get isDivImage => _index == 5;
bool get isDivIndicator => _index == 6;
bool get isDivInput => _index == 7;
bool get isDivPager => _index == 8;
bool get isDivSelect => _index == 9;
bool get isDivSeparator => _index == 10;
bool get isDivSlider => _index == 11;
bool get isDivState => _index == 12;
bool get isDivTabs => _index == 13;
bool get isDivText => _index == 14;
bool get isDivVideo => _index == 15;
@override
Future<void> preload(Map<String, dynamic> context) => value.preload(context);
@@ -161,6 +161,25 @@ enum DivAccessibilityType implements Preloadable {
final String value;
const DivAccessibilityType(this.value);
bool get isNone => this == none;
bool get isButton => this == button;
bool get isImage => this == image;
bool get isText => this == text;
bool get isEditText => this == editText;
bool get isHeader => this == header;
bool get isTabBar => this == tabBar;
bool get isList => this == list;
bool get isSelect => this == select;
bool get isAuto => this == auto;
T map<T>({
required T Function() none,
@@ -317,6 +336,11 @@ enum DivAccessibilityMode implements Preloadable {
final String value;
const DivAccessibilityMode(this.value);
bool get isDefault => this == default_;
bool get isMerge => this == merge;
bool get isExclude => this == exclude;
T map<T>({
required T Function() default_,
@@ -298,6 +298,9 @@ enum DivActionTarget implements Preloadable {
final String value;
const DivActionTarget(this.value);
bool get isSelf => this == self;
bool get isBlank => this == blank;
T map<T>({
required T Function() self,
@@ -65,6 +65,10 @@ class DivActionCopyToClipboardContent extends Preloadable with EquatableMixin {
) : value = obj,
_index = 1;
bool get isContentText => _index == 0;
bool get isContentUrl => _index == 1;
@override
Future<void> preload(Map<String, dynamic> context) => value.preload(context);
@@ -179,6 +179,22 @@ class DivActionTyped extends Preloadable with EquatableMixin {
) : value = obj,
_index = 7;
bool get isDivActionArrayInsertValue => _index == 0;
bool get isDivActionArrayRemoveValue => _index == 1;
bool get isDivActionArraySetValue => _index == 2;
bool get isDivActionClearFocus => _index == 3;
bool get isDivActionCopyToClipboard => _index == 4;
bool get isDivActionDictSetValue => _index == 5;
bool get isDivActionFocusElement => _index == 6;
bool get isDivActionSetVariable => _index == 7;
@override
Future<void> preload(Map<String, dynamic> context) => value.preload(context);
@@ -12,6 +12,15 @@ enum DivAlignmentHorizontal implements Preloadable {
final String value;
const DivAlignmentHorizontal(this.value);
bool get isLeft => this == left;
bool get isCenter => this == center;
bool get isRight => this == right;
bool get isStart => this == start;
bool get isEnd => this == end;
T map<T>({
required T Function() left,
@@ -11,6 +11,13 @@ enum DivAlignmentVertical implements Preloadable {
final String value;
const DivAlignmentVertical(this.value);
bool get isTop => this == top;
bool get isCenter => this == center;
bool get isBottom => this == bottom;
bool get isBaseline => this == baseline;
T map<T>({
required T Function() top,
@@ -202,6 +202,17 @@ enum DivAnimationName implements Preloadable {
final String value;
const DivAnimationName(this.value);
bool get isFade => this == fade;
bool get isTranslate => this == translate;
bool get isScale => this == scale;
bool get isNative => this == native;
bool get isSet => this == set;
bool get isNoAnimation => this == noAnimation;
T map<T>({
required T Function() fade,
@@ -13,6 +13,17 @@ enum DivAnimationInterpolator implements Preloadable {
final String value;
const DivAnimationInterpolator(this.value);
bool get isLinear => this == linear;
bool get isEase => this == ease;
bool get isEaseIn => this == easeIn;
bool get isEaseOut => this == easeOut;
bool get isEaseInOut => this == easeInOut;
bool get isSpring => this == spring;
T map<T>({
required T Function() linear,
@@ -103,6 +103,14 @@ class DivAppearanceTransition extends Preloadable with EquatableMixin {
) : value = obj,
_index = 3;
bool get isDivAppearanceSetTransition => _index == 0;
bool get isDivFadeTransition => _index == 1;
bool get isDivScaleTransition => _index == 2;
bool get isDivSlideTransition => _index == 3;
@override
Future<void> preload(Map<String, dynamic> context) => value.preload(context);
@@ -122,6 +122,16 @@ class DivBackground extends Preloadable with EquatableMixin {
) : value = obj,
_index = 4;
bool get isDivImageBackground => _index == 0;
bool get isDivLinearGradient => _index == 1;
bool get isDivNinePatchBackground => _index == 2;
bool get isDivRadialGradient => _index == 3;
bool get isDivSolidBackground => _index == 4;
@override
Future<void> preload(Map<String, dynamic> context) => value.preload(context);
@@ -13,6 +13,17 @@ enum DivBlendMode implements Preloadable {
final String value;
const DivBlendMode(this.value);
bool get isSourceIn => this == sourceIn;
bool get isSourceAtop => this == sourceAtop;
bool get isDarken => this == darken;
bool get isLighten => this == lighten;
bool get isMultiply => this == multiply;
bool get isScreen => this == screen;
T map<T>({
required T Function() sourceIn,
@@ -65,6 +65,10 @@ class DivChangeTransition extends Preloadable with EquatableMixin {
) : value = obj,
_index = 1;
bool get isDivChangeBoundsTransition => _index == 0;
bool get isDivChangeSetTransition => _index == 1;
@override
Future<void> preload(Map<String, dynamic> context) => value.preload(context);
@@ -1094,6 +1094,11 @@ enum DivContainerOrientation implements Preloadable {
final String value;
const DivContainerOrientation(this.value);
bool get isVertical => this == vertical;
bool get isHorizontal => this == horizontal;
bool get isOverlap => this == overlap;
T map<T>({
required T Function() vertical,
@@ -1179,6 +1184,9 @@ enum DivContainerLayoutMode implements Preloadable {
final String value;
const DivContainerLayoutMode(this.value);
bool get isNoWrap => this == noWrap;
bool get isWrap => this == wrap;
T map<T>({
required T Function() noWrap,
@@ -15,6 +15,21 @@ enum DivContentAlignmentHorizontal implements Preloadable {
final String value;
const DivContentAlignmentHorizontal(this.value);
bool get isLeft => this == left;
bool get isCenter => this == center;
bool get isRight => this == right;
bool get isStart => this == start;
bool get isEnd => this == end;
bool get isSpaceBetween => this == spaceBetween;
bool get isSpaceAround => this == spaceAround;
bool get isSpaceEvenly => this == spaceEvenly;
T map<T>({
required T Function() left,
@@ -14,6 +14,19 @@ enum DivContentAlignmentVertical implements Preloadable {
final String value;
const DivContentAlignmentVertical(this.value);
bool get isTop => this == top;
bool get isCenter => this == center;
bool get isBottom => this == bottom;
bool get isBaseline => this == baseline;
bool get isSpaceBetween => this == spaceBetween;
bool get isSpaceAround => this == spaceAround;
bool get isSpaceEvenly => this == spaceEvenly;
T map<T>({
required T Function() top,
@@ -65,6 +65,10 @@ class DivCount extends Preloadable with EquatableMixin {
) : value = obj,
_index = 1;
bool get isDivFixedCount => _index == 0;
bool get isDivInfinityCount => _index == 1;
@override
Future<void> preload(Map<String, dynamic> context) => value.preload(context);
@@ -46,6 +46,8 @@ class DivDrawable extends Preloadable with EquatableMixin {
) : value = obj,
_index = 0;
bool get isDivShapeDrawable => _index == 0;
@override
Future<void> preload(Map<String, dynamic> context) => value.preload(context);
@@ -65,6 +65,10 @@ class DivFilter extends Preloadable with EquatableMixin {
) : value = obj,
_index = 1;
bool get isDivBlur => _index == 0;
bool get isDivFilterRtlMirror => _index == 1;
@override
Future<void> preload(Map<String, dynamic> context) => value.preload(context);
@@ -11,6 +11,13 @@ enum DivFontWeight implements Preloadable {
final String value;
const DivFontWeight(this.value);
bool get isLight => this == light;
bool get isMedium => this == medium;
bool get isRegular => this == regular;
bool get isBold => this == bold;
T map<T>({
required T Function() light,
@@ -840,6 +840,11 @@ enum DivGalleryCrossContentAlignment implements Preloadable {
final String value;
const DivGalleryCrossContentAlignment(this.value);
bool get isStart => this == start;
bool get isCenter => this == center;
bool get isEnd => this == end;
T map<T>({
required T Function() start,
@@ -925,6 +930,9 @@ enum DivGalleryScrollMode implements Preloadable {
final String value;
const DivGalleryScrollMode(this.value);
bool get isPaging => this == paging;
bool get isDefault => this == default_;
T map<T>({
required T Function() paging,
@@ -1000,6 +1008,9 @@ enum DivGalleryOrientation implements Preloadable {
final String value;
const DivGalleryOrientation(this.value);
bool get isHorizontal => this == horizontal;
bool get isVertical => this == vertical;
T map<T>({
required T Function() horizontal,
@@ -1075,6 +1086,9 @@ enum DivGalleryScrollbar implements Preloadable {
final String value;
const DivGalleryScrollbar(this.value);
bool get isNone => this == none;
bool get isAuto => this == auto;
T map<T>({
required T Function() none,
@@ -11,6 +11,13 @@ enum DivImageScale implements Preloadable {
final String value;
const DivImageScale(this.value);
bool get isFill => this == fill;
bool get isNoScale => this == noScale;
bool get isFit => this == fit;
bool get isStretch => this == stretch;
T map<T>({
required T Function() fill,
@@ -857,6 +857,11 @@ enum DivIndicatorAnimation implements Preloadable {
final String value;
const DivIndicatorAnimation(this.value);
bool get isScale => this == scale;
bool get isWorm => this == worm;
bool get isSlider => this == slider;
T map<T>({
required T Function() scale,
@@ -69,6 +69,10 @@ class DivIndicatorItemPlacement extends Preloadable with EquatableMixin {
) : value = obj,
_index = 1;
bool get isDivDefaultIndicatorItemPlacement => _index == 0;
bool get isDivStretchIndicatorItemPlacement => _index == 1;
@override
Future<void> preload(Map<String, dynamic> context) => value.preload(context);
@@ -1071,6 +1071,19 @@ enum DivInputKeyboardType implements Preloadable {
final String value;
const DivInputKeyboardType(this.value);
bool get isSingleLineText => this == singleLineText;
bool get isMultiLineText => this == multiLineText;
bool get isPhone => this == phone;
bool get isNumber => this == number;
bool get isEmail => this == email;
bool get isUri => this == uri;
bool get isPassword => this == password;
T map<T>({
required T Function() singleLineText,
@@ -85,6 +85,12 @@ class DivInputMask extends Preloadable with EquatableMixin {
) : value = obj,
_index = 2;
bool get isDivCurrencyInputMask => _index == 0;
bool get isDivFixedLengthInputMask => _index == 1;
bool get isDivPhoneInputMask => _index == 2;
@override
Future<void> preload(Map<String, dynamic> context) => value.preload(context);
@@ -66,6 +66,10 @@ class DivInputValidator extends Preloadable with EquatableMixin {
) : value = obj,
_index = 1;
bool get isDivInputValidatorExpression => _index == 0;
bool get isDivInputValidatorRegex => _index == 1;
@override
Future<void> preload(Map<String, dynamic> context) => value.preload(context);
@@ -9,6 +9,9 @@ enum DivLineStyle implements Preloadable {
final String value;
const DivLineStyle(this.value);
bool get isNone => this == none;
bool get isSingle => this == single;
T map<T>({
required T Function() none,
@@ -66,6 +66,10 @@ class DivPageTransformation extends Preloadable with EquatableMixin {
) : value = obj,
_index = 1;
bool get isDivPageTransformationOverlap => _index == 0;
bool get isDivPageTransformationSlide => _index == 1;
@override
Future<void> preload(Map<String, dynamic> context) => value.preload(context);
@@ -816,6 +816,9 @@ enum DivPagerOrientation implements Preloadable {
final String value;
const DivPagerOrientation(this.value);
bool get isHorizontal => this == horizontal;
bool get isVertical => this == vertical;
T map<T>({
required T Function() horizontal,
@@ -65,6 +65,10 @@ class DivPagerLayoutMode extends Preloadable with EquatableMixin {
) : value = obj,
_index = 1;
bool get isDivNeighbourPageSize => _index == 0;
bool get isDivPageSize => _index == 1;
@override
Future<void> preload(Map<String, dynamic> context) => value.preload(context);
@@ -155,6 +155,9 @@ enum DivPatchMode implements Preloadable {
final String value;
const DivPatchMode(this.value);
bool get isTransactional => this == transactional;
bool get isPartial => this == partial;
T map<T>({
required T Function() transactional,
@@ -65,6 +65,10 @@ class DivPivot extends Preloadable with EquatableMixin {
) : value = obj,
_index = 1;
bool get isDivPivotFixed => _index == 0;
bool get isDivPivotPercentage => _index == 1;
@override
Future<void> preload(Map<String, dynamic> context) => value.preload(context);
@@ -68,6 +68,10 @@ class DivRadialGradientCenter extends Preloadable with EquatableMixin {
) : value = obj,
_index = 1;
bool get isDivRadialGradientFixedCenter => _index == 0;
bool get isDivRadialGradientRelativeCenter => _index == 1;
@override
Future<void> preload(Map<String, dynamic> context) => value.preload(context);
@@ -67,6 +67,10 @@ class DivRadialGradientRadius extends Preloadable with EquatableMixin {
) : value = obj,
_index = 1;
bool get isDivFixedSize => _index == 0;
bool get isDivRadialGradientRelativeRadius => _index == 1;
@override
Future<void> preload(Map<String, dynamic> context) => value.preload(context);
@@ -81,6 +81,13 @@ enum DivRadialGradientRelativeRadiusValue implements Preloadable {
final String value;
const DivRadialGradientRelativeRadiusValue(this.value);
bool get isNearestCorner => this == nearestCorner;
bool get isFarthestCorner => this == farthestCorner;
bool get isNearestSide => this == nearestSide;
bool get isFarthestSide => this == farthestSide;
T map<T>({
required T Function() nearestCorner,
@@ -899,6 +899,9 @@ enum DivSeparatorDelimiterStyleOrientation implements Preloadable {
final String value;
const DivSeparatorDelimiterStyleOrientation(this.value);
bool get isVertical => this == vertical;
bool get isHorizontal => this == horizontal;
T map<T>({
required T Function() vertical,
@@ -65,6 +65,10 @@ class DivShape extends Preloadable with EquatableMixin {
) : value = obj,
_index = 1;
bool get isDivCircleShape => _index == 0;
bool get isDivRoundedRectangleShape => _index == 1;
@override
Future<void> preload(Map<String, dynamic> context) => value.preload(context);
@@ -84,6 +84,12 @@ class DivSize extends Preloadable with EquatableMixin {
) : value = obj,
_index = 2;
bool get isDivFixedSize => _index == 0;
bool get isDivMatchParentSize => _index == 1;
bool get isDivWrapContentSize => _index == 2;
@override
Future<void> preload(Map<String, dynamic> context) => value.preload(context);
@@ -10,6 +10,11 @@ enum DivSizeUnit implements Preloadable {
final String value;
const DivSizeUnit(this.value);
bool get isDp => this == dp;
bool get isSp => this == sp;
bool get isPx => this == px;
T map<T>({
required T Function() dp,
@@ -152,6 +152,13 @@ enum DivSlideTransitionEdge implements Preloadable {
final String value;
const DivSlideTransitionEdge(this.value);
bool get isLeft => this == left;
bool get isTop => this == top;
bool get isRight => this == right;
bool get isBottom => this == bottom;
T map<T>({
required T Function() left,
@@ -1283,6 +1283,11 @@ enum DivTabsTabTitleStyleAnimationType implements Preloadable {
final String value;
const DivTabsTabTitleStyleAnimationType(this.value);
bool get isSlide => this == slide;
bool get isFade => this == fade;
bool get isNone => this == none;
T map<T>({
required T Function() slide,
@@ -1823,6 +1823,13 @@ enum DivTextTruncate implements Preloadable {
final String value;
const DivTextTruncate(this.value);
bool get isNone => this == none;
bool get isStart => this == start;
bool get isEnd => this == end;
bool get isMiddle => this == middle;
T map<T>({
required T Function() none,
@@ -65,6 +65,10 @@ class DivTextGradient extends Preloadable with EquatableMixin {
) : value = obj,
_index = 1;
bool get isDivLinearGradient => _index == 0;
bool get isDivRadialGradient => _index == 1;
@override
Future<void> preload(Map<String, dynamic> context) => value.preload(context);
@@ -46,6 +46,8 @@ class DivTextRangeBackground extends Preloadable with EquatableMixin {
) : value = obj,
_index = 0;
bool get isDivSolidBackground => _index == 0;
@override
Future<void> preload(Map<String, dynamic> context) => value.preload(context);
@@ -168,6 +168,23 @@ enum DivTooltipPosition implements Preloadable {
final String value;
const DivTooltipPosition(this.value);
bool get isLeft => this == left;
bool get isTopLeft => this == topLeft;
bool get isTop => this == top;
bool get isTopRight => this == topRight;
bool get isRight => this == right;
bool get isBottomRight => this == bottomRight;
bool get isBottom => this == bottom;
bool get isBottomLeft => this == bottomLeft;
bool get isCenter => this == center;
T map<T>({
required T Function() left,
@@ -11,6 +11,13 @@ enum DivTransitionSelector implements Preloadable {
final String value;
const DivTransitionSelector(this.value);
bool get isNone => this == none;
bool get isDataChange => this == dataChange;
bool get isStateChange => this == stateChange;
bool get isAnyChange => this == anyChange;
T map<T>({
required T Function() none,
@@ -10,6 +10,11 @@ enum DivTransitionTrigger implements Preloadable {
final String value;
const DivTransitionTrigger(this.value);
bool get isDataChange => this == dataChange;
bool get isStateChange => this == stateChange;
bool get isVisibilityChange => this == visibilityChange;
T map<T>({
required T Function() dataChange,
@@ -117,6 +117,9 @@ enum DivTriggerMode implements Preloadable {
final String value;
const DivTriggerMode(this.value);
bool get isOnCondition => this == onCondition;
bool get isOnVariable => this == onVariable;
T map<T>({
required T Function() onCondition,
@@ -179,6 +179,22 @@ class DivTypedValue extends Preloadable with EquatableMixin {
) : value = obj,
_index = 7;
bool get isArrayValue => _index == 0;
bool get isBooleanValue => _index == 1;
bool get isColorValue => _index == 2;
bool get isDictValue => _index == 3;
bool get isIntegerValue => _index == 4;
bool get isNumberValue => _index == 5;
bool get isStringValue => _index == 6;
bool get isUrlValue => _index == 7;
@override
Future<void> preload(Map<String, dynamic> context) => value.preload(context);
@@ -179,6 +179,22 @@ class DivVariable extends Preloadable with EquatableMixin {
) : value = obj,
_index = 7;
bool get isArrayVariable => _index == 0;
bool get isBooleanVariable => _index == 1;
bool get isColorVariable => _index == 2;
bool get isDictVariable => _index == 3;
bool get isIntegerVariable => _index == 4;
bool get isNumberVariable => _index == 5;
bool get isStringVariable => _index == 6;
bool get isUrlVariable => _index == 7;
@override
Future<void> preload(Map<String, dynamic> context) => value.preload(context);
@@ -10,6 +10,11 @@ enum DivVideoScale implements Preloadable {
final String value;
const DivVideoScale(this.value);
bool get isFill => this == fill;
bool get isNoScale => this == noScale;
bool get isFit => this == fit;
T map<T>({
required T Function() fill,
@@ -10,6 +10,11 @@ enum DivVisibility implements Preloadable {
final String value;
const DivVisibility(this.value);
bool get isVisible => this == visible;
bool get isInvisible => this == invisible;
bool get isGone => this == gone;
T map<T>({
required T Function() visible,
@@ -0,0 +1,8 @@
/// Experiments and optional features.
class DivConfiguration {
final bool showUnsupportedDivs;
const DivConfiguration({
this.showUnsupportedDivs = false,
});
}
@@ -79,22 +79,10 @@ abstract class ContentAlignment {
const ContentAlignment();
T map<T>({
required T Function(FlexContentAlignment) flex,
required T Function(WrapContentAlignment) wrap,
required T Function(StackContentAlignment) stack,
}) {
final value = this;
if (value is FlexContentAlignment) {
return flex(value);
}
if (value is WrapContentAlignment) {
return wrap(value);
}
if (value is StackContentAlignment) {
return stack(value);
}
throw Exception("Unsupported ContentAlignment type");
}
required T Function(FlexContentAlignment data) flex,
required T Function(WrapContentAlignment data) wrap,
required T Function(StackContentAlignment data) stack,
});
}
class FlexContentAlignment extends ContentAlignment with EquatableMixin {
@@ -109,6 +97,14 @@ class FlexContentAlignment extends ContentAlignment with EquatableMixin {
required this.mainAxisAlignment,
});
@override
T map<T>({
required T Function(FlexContentAlignment data) flex,
required T Function(WrapContentAlignment data) wrap,
required T Function(StackContentAlignment data) stack,
}) =>
flex(this);
@override
List<Object?> get props => [direction, crossAxisAlignment, mainAxisAlignment];
}
@@ -125,6 +121,14 @@ class WrapContentAlignment extends ContentAlignment with EquatableMixin {
required this.runAlignment,
});
@override
T map<T>({
required T Function(FlexContentAlignment data) flex,
required T Function(WrapContentAlignment data) wrap,
required T Function(StackContentAlignment data) stack,
}) =>
wrap(this);
@override
List<Object?> get props => [direction, wrapAlignment, runAlignment];
}
@@ -136,6 +140,14 @@ class StackContentAlignment extends ContentAlignment with EquatableMixin {
required this.contentAlignment,
});
@override
T map<T>({
required T Function(FlexContentAlignment data) flex,
required T Function(WrapContentAlignment data) wrap,
required T Function(StackContentAlignment data) stack,
}) =>
stack(this);
@override
List<Object?> get props => [contentAlignment];
}
@@ -163,7 +175,6 @@ class PassDivContentAlignment {
final divVertical = await safeVertical.resolveValue(context: context);
final divHorizontal = await safeHorizontal.resolveValue(context: context);
final isWrap = await layoutMode.resolveValue(context: context) ==
DivContainerLayoutMode.wrap;
@@ -80,15 +80,15 @@ class DivAlignment with EquatableMixin {
);
} else if (safeVertical != null) {
return safeVertical.map(
start: () => Alignment.topCenter,
center: () => Alignment.center,
end: () => Alignment.bottomCenter,
start: () => Alignment.topLeft,
center: () => Alignment.centerLeft,
end: () => Alignment.bottomLeft,
);
} else if (safeHorizontal != null) {
return safeHorizontal.map(
start: () => Alignment.centerLeft,
center: () => Alignment.center,
end: () => Alignment.centerRight,
start: () => Alignment.topLeft,
center: () => Alignment.topCenter,
end: () => Alignment.topRight,
);
}
return null;
@@ -845,21 +845,6 @@ extension DivSizeUnitMultiplier on DivSizeUnit {
}
}
extension PassDivVisibility on DivVisibility {
bool get isGone => this == DivVisibility.gone;
double? get asOpacity {
switch (this) {
case DivVisibility.visible:
return 1;
case DivVisibility.invisible:
return 0;
case DivVisibility.gone:
return null;
}
}
}
extension PassDivImageScale on DivImageScale {
BoxFit get asBoxFit {
switch (this) {
@@ -1,566 +0,0 @@
import 'package:divkit/divkit.dart';
import 'package:divkit/src/utils/content_alignment_converters.dart';
import 'package:divkit/src/utils/provider.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
abstract class PassDivSize {
const PassDivSize();
T map<T>({
required T Function(FixedDivSize) fixed,
required T Function(FlexDivSize) flex,
required T Function(WrapDivSize) wrapped,
}) {
final value = this;
if (value is FixedDivSize) {
return fixed(value);
}
if (value is FlexDivSize) {
return flex(value);
}
if (value is WrapDivSize) {
return wrapped(value);
}
throw Exception("Unsupported ContentAlignment type");
}
double? get maxConstraint => map(
fixed: (fixed) => fixed.size,
flex: (_) => null,
wrapped: (wrapped) => wrapped.maxSize?.size ?? double.infinity,
);
double? get minConstraint => map(
fixed: (fixed) => fixed.size,
flex: (_) => null,
wrapped: (wrapped) => wrapped.minSize?.size ?? 0,
);
}
class FixedDivSize extends PassDivSize with EquatableMixin {
final double size;
const FixedDivSize(this.size);
@override
List<Object?> get props => [size];
}
class FlexDivSize extends PassDivSize with EquatableMixin {
final int? weight;
const FlexDivSize(this.weight);
@override
List<Object?> get props => [weight];
}
class WrapDivSize extends PassDivSize with EquatableMixin {
final bool? constrained;
final FixedDivSize? maxSize;
final FixedDivSize? minSize;
const WrapDivSize(
this.constrained, {
required this.maxSize,
required this.minSize,
});
@override
List<Object?> get props => [constrained, maxSize, minSize];
}
extension PassDivSizeImpl on DivSize {
Future<PassDivSize> resolve({
required DivVariableContext context,
required double viewScale,
double extension = 0,
}) async =>
map(
divFixedSize: (fixed) async => FixedDivSize(
await fixed.resolveDimension(
context: context,
viewScale: viewScale,
) +
extension * viewScale,
),
divMatchParentSize: (flex) async => FlexDivSize(
(await flex.weight?.resolveValue(context: context))?.toInt(),
),
divWrapContentSize: (wrapped) async {
final FixedDivSize? parsedMax, parsedMin;
final maxSize = wrapped.maxSize;
final minSize = wrapped.minSize;
if (maxSize != null) {
parsedMax = FixedDivSize(
await maxSize.resolveDimension(
context: context,
viewScale: viewScale,
) +
extension * viewScale,
);
} else {
parsedMax = null;
}
if (minSize != null) {
parsedMin = FixedDivSize(
await minSize.resolveDimension(
context: context,
viewScale: viewScale,
) +
extension * viewScale,
);
} else {
parsedMin = null;
}
return WrapDivSize(
await wrapped.constrained?.resolveValue(context: context),
maxSize: parsedMax,
minSize: parsedMin,
);
},
);
PassDivSize passValue({
required double viewScale,
double extension = 0,
}) =>
map(
divFixedSize: (fixed) => FixedDivSize(
fixed.valueDimension(
viewScale: viewScale,
) +
extension * viewScale,
),
divMatchParentSize: (flex) => FlexDivSize(
flex.weight?.requireValue.toInt(),
),
divWrapContentSize: (wrapped) {
final FixedDivSize? parsedMax, parsedMin;
final maxSize = wrapped.maxSize;
final minSize = wrapped.minSize;
if (maxSize != null) {
parsedMax = FixedDivSize(
maxSize.valueDimension(
viewScale: viewScale,
) +
extension * viewScale,
);
} else {
parsedMax = null;
}
if (minSize != null) {
parsedMin = FixedDivSize(
minSize.valueDimension(
viewScale: viewScale,
) +
extension * viewScale,
);
} else {
parsedMin = null;
}
return WrapDivSize(
wrapped.constrained?.requireValue,
maxSize: parsedMax,
minSize: parsedMin,
);
},
);
}
abstract class _AxisWrapper extends StatelessWidget {
Axis get primaryAxis;
DivAxisAlignment? get alignment;
ContentAlignment? get contentAlignment;
PassDivSize get sizeOnCrossAxis;
bool get canExpand {
final contentAlignment = this.contentAlignment;
return alignment != null &&
(contentAlignment is! FlexContentAlignment ||
contentAlignment.direction != primaryAxis);
}
int? get flex {
final contentAlignment = this.contentAlignment;
if (contentAlignment is FlexContentAlignment) {
return contentAlignment.direction == primaryAxis ||
sizeOnCrossAxis is FixedDivSize
? 0
: 1;
}
return null;
}
const _AxisWrapper();
}
class _HorizontalWrapper extends _AxisWrapper {
@override
final primaryAxis = Axis.horizontal;
@override
final DivAxisAlignment? alignment;
@override
final ContentAlignment? contentAlignment;
@override
final PassDivSize sizeOnCrossAxis;
final Widget child;
const _HorizontalWrapper({
required this.child,
required this.alignment,
required this.contentAlignment,
required this.sizeOnCrossAxis,
});
@override
Widget build(BuildContext context) {
final row = Row(
mainAxisAlignment: alignment?.asMainAxis ?? MainAxisAlignment.start,
mainAxisSize: canExpand ? MainAxisSize.max : MainAxisSize.min,
children: [child],
);
final flex = super.flex;
if (flex != null) {
return Flexible(
flex: flex,
child: row,
);
}
return row;
}
}
class _VerticalWrapper extends _AxisWrapper {
@override
final primaryAxis = Axis.vertical;
@override
final DivAxisAlignment? alignment;
@override
final ContentAlignment? contentAlignment;
@override
final PassDivSize sizeOnCrossAxis;
final Widget child;
const _VerticalWrapper({
required this.child,
required this.alignment,
required this.contentAlignment,
required this.sizeOnCrossAxis,
});
@override
Widget build(BuildContext context) {
final column = Column(
mainAxisAlignment: alignment?.asMainAxis ?? MainAxisAlignment.start,
mainAxisSize: canExpand ? MainAxisSize.max : MainAxisSize.min,
children: [child],
);
final flex = super.flex;
if (flex != null) {
return Flexible(
flex: flex,
child: column,
);
}
return column;
}
}
enum DivParentData {
none,
box,
wrap,
flex,
stack;
bool get isBox => this == DivParentData.box;
bool get isWrap => this == DivParentData.wrap;
bool get isFlex => this == DivParentData.flex;
bool get isStack => this == DivParentData.stack;
static DivParentData? from(ParentData? data) {
if (data is FlexParentData) {
return DivParentData.flex;
} else if (data is StackParentData) {
return DivParentData.stack;
} else if (data is WrapParentData) {
return DivParentData.wrap;
} else if (data is BoxParentData) {
return DivParentData.box;
}
return null;
}
}
class DivSizeWrapper extends StatelessWidget {
final PassDivSize width;
final PassDivSize height;
final Widget child;
final DivAlignment alignment;
final ContentAlignment? contentAlignment;
final DivParentData? parent;
const DivSizeWrapper({
required this.child,
required this.height,
required this.width,
required this.alignment,
required this.contentAlignment,
required this.parent,
super.key,
});
@override
Widget build(BuildContext context) {
final safeWidth = width;
final safeHeight = height;
final safeAlignment = alignment;
final maxWidth = safeWidth.maxConstraint;
final minWidth = safeWidth.minConstraint;
final maxHeight = safeHeight.maxConstraint;
final minHeight = safeHeight.minConstraint;
final safeContentAlignment = contentAlignment;
final isFlex = parent?.isFlex ?? false;
final isStack = parent?.isStack ?? false;
final Widget wrapper;
/// Used for stack when layouting complicated objects (Column or Row inside stack with custom position)
final bool dropStackLayout;
if (minHeight != null &&
maxHeight != null &&
minWidth != null &&
maxWidth != null) {
dropStackLayout = false;
final canBeWrappedHorizontal = safeWidth is! WrapDivSize;
final canBeWrappedVertical = safeHeight is! WrapDivSize;
final isFlexibleOnVerticalAxis =
safeContentAlignment is FlexContentAlignment &&
safeContentAlignment.direction == Axis.vertical;
final isFlexibleOnHorizontalAxis =
safeContentAlignment is FlexContentAlignment &&
safeContentAlignment.direction == Axis.horizontal;
if (canBeWrappedHorizontal && canBeWrappedVertical) {
wrapper = _VerticalWrapper(
sizeOnCrossAxis: safeWidth,
contentAlignment: safeContentAlignment,
alignment: safeAlignment.vertical,
child: _HorizontalWrapper(
sizeOnCrossAxis: safeHeight,
contentAlignment: safeContentAlignment,
alignment: safeAlignment.horizontal,
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: minHeight,
maxWidth: maxWidth,
maxHeight: maxHeight,
minWidth: minWidth,
),
child: child,
),
),
);
} else if (canBeWrappedHorizontal || isFlexibleOnHorizontalAxis) {
wrapper = _VerticalWrapper(
sizeOnCrossAxis: safeWidth,
contentAlignment: null,
alignment: safeAlignment.vertical,
child: _HorizontalWrapper(
sizeOnCrossAxis: safeHeight,
contentAlignment: safeContentAlignment,
alignment: safeAlignment.horizontal,
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: minHeight,
maxWidth: maxWidth,
maxHeight: maxHeight,
minWidth: minWidth,
),
child: child,
),
),
);
} else if (canBeWrappedVertical || isFlexibleOnVerticalAxis) {
wrapper = _VerticalWrapper(
sizeOnCrossAxis: safeWidth,
contentAlignment: safeContentAlignment,
alignment: safeAlignment.vertical,
child: _HorizontalWrapper(
sizeOnCrossAxis: safeHeight,
contentAlignment: null,
alignment: safeAlignment.horizontal,
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: minHeight,
maxWidth: maxWidth,
maxHeight: maxHeight,
minWidth: minWidth,
),
child: child,
),
),
);
} else {
wrapper = ConstrainedBox(
constraints: BoxConstraints(
minHeight: minHeight,
maxWidth: maxWidth,
maxHeight: maxHeight,
minWidth: minWidth,
),
child: child,
);
}
} else if (minHeight != null && maxHeight != null) {
final Widget localWrapper;
if (safeWidth is FlexDivSize) {
localWrapper = _HorizontalWrapper(
sizeOnCrossAxis: safeHeight,
contentAlignment: isFlex ? safeContentAlignment : null,
alignment: safeAlignment.horizontal,
child: Expanded(
flex: safeWidth.weight ?? 1,
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: minHeight,
maxHeight: maxHeight,
),
child: child,
),
),
);
} else {
localWrapper = ConstrainedBox(
constraints: BoxConstraints(
minHeight: minHeight,
maxHeight: maxHeight,
),
child: child,
);
}
if (safeAlignment.vertical != null) {
dropStackLayout = true;
} else {
dropStackLayout = false;
}
wrapper = _VerticalWrapper(
sizeOnCrossAxis: safeWidth,
contentAlignment: isFlex ? safeContentAlignment : null,
alignment: safeAlignment.vertical,
child: localWrapper,
);
} else if (minWidth != null && maxWidth != null) {
final Widget localWrapper;
if (safeHeight is FlexDivSize) {
localWrapper = _VerticalWrapper(
sizeOnCrossAxis: safeWidth,
contentAlignment: isFlex ? safeContentAlignment : null,
alignment: safeAlignment.vertical,
child: Expanded(
flex: safeHeight.weight ?? 1,
child: ConstrainedBox(
constraints: BoxConstraints(
maxWidth: maxWidth,
minWidth: minWidth,
),
child: child,
),
),
);
} else {
localWrapper = ConstrainedBox(
constraints: BoxConstraints(
maxWidth: maxWidth,
minWidth: minWidth,
),
child: child,
);
}
if (safeAlignment.horizontal != null) {
dropStackLayout = true;
} else {
dropStackLayout = false;
}
wrapper = _HorizontalWrapper(
sizeOnCrossAxis: safeHeight,
contentAlignment: isFlex ? safeContentAlignment : null,
alignment: safeAlignment.horizontal,
child: localWrapper,
);
} else if (safeHeight is FlexDivSize &&
safeWidth is FlexDivSize &&
(isFlex || isStack)) {
dropStackLayout = false;
if (isFlex) {
wrapper = Expanded(
flex: safeHeight.weight ?? safeWidth.weight ?? 1,
child: SizedBox(
height: double.infinity,
width: double.infinity,
child: child,
),
);
} else {
wrapper = SizedBox(
height: double.infinity,
width: double.infinity,
child: child,
);
}
} else {
dropStackLayout = false;
wrapper = child;
}
if (isStack) {
if (dropStackLayout) {
return Positioned.fill(
child: Align(
alignment: safeAlignment.asAlignment ?? Alignment.center,
child: wrapper,
),
);
}
final alignment = safeAlignment.asAlignment;
return Positioned.fill(
left: safeAlignment.horizontal == DivAxisAlignment.start ? 0 : null,
right: safeAlignment.horizontal == DivAxisAlignment.end ? 0 : null,
top: safeAlignment.vertical == DivAxisAlignment.start ? 0 : null,
bottom: safeAlignment.vertical == DivAxisAlignment.end ? 0 : null,
child: alignment != null
? Align(
alignment: alignment,
child: wrapper,
)
: wrapper,
);
}
return provide(
DivParentData.none,
child: wrapper,
);
}
}
+1 -1
View File
@@ -2,7 +2,7 @@ name: divkit
description: Open source SDUI framework for server-driven app updates, rapid UI prototyping, and cross-platform layouts.
homepage: https://github.com/divkit/divkit
repository: https://github.com/divkit/divkit
version: 0.3.1-rc.1
version: 0.4.0-rc.1
environment:
sdk: '>=2.17.0 <4.0.0'
Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Some files were not shown because too many files have changed in this diff Show More