Skip to content

Commit fcc712f

Browse files
View.can_pop and View.on_confirm_pop (#5284)
* View.can_pop and View.on_confirm_pop * Dismissible and View: added 5 minutes timeout for confirm callbacks
1 parent c3d550a commit fcc712f

File tree

7 files changed

+669
-593
lines changed

7 files changed

+669
-593
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,5 @@
2525
.DS_Store
2626
*.bkp
2727
.python-version
28-
vendor/
28+
vendor/
29+
/client/android/app/.cxx

client/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
ignoresPersistentStateOnLaunch = "NO"
4949
debugDocumentVersioning = "YES"
5050
debugServiceExtension = "internal"
51+
enableGPUValidationMode = "1"
5152
allowLocationSimulation = "YES">
5253
<BuildableProductRunnable
5354
runnableDebuggingMode = "0">

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ class _DismissibleControlState extends State<DismissibleControl> {
5656
parseDismissThresholds(widget.control, "dismissThresholds");
5757

5858
DismissDirection direction = parseDismissDirection(
59-
widget.control.attrString("dismissDirection"), DismissDirection.horizontal)!;
59+
widget.control.attrString("dismissDirection"),
60+
DismissDirection.horizontal)!;
6061

6162
widget.backend.subscribeMethods(widget.control.id,
6263
(methodName, args) async {
@@ -118,7 +119,10 @@ class _DismissibleControlState extends State<DismissibleControl> {
118119
widget.control.state["confirm_dismiss"] = completer;
119120
widget.backend.triggerControlEvent(
120121
widget.control.id, "confirm_dismiss", direction.name);
121-
return completer.future;
122+
return completer.future.timeout(
123+
const Duration(minutes: 5),
124+
onTimeout: () => false,
125+
);
122126
}
123127
: null,
124128
movementDuration: Duration(

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

Lines changed: 55 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import 'dart:async';
12
import 'dart:convert';
23

34
import 'package:collection/collection.dart';
@@ -768,6 +769,7 @@ class _PageControlState extends State<PageControl> with FletStoreMixin {
768769
widgetsDesign: _widgetsDesign,
769770
brightness: _brightness,
770771
themeMode: _themeMode,
772+
isRootView: view.id == routesView.views.first.id,
771773
);
772774

773775
//debugPrint("ROUTES: $_prevViewRoutes $viewRoutes");
@@ -792,10 +794,11 @@ class _PageControlState extends State<PageControl> with FletStoreMixin {
792794
Widget nextChild = Navigator(
793795
key: navigatorKey,
794796
pages: pages,
795-
onPopPage: (route, dynamic result) {
796-
widget.backend.triggerControlEvent("page", "view_pop",
797-
((route.settings as Page).key as ValueKey).value);
798-
return false;
797+
onDidRemovePage: (page) {
798+
if (page.key != null) {
799+
widget.backend.triggerControlEvent(
800+
"page", "view_pop", (page.key as ValueKey).value);
801+
}
799802
});
800803

801804
// wrap navigator into non-visual offstage controls
@@ -821,9 +824,10 @@ class ViewControl extends StatefulWidget {
821824
final PageDesign widgetsDesign;
822825
final Brightness? brightness;
823826
final ThemeMode? themeMode;
827+
final bool isRootView;
824828

825-
const ViewControl(
826-
{super.key,
829+
ViewControl(
830+
{Key? key,
827831
required this.parent,
828832
required this.viewId,
829833
required this.overlayWidgets,
@@ -832,14 +836,17 @@ class ViewControl extends StatefulWidget {
832836
required this.parentAdaptive,
833837
required this.widgetsDesign,
834838
required this.brightness,
835-
required this.themeMode});
839+
required this.themeMode,
840+
required this.isRootView})
841+
: super(key: ValueKey("control_$viewId"));
836842

837843
@override
838844
State<ViewControl> createState() => _ViewControlState();
839845
}
840846

841847
class _ViewControlState extends State<ViewControl> with FletStoreMixin {
842848
final scaffoldKey = GlobalKey<ScaffoldState>();
849+
Completer<bool>? _popCompleter;
843850

844851
@override
845852
Widget build(BuildContext context) {
@@ -875,6 +882,16 @@ class _ViewControlState extends State<ViewControl> with FletStoreMixin {
875882
CrossAxisAlignment.start)!;
876883
final fabLocation = parseFloatingActionButtonLocation(
877884
control, "floatingActionButtonLocation");
885+
final canPop = control.attrBool("canPop", true)!;
886+
887+
widget.backend.subscribeMethods(control.id, (methodName, args) async {
888+
debugPrint("View.onMethod(${control.id})");
889+
if (methodName == "confirm_pop") {
890+
_popCompleter?.complete(bool.tryParse(args["shouldPop"] ?? ""));
891+
widget.backend.unsubscribeMethods(control.id);
892+
}
893+
return null;
894+
});
878895

879896
Control? appBar;
880897
Control? cupertinoAppBar;
@@ -907,7 +924,8 @@ class _ViewControlState extends State<ViewControl> with FletStoreMixin {
907924
ctrl.name == "drawer_start") {
908925
drawer = ctrl;
909926
continue;
910-
} else if (ctrl.type == "navigationdrawer" && ctrl.name == "drawer_end") {
927+
} else if (ctrl.type == "navigationdrawer" &&
928+
ctrl.name == "drawer_end") {
911929
endDrawer = ctrl;
912930
continue;
913931
}
@@ -1153,13 +1171,41 @@ class _ViewControlState extends State<ViewControl> with FletStoreMixin {
11531171
child: scaffold,
11541172
);
11551173
}
1156-
var result = Directionality(
1174+
Widget result = Directionality(
11571175
textDirection: textDirection,
11581176
child: widget.loadingPage != null
11591177
? Stack(
11601178
children: [scaffold, widget.loadingPage!],
11611179
)
11621180
: scaffold);
1181+
1182+
result = PopScope(
1183+
canPop: canPop,
1184+
onPopInvokedWithResult: (didPop, result) {
1185+
if (didPop || !control.attrBool("onConfirmPop", false)!) {
1186+
return;
1187+
}
1188+
debugPrint("Page.onPopInvokedWithResult()");
1189+
_popCompleter = Completer<bool>();
1190+
widget.backend
1191+
.triggerControlEvent(widget.viewId, "confirm_pop");
1192+
_popCompleter!.future
1193+
.timeout(
1194+
const Duration(minutes: 5),
1195+
onTimeout: () => false,
1196+
)
1197+
.then((shouldPop) {
1198+
if (context.mounted && shouldPop) {
1199+
if (widget.isRootView) {
1200+
SystemNavigator.pop();
1201+
} else {
1202+
Navigator.pop(context);
1203+
}
1204+
}
1205+
});
1206+
},
1207+
child: result);
1208+
11631209
return withPageArgs((context, pageArgs) {
11641210
var backgroundDecoration = parseBoxDecoration(
11651211
Theme.of(context), control, "decoration", pageArgs);

packages/flet/lib/src/utils/platform.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,16 @@ bool isMobilePlatform() {
1515
defaultTargetPlatform == TargetPlatform.android);
1616
}
1717

18+
/// Checks if the current platform is iOS
19+
bool isiOSPlatform() {
20+
return !kIsWeb && defaultTargetPlatform == TargetPlatform.iOS;
21+
}
22+
23+
/// Checks if the current platform is Android
24+
bool isAndroidPlatform() {
25+
return !kIsWeb && defaultTargetPlatform == TargetPlatform.android;
26+
}
27+
1828
/// Checks if the current platform is Windows desktop.
1929
bool isWindowsDesktop() {
2030
return !kIsWeb && (defaultTargetPlatform == TargetPlatform.windows);

sdk/python/packages/flet/src/flet/core/view.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
FloatingActionButtonLocation,
1919
MainAxisAlignment,
2020
OffsetValue,
21+
OptionalControlEventCallable,
2122
OptionalEventCallable,
2223
PaddingValue,
2324
ScrollMode,
@@ -55,6 +56,8 @@ def __init__(
5556
bgcolor: Optional[ColorValue] = None,
5657
decoration: Optional[BoxDecoration] = None,
5758
foreground_decoration: Optional[BoxDecoration] = None,
59+
can_pop: Optional[bool] = None,
60+
on_confirm_pop: OptionalControlEventCallable = None,
5861
#
5962
# ScrollableControl
6063
#
@@ -99,6 +102,8 @@ def __init__(
99102
self.fullscreen_dialog = fullscreen_dialog
100103
self.decoration = decoration
101104
self.foreground_decoration = foreground_decoration
105+
self.can_pop = can_pop
106+
self.on_confirm_pop = on_confirm_pop
102107

103108
def _get_control_name(self):
104109
return "view"
@@ -134,6 +139,9 @@ def _get_children(self):
134139
children.append(self.__end_drawer)
135140
return children + self.__controls
136141

142+
def confirm_pop(self, shouldPop: bool):
143+
self.invoke_method("confirm_pop", {"shouldPop": str(shouldPop).lower()})
144+
137145
# route
138146
@property
139147
def route(self):
@@ -303,6 +311,25 @@ def decoration(self) -> Optional[BoxDecoration]:
303311
def decoration(self, value: Optional[BoxDecoration]):
304312
self.__decoration = value
305313

314+
# can_pop
315+
@property
316+
def can_pop(self) -> Optional[bool]:
317+
return self._get_attr("canPop", data_type="bool")
318+
319+
@can_pop.setter
320+
def can_pop(self, value: Optional[bool]):
321+
self._set_attr("canPop", value)
322+
323+
# on_confirm_pop
324+
@property
325+
def on_confirm_pop(self):
326+
return self._get_event_handler("confirm_pop")
327+
328+
@on_confirm_pop.setter
329+
def on_confirm_pop(self, handler: OptionalControlEventCallable):
330+
self._add_event_handler("confirm_pop", handler)
331+
self._set_attr("onConfirmPop", True if handler is not None else None)
332+
306333
# Magic methods
307334
def __contains__(self, item: Control) -> bool:
308335
return item in self.__controls

0 commit comments

Comments
 (0)