Skip to content

Commit a1a425e

Browse files
authored
Merge branch 'flet-dev:main' into add-pep561-pytyped
2 parents 11ef2d0 + 77a1958 commit a1a425e

File tree

528 files changed

+1384
-619
lines changed

Some content is hidden

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

528 files changed

+1384
-619
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
* Controls in Python are now defined as plain dataclasses
1414
* Unified diffing algorithm supports both imperative and declarative styles
1515
* Refactored Flutter layer using inherited widgets and `Provider`
16+
* Added a Shimmer control for building skeleton loaders and animated placeholders.
1617
* Added `FletApp.appErrorMessage` template to customize loading screen errors.
1718
* See the list of [breaking changes](https://github.com/flet-dev/flet/issues/5238)
1819

client/pubspec.lock

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1206,6 +1206,14 @@ packages:
12061206
url: "https://pub.dev"
12071207
source: hosted
12081208
version: "2.4.1"
1209+
shimmer:
1210+
dependency: transitive
1211+
description:
1212+
name: shimmer
1213+
sha256: "5f88c883a22e9f9f299e5ba0e4f7e6054857224976a5d9f839d4ebdc94a14ac9"
1214+
url: "https://pub.dev"
1215+
source: hosted
1216+
version: "3.0.0"
12091217
sky_engine:
12101218
dependency: transitive
12111219
description: flutter

packages/flet/lib/src/controls/container.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ class ContainerControl extends StatelessWidget with FletStoreMixin {
5050
Widget? container;
5151

5252
var onAnimationEnd = control.getBool("on_animation_end", false)!
53-
? () => control.triggerEvent("animation_end" "container")
53+
? () => control.triggerEvent("animation_end", "container")
5454
: null;
5555
if ((onClick || url != null || onLongPress || onHover || onTapDown) &&
5656
ink &&

packages/flet/lib/src/controls/dropdown.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,7 @@ class _DropdownControlState extends State<DropdownControl> {
280280
helperText: widget.control.getString("helper_text"),
281281
menuStyle: menuStyle,
282282
inputDecorationTheme: inputDecorationTheme,
283+
inputFormatters: inputFormatters.isEmpty ? null : inputFormatters,
283284
onSelected: widget.control.disabled
284285
? null
285286
: (String? selection) {
@@ -291,7 +292,6 @@ class _DropdownControlState extends State<DropdownControl> {
291292
);
292293

293294
var didAutoFocus = false;
294-
295295
if (!didAutoFocus && autofocus) {
296296
didAutoFocus = true;
297297
SchedulerBinding.instance.addPostFrameCallback((_) {

packages/flet/lib/src/controls/page.dart

Lines changed: 76 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import '../models/page_design.dart';
2020
import '../routing/route_parser.dart';
2121
import '../routing/route_state.dart';
2222
import '../routing/router_delegate.dart';
23+
import '../services/service_binding.dart';
2324
import '../services/service_registry.dart';
2425
import '../utils/device_info.dart';
2526
import '../utils/locale.dart';
@@ -57,6 +58,9 @@ class _PageControlState extends State<PageControl> with WidgetsBindingObserver {
5758
late final AppLifecycleListener _appLifecycleListener;
5859
ServiceRegistry? _pageServices;
5960
ServiceRegistry? _userServices;
61+
String? _userServicesUid;
62+
ServiceBinding? _windowService;
63+
Control? _windowControl;
6064
bool? _prevOnKeyboardEvent;
6165
bool _keyboardHandlerSubscribed = false;
6266
String? _prevViewRoutes;
@@ -95,12 +99,15 @@ class _PageControlState extends State<PageControl> with WidgetsBindingObserver {
9599

96100
_attachKeyboardListenerIfNeeded();
97101
widget.control.addInvokeMethodListener(_invokeMethod);
102+
widget.control.addListener(_onPageControlChanged);
103+
_ensureServiceRegistries();
98104
}
99105

100106
@override
101107
void didChangeDependencies() {
102108
debugPrint("Page.didChangeDependencies: ${widget.control.id}");
103109
super.didChangeDependencies();
110+
_ensureServiceRegistries();
104111
_loadFontsIfNeeded(FletBackend.of(context));
105112
}
106113

@@ -109,26 +116,7 @@ class _PageControlState extends State<PageControl> with WidgetsBindingObserver {
109116
debugPrint("Page.didUpdateWidget: ${widget.control.id}");
110117
super.didUpdateWidget(oldWidget);
111118
_updateMultiViews();
112-
113-
// page services
114-
_pageServices ??= ServiceRegistry(
115-
control: widget.control,
116-
propertyName: "_services",
117-
backend: FletBackend.of(context));
118-
119-
// user services
120-
var userServicesControl = widget.control.child("_user_services");
121-
if (userServicesControl != null) {
122-
if (_userServices == null ||
123-
_userServices?.control.internals?["uid"] !=
124-
userServicesControl.internals?["uid"]) {
125-
_userServices = ServiceRegistry(
126-
control: userServicesControl,
127-
propertyName: "_services",
128-
backend: FletBackend.of(context));
129-
}
130-
}
131-
119+
_ensureServiceRegistries();
132120
_attachKeyboardListenerIfNeeded();
133121
_loadFontsIfNeeded(FletBackend.of(context));
134122
}
@@ -148,9 +136,60 @@ class _PageControlState extends State<PageControl> with WidgetsBindingObserver {
148136
HardwareKeyboard.instance.removeHandler(_handleKeyDown);
149137
}
150138
widget.control.removeInvokeMethodListener(_invokeMethod);
139+
widget.control.removeListener(_onPageControlChanged);
140+
_pageServices?.dispose();
141+
_userServices?.dispose();
142+
_windowService?.dispose();
151143
super.dispose();
152144
}
153145

146+
void _onPageControlChanged() {
147+
_ensureServiceRegistries();
148+
}
149+
150+
void _ensureServiceRegistries() {
151+
if (!mounted) {
152+
return;
153+
}
154+
var backend = FletBackend.of(context);
155+
156+
_pageServices ??= ServiceRegistry(
157+
control: widget.control,
158+
propertyName: "_services",
159+
backend: backend);
160+
161+
var userServicesControl = widget.control.child("_user_services");
162+
if (userServicesControl != null) {
163+
var uid = userServicesControl.internals?["uid"];
164+
if (_userServices == null || _userServicesUid != uid) {
165+
_userServices?.dispose();
166+
_userServices = ServiceRegistry(
167+
control: userServicesControl,
168+
propertyName: "_services",
169+
backend: backend);
170+
_userServicesUid = uid;
171+
}
172+
} else if (_userServices != null) {
173+
_userServices?.dispose();
174+
_userServices = null;
175+
_userServicesUid = null;
176+
}
177+
178+
var windowControl = widget.control.child("window", visibleOnly: false);
179+
if (windowControl != null) {
180+
if (!identical(windowControl, _windowControl)) {
181+
_windowService?.dispose();
182+
_windowService =
183+
ServiceBinding(control: windowControl, backend: backend);
184+
_windowControl = windowControl;
185+
}
186+
} else if (_windowService != null) {
187+
_windowService?.dispose();
188+
_windowService = null;
189+
_windowControl = null;
190+
}
191+
}
192+
154193
Future<dynamic> _invokeMethod(String name, dynamic args) async {
155194
debugPrint("Page.$name($args)");
156195

@@ -341,8 +380,7 @@ class _PageControlState extends State<PageControl> with WidgetsBindingObserver {
341380
var appStatus = context
342381
.select<FletBackend, ({bool isLoading, String error})>((backend) =>
343382
(isLoading: backend.isLoading, error: backend.error));
344-
var appStartupScreenMessage =
345-
backend.appStartupScreenMessage ?? "";
383+
var appStartupScreenMessage = backend.appStartupScreenMessage ?? "";
346384
var formattedErrorMessage =
347385
backend.formatAppErrorMessage(appStatus.error);
348386

@@ -432,6 +470,18 @@ class _PageControlState extends State<PageControl> with WidgetsBindingObserver {
432470
? control.getCupertinoTheme("dark_theme", context, Brightness.dark)
433471
: control.getCupertinoTheme("theme", context, Brightness.dark);
434472

473+
var materialTheme = themeMode == ThemeMode.dark ||
474+
((themeMode == null || themeMode == ThemeMode.system) &&
475+
brightness == Brightness.dark)
476+
? darkTheme
477+
: lightTheme;
478+
479+
Widget scaffoldMessengerBuilder(BuildContext context, Widget? child) {
480+
return Theme(
481+
data: materialTheme ?? lightTheme ?? ThemeData(),
482+
child: ScaffoldMessenger(child: child ?? const SizedBox.shrink()));
483+
}
484+
435485
var showSemanticsDebugger =
436486
control.getBool("show_semantics_debugger", false)!;
437487

@@ -442,6 +492,7 @@ class _PageControlState extends State<PageControl> with WidgetsBindingObserver {
442492
showSemanticsDebugger: showSemanticsDebugger,
443493
title: windowTitle,
444494
theme: cupertinoTheme,
495+
builder: scaffoldMessengerBuilder,
445496
supportedLocales: localeConfiguration.supportedLocales,
446497
locale: localeConfiguration.locale,
447498
localizationsDelegates: localizationsDelegates,
@@ -454,6 +505,7 @@ class _PageControlState extends State<PageControl> with WidgetsBindingObserver {
454505
routeInformationParser: _routeParser,
455506
title: windowTitle,
456507
theme: cupertinoTheme,
508+
builder: scaffoldMessengerBuilder,
457509
localizationsDelegates: localizationsDelegates,
458510
supportedLocales: localeConfiguration.supportedLocales,
459511
locale: localeConfiguration.locale,
@@ -503,14 +555,12 @@ class _PageControlState extends State<PageControl> with WidgetsBindingObserver {
503555

504556
var backend = FletBackend.of(context);
505557
var showAppStartupScreen = backend.showAppStartupScreen ?? false;
506-
var appStartupScreenMessage =
507-
backend.appStartupScreenMessage ?? "";
558+
var appStartupScreenMessage = backend.appStartupScreenMessage ?? "";
508559

509560
var appStatus =
510561
context.select<FletBackend, ({bool isLoading, String error})>(
511562
(backend) => (isLoading: backend.isLoading, error: backend.error));
512-
var formattedErrorMessage =
513-
backend.formatAppErrorMessage(appStatus.error);
563+
var formattedErrorMessage = backend.formatAppErrorMessage(appStatus.error);
514564

515565
var views = widget.control.children("views");
516566
List<Page<dynamic>> pages = [];

packages/flet/lib/src/controls/semantics.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class SemanticsControl extends StatelessWidget {
3131
hint: control.getString("hint"),
3232
onTapHint: control.getString("on_tap_hint"),
3333
onLongPressHint: control.getString("on_long_press_hint"),
34-
container: control.getBool("container")!,
34+
container: control.getBool("container", false)!,
3535
liveRegion: control.getBool("live_region"),
3636
obscured: control.getBool("obscured"),
3737
multiline: control.getBool("multiline"),
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import 'package:collection/collection.dart';
2+
import 'package:flutter/material.dart';
3+
import 'package:shimmer/shimmer.dart';
4+
5+
import '../extensions/control.dart';
6+
import '../models/control.dart';
7+
import '../utils/colors.dart';
8+
import '../utils/gradient.dart';
9+
import '../utils/numbers.dart';
10+
import '../utils/time.dart';
11+
import '../widgets/error.dart';
12+
import 'base_controls.dart';
13+
14+
class ShimmerControl extends StatelessWidget {
15+
final Control control;
16+
17+
const ShimmerControl({super.key, required this.control});
18+
19+
@override
20+
Widget build(BuildContext context) {
21+
debugPrint("Shimmer build: ${control.id}");
22+
23+
final content = control.buildWidget("content");
24+
if (content == null) {
25+
return const ErrorControl("Shimmer.content must be specified");
26+
}
27+
28+
final gradient = control.getGradient("gradient", Theme.of(context));
29+
final baseColor = control.getColor("base_color", context);
30+
final highlightColor = control.getColor("highlight_color", context);
31+
32+
if (gradient == null && (baseColor == null || highlightColor == null)) {
33+
return const ErrorControl(
34+
"Shimmer requires either gradient or base/highlight colors");
35+
}
36+
37+
final direction = _parseDirection(control.getString("direction"));
38+
final period =
39+
control.getDuration("period", const Duration(milliseconds: 1500))!;
40+
final loop = control.getInt("loop", 0)!;
41+
42+
final shimmerWidget = gradient != null
43+
? Shimmer(
44+
gradient: gradient,
45+
direction: direction,
46+
period: period,
47+
loop: loop,
48+
enabled: !control.disabled,
49+
child: content,
50+
)
51+
: Shimmer.fromColors(
52+
baseColor: baseColor!,
53+
highlightColor: highlightColor!,
54+
direction: direction,
55+
period: period,
56+
loop: loop,
57+
enabled: !control.disabled,
58+
child: content,
59+
);
60+
61+
return LayoutControl(control: control, child: shimmerWidget);
62+
}
63+
}
64+
65+
ShimmerDirection _parseDirection(String? value,
66+
[ShimmerDirection defaultValue = ShimmerDirection.ltr]) {
67+
if (value == null) {
68+
return defaultValue;
69+
}
70+
return ShimmerDirection.values.firstWhereOrNull(
71+
(dir) => dir.name.toLowerCase() == value.toLowerCase()) ??
72+
defaultValue;
73+
}

packages/flet/lib/src/controls/view.dart

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -168,10 +168,6 @@ class _ViewControlState extends State<ViewControl> {
168168
overlayWidgets.add(PageMedia(view: widget.control.parent));
169169
}
170170

171-
var windowControl = control.parent?.get("window");
172-
if (windowControl != null && isRootView && isDesktopPlatform()) {
173-
overlayWidgets.add(ControlWidget(control: windowControl));
174-
}
175171
}
176172

177173
Widget body = Stack(children: [

packages/flet/lib/src/flet_backend.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ class FletBackend extends ChangeNotifier {
192192
}
193193

194194
_registerClient() {
195+
debugPrint("Registering web client: $page");
195196
_send(
196197
Message(
197198
action: MessageAction.registerClient,
@@ -211,7 +212,7 @@ class FletBackend extends ChangeNotifier {
211212
"width": page.get("width"),
212213
"height": page.get("height"),
213214
"platform": page.get("platform"),
214-
"window": page.child("window")!.toMap(),
215+
"window": page.child("window", visibleOnly: false)!.toMap(),
215216
"media": page.get("media"),
216217
}).toMap()),
217218
unbuffered: true);
@@ -333,7 +334,7 @@ class FletBackend extends ChangeNotifier {
333334
if (isDesktopPlatform()) {
334335
var windowState = await getWindowState();
335336
debugPrint("Window state updated: $windowState");
336-
var window = page.child("window")!;
337+
var window = page.child("window", visibleOnly: false)!;
337338
updateControl(window.id, windowState.toMap());
338339
triggerControlEvent(window, "event", {"type": "resized"});
339340
}
@@ -470,8 +471,7 @@ class FletBackend extends ChangeNotifier {
470471
var template = appErrorMessage ?? defaultAppErrorMessageTemplate;
471472
final lines = const LineSplitter().convert(rawError);
472473
final message = lines.isNotEmpty ? lines.first : "";
473-
final details =
474-
lines.length > 1 ? lines.sublist(1).join("\n") : "";
474+
final details = lines.length > 1 ? lines.sublist(1).join("\n") : "";
475475
template = template.replaceAll("{message}", message);
476476
if (details.isEmpty) {
477477
template = template.replaceAll(RegExp(r'(\r?\n)*\{details\}'), "");

0 commit comments

Comments
 (0)