Skip to content

Commit 905b4ff

Browse files
Implicit animations and AnimatedSwitcher control (#162)
* Add new props on Python side * All implicit animations * create_control changes * animate_offset, offset added to all controls * Offset animation added * AnimatedSwitcher * .on_hover for all buttons (except IconButton) * Cleanup, fix animations, image inside container * Added TextThemeStyle * SnackBar bgcolor and elevation
1 parent f4967b7 commit 905b4ff

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+1837
-177
lines changed
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import 'package:flet_view/controls/error.dart';
2+
import 'package:flet_view/utils/animations.dart';
3+
import 'package:flet_view/utils/gradient.dart';
4+
import 'package:flutter/material.dart';
5+
6+
import '../models/control.dart';
7+
import '../utils/borders.dart';
8+
import 'create_control.dart';
9+
10+
class AnimatedSwitcherControl extends StatelessWidget {
11+
final Control? parent;
12+
final Control control;
13+
final List<Control> children;
14+
final bool parentDisabled;
15+
16+
const AnimatedSwitcherControl(
17+
{Key? key,
18+
this.parent,
19+
required this.control,
20+
required this.children,
21+
required this.parentDisabled})
22+
: super(key: key);
23+
24+
@override
25+
Widget build(BuildContext context) {
26+
debugPrint("AnimatedSwitcher build: ${control.id}");
27+
28+
var contentCtrls =
29+
children.where((c) => c.name == "content" && c.isVisible);
30+
31+
var switchInCurve = parseCurve(control.attrString("switchInCurve", "")!);
32+
var switchOutCurve = parseCurve(control.attrString("switchOutCurve", "")!);
33+
var duration = control.attrInt("duration", 1000)!;
34+
var reverseDuration = control.attrInt("reverseDuration", 1000)!;
35+
bool disabled = control.isDisabled || parentDisabled;
36+
37+
if (contentCtrls.isEmpty) {
38+
return const ErrorControl("Content is not set.");
39+
}
40+
41+
var child = createControl(control, contentCtrls.first.id, disabled);
42+
43+
return constrainedControl(
44+
AnimatedSwitcher(
45+
duration: Duration(milliseconds: duration),
46+
reverseDuration: Duration(milliseconds: reverseDuration),
47+
switchInCurve: switchInCurve,
48+
switchOutCurve: switchOutCurve,
49+
transitionBuilder: (child, animation) {
50+
switch (control.attrString("transition", "")!.toLowerCase()) {
51+
case "rotation":
52+
return RotationTransition(turns: animation, child: child);
53+
case "scale":
54+
return ScaleTransition(scale: animation, child: child);
55+
default:
56+
return FadeTransition(opacity: animation, child: child);
57+
}
58+
},
59+
child: child),
60+
parent,
61+
control);
62+
}
63+
}

client/lib/controls/container.dart

Lines changed: 141 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
1+
import 'dart:convert';
2+
import 'dart:typed_data';
3+
4+
import 'package:flet_view/utils/animations.dart';
15
import 'package:flutter/material.dart';
6+
import 'package:flutter_redux/flutter_redux.dart';
27

8+
import '../models/app_state.dart';
39
import '../models/control.dart';
410
import '../utils/alignment.dart';
511
import '../utils/borders.dart';
612
import '../utils/colors.dart';
713
import '../utils/edge_insets.dart';
814
import '../utils/gradient.dart';
15+
import '../utils/images.dart';
16+
import '../utils/uri.dart';
917
import '../web_socket_client.dart';
1018
import 'create_control.dart';
19+
import 'error.dart';
1120

1221
class ContainerControl extends StatelessWidget {
1322
final Control? parent;
@@ -37,23 +46,58 @@ class ContainerControl extends StatelessWidget {
3746
bool onHover = control.attrBool("onHover", false)!;
3847
bool disabled = control.isDisabled || parentDisabled;
3948

40-
var boxDecor = BoxDecoration(
41-
color: bgColor,
42-
gradient: parseGradient(Theme.of(context), control, "gradient"),
43-
border: parseBorder(Theme.of(context), control, "border"),
44-
borderRadius: parseBorderRadius(control, "borderRadius"));
49+
var imageSrc = control.attrString("imageSrc", "")!;
50+
var imageSrcBase64 = control.attrString("imageSrcBase64", "")!;
51+
var imageRepeat = parseImageRepeat(control, "imageRepeat");
52+
var imageFit = parseBoxFit(control, "imageFit");
53+
var imageOpacity = control.attrDouble("imageOpacity", 1)!;
4554

4655
Widget? child = contentCtrls.isNotEmpty
4756
? createControl(control, contentCtrls.first.id, disabled)
4857
: null;
4958

50-
if ((onClick || onLongPress || onHover) && ink) {
51-
return constrainedControl(
52-
Container(
53-
margin: parseEdgeInsets(control, "margin"),
54-
child: Ink(
59+
var animation = parseAnimation(control, "animate");
60+
61+
return StoreConnector<AppState, Uri?>(
62+
distinct: true,
63+
converter: (store) => store.state.pageUri,
64+
builder: (context, pageUri) {
65+
DecorationImage? image;
66+
67+
if (imageSrcBase64 != "") {
68+
try {
69+
Uint8List bytes = base64Decode(imageSrcBase64);
70+
image = DecorationImage(
71+
image: MemoryImage(bytes),
72+
repeat: imageRepeat,
73+
fit: imageFit,
74+
opacity: imageOpacity);
75+
} catch (ex) {
76+
return ErrorControl("Error decoding base64: ${ex.toString()}");
77+
}
78+
} else if (imageSrc != "") {
79+
var uri = Uri.parse(imageSrc);
80+
image = DecorationImage(
81+
image: NetworkImage(uri.hasAuthority
82+
? imageSrc
83+
: getAssetUri(pageUri!, imageSrc).toString()),
84+
repeat: imageRepeat,
85+
fit: imageFit,
86+
opacity: imageOpacity);
87+
}
88+
89+
var boxDecor = BoxDecoration(
90+
color: bgColor,
91+
gradient: parseGradient(Theme.of(context), control, "gradient"),
92+
image: image,
93+
backgroundBlendMode: BlendMode.modulate,
94+
border: parseBorder(Theme.of(context), control, "border"),
95+
borderRadius: parseBorderRadius(control, "borderRadius"));
96+
97+
if ((onClick || onLongPress || onHover) && ink) {
98+
var ink = Ink(
5599
child: InkWell(
56-
onTap: onClick || onHover
100+
onTap: !disabled && (onClick || onHover)
57101
? () {
58102
debugPrint("Container ${control.id} clicked!");
59103
ws.pageEventFromWeb(
@@ -62,7 +106,7 @@ class ContainerControl extends StatelessWidget {
62106
eventData: control.attrs["data"] ?? "");
63107
}
64108
: null,
65-
onLongPress: onLongPress || onHover
109+
onLongPress: !disabled && (onLongPress || onHover)
66110
? () {
67111
debugPrint("Container ${control.id} long pressed!");
68112
ws.pageEventFromWeb(
@@ -71,7 +115,7 @@ class ContainerControl extends StatelessWidget {
71115
eventData: control.attrs["data"] ?? "");
72116
}
73117
: null,
74-
onHover: onHover
118+
onHover: !disabled && onHover
75119
? (value) {
76120
debugPrint("Container ${control.id} hovered!");
77121
ws.pageEventFromWeb(
@@ -86,64 +130,90 @@ class ContainerControl extends StatelessWidget {
86130
alignment: parseAlignment(control, "alignment"),
87131
),
88132
borderRadius: parseBorderRadius(control, "borderRadius")),
89-
decoration: boxDecor),
90-
),
91-
parent,
92-
control);
93-
} else {
94-
Widget container = Container(
95-
padding: parseEdgeInsets(control, "padding"),
96-
margin: parseEdgeInsets(control, "margin"),
97-
alignment: parseAlignment(control, "alignment"),
98-
decoration: boxDecor,
99-
child: child);
133+
decoration: boxDecor);
134+
return constrainedControl(
135+
animation == null
136+
? Container(
137+
margin: parseEdgeInsets(control, "margin"),
138+
child: ink,
139+
)
140+
: AnimatedContainer(
141+
duration: animation.duration,
142+
curve: animation.curve,
143+
margin: parseEdgeInsets(control, "margin"),
144+
child: ink),
145+
parent,
146+
control);
147+
} else {
148+
Widget container = animation == null
149+
? Container(
150+
width: control.attrDouble("width"),
151+
height: control.attrDouble("height"),
152+
padding: parseEdgeInsets(control, "padding"),
153+
margin: parseEdgeInsets(control, "margin"),
154+
alignment: parseAlignment(control, "alignment"),
155+
decoration: boxDecor,
156+
child: child)
157+
: AnimatedContainer(
158+
duration: animation.duration,
159+
curve: animation.curve,
160+
width: control.attrDouble("width"),
161+
height: control.attrDouble("height"),
162+
padding: parseEdgeInsets(control, "padding"),
163+
margin: parseEdgeInsets(control, "margin"),
164+
alignment: parseAlignment(control, "alignment"),
165+
decoration: boxDecor,
166+
child: child);
100167

101-
if (onClick || onLongPress || onHover) {
102-
container = MouseRegion(
103-
cursor: SystemMouseCursors.click,
104-
onEnter: onHover
105-
? (value) {
106-
debugPrint("Container's mouse region ${control.id} entered!");
107-
ws.pageEventFromWeb(
108-
eventTarget: control.id,
109-
eventName: "hover",
110-
eventData: "true");
111-
}
112-
: null,
113-
onExit: onHover
114-
? (value) {
115-
debugPrint("Container's mouse region ${control.id} exited!");
116-
ws.pageEventFromWeb(
117-
eventTarget: control.id,
118-
eventName: "hover",
119-
eventData: "false");
120-
}
121-
: null,
122-
child: GestureDetector(
123-
child: container,
124-
onTapDown: onClick
125-
? (details) {
126-
debugPrint("Container ${control.id} clicked!");
127-
ws.pageEventFromWeb(
128-
eventTarget: control.id,
129-
eventName: "click",
130-
eventData: control.attrString("data", "")! +
131-
"${details.localPosition.dx}:${details.localPosition.dy} ${details.globalPosition.dx}:${details.globalPosition.dy}");
132-
}
133-
: null,
134-
onLongPress: onLongPress
135-
? () {
136-
debugPrint("Container ${control.id} clicked!");
137-
ws.pageEventFromWeb(
138-
eventTarget: control.id,
139-
eventName: "long_press",
140-
eventData: control.attrs["data"] ?? "");
141-
}
142-
: null,
143-
),
144-
);
145-
}
146-
return constrainedControl(container, parent, control);
147-
}
168+
if (onClick || onLongPress || onHover) {
169+
container = MouseRegion(
170+
cursor: SystemMouseCursors.click,
171+
onEnter: !disabled && onHover
172+
? (value) {
173+
debugPrint(
174+
"Container's mouse region ${control.id} entered!");
175+
ws.pageEventFromWeb(
176+
eventTarget: control.id,
177+
eventName: "hover",
178+
eventData: "true");
179+
}
180+
: null,
181+
onExit: !disabled && onHover
182+
? (value) {
183+
debugPrint(
184+
"Container's mouse region ${control.id} exited!");
185+
ws.pageEventFromWeb(
186+
eventTarget: control.id,
187+
eventName: "hover",
188+
eventData: "false");
189+
}
190+
: null,
191+
child: GestureDetector(
192+
child: container,
193+
onTapDown: !disabled && onClick
194+
? (details) {
195+
debugPrint("Container ${control.id} clicked!");
196+
ws.pageEventFromWeb(
197+
eventTarget: control.id,
198+
eventName: "click",
199+
eventData: control.attrString("data", "")! +
200+
"${details.localPosition.dx}:${details.localPosition.dy} ${details.globalPosition.dx}:${details.globalPosition.dy}");
201+
}
202+
: null,
203+
onLongPress: !disabled && onLongPress
204+
? () {
205+
debugPrint("Container ${control.id} clicked!");
206+
ws.pageEventFromWeb(
207+
eventTarget: control.id,
208+
eventName: "long_press",
209+
eventData: control.attrs["data"] ?? "");
210+
}
211+
: null,
212+
),
213+
);
214+
}
215+
return constrainedControl(container, parent, control);
216+
}
217+
});
148218
}
149219
}

0 commit comments

Comments
 (0)