Skip to content

Commit

Permalink
CupertinoActionSheet and CupertinoActionSheetAction controls (#2763)
Browse files Browse the repository at this point in the history
* add CupertinoActionSheetButton

* add CupertinoActionSheet

* rename CupertinoActionSheetButton to CupertinoActionSheetAction

* Converted to `StatelessWidget`s

* Make CupertinoActionSheet behave like BottomSheet

---------

Co-authored-by: Feodor Fitsner <[email protected]>
  • Loading branch information
ndonkoHenri and FeodorFitsner authored Mar 1, 2024
1 parent 60fc766 commit 366770e
Show file tree
Hide file tree
Showing 7 changed files with 490 additions and 4 deletions.
21 changes: 21 additions & 0 deletions packages/flet/lib/src/controls/create_control.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import 'circle_avatar.dart';
import 'clipboard.dart';
import 'column.dart';
import 'container.dart';
import 'cupertino_action_sheet.dart';
import 'cupertino_action_sheet_action.dart';
import 'cupertino_activity_indicator.dart';
import 'cupertino_alert_dialog.dart';
import 'cupertino_button.dart';
Expand Down Expand Up @@ -590,6 +592,25 @@ Widget createWidget(
parentDisabled: parentDisabled,
parentAdaptive: parentAdaptive,
backend: backend);
case "cupertinoactionsheet":
return CupertinoActionSheetControl(
key: key,
parent: parent,
control: controlView.control,
children: controlView.children,
parentDisabled: parentDisabled,
parentAdaptive: parentAdaptive,
nextChild: nextChild,
backend: backend);
case "cupertinoactionsheetaction":
return CupertinoActionSheetActionControl(
key: key,
parent: parent,
control: controlView.control,
children: controlView.children,
parentDisabled: parentDisabled,
parentAdaptive: parentAdaptive,
backend: backend);
case "expansiontile":
return ExpansionTileControl(
key: key,
Expand Down
118 changes: 118 additions & 0 deletions packages/flet/lib/src/controls/cupertino_action_sheet.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import 'package:flutter/cupertino.dart';

import '../flet_control_backend.dart';
import '../models/control.dart';
import 'create_control.dart';
import 'error.dart';

class CupertinoActionSheetControl extends StatefulWidget {
final Control? parent;
final Control control;
final List<Control> children;
final bool parentDisabled;
final bool? parentAdaptive;
final Widget? nextChild;
final FletControlBackend backend;

const CupertinoActionSheetControl(
{super.key,
this.parent,
required this.control,
required this.children,
required this.parentDisabled,
required this.parentAdaptive,
required this.nextChild,
required this.backend});

@override
State<CupertinoActionSheetControl> createState() =>
_CupertinoActionSheetControlState();
}

class _CupertinoActionSheetControlState
extends State<CupertinoActionSheetControl> {
Widget _createActionSheet() {
bool disabled = widget.control.isDisabled || widget.parentDisabled;

var titleCtrls =
widget.children.where((c) => c.name == "title" && c.isVisible);
var messageCtrls =
widget.children.where((c) => c.name == "message" && c.isVisible);
var cancelButtonCtrls =
widget.children.where((c) => c.name == "cancel" && c.isVisible);
var actionCtrls =
widget.children.where((c) => c.name == "action" && c.isVisible);

return CupertinoActionSheet(
title: titleCtrls.isNotEmpty
? createControl(widget.control, titleCtrls.first.id, disabled,
parentAdaptive: widget.parentAdaptive)
: null,
message: messageCtrls.isNotEmpty
? createControl(widget.control, messageCtrls.first.id, disabled,
parentAdaptive: widget.parentAdaptive)
: null,
cancelButton: cancelButtonCtrls.isNotEmpty
? createControl(widget.control, cancelButtonCtrls.first.id, disabled,
parentAdaptive: widget.parentAdaptive)
: null,
actions: actionCtrls.isNotEmpty
? actionCtrls
.map((c) => createControl(widget.control, c.id, disabled,
parentAdaptive: widget.parentAdaptive))
.toList()
: null,
);
}

@override
Widget build(BuildContext context) {
debugPrint("CupertinoActionSheetControl build: ${widget.control.id}");

bool lastOpen = widget.control.state["open"] ?? false;

var open = widget.control.attrBool("open", false)!;
var modal = widget.control.attrBool("modal", false)!;

debugPrint("Current open state: $lastOpen");
debugPrint("New open state: $open");

if (open && (open != lastOpen)) {
var dialog = _createActionSheet();
if (dialog is ErrorControl) {
return dialog;
}

// close previous dialog
if (ModalRoute.of(context)?.isCurrent != true) {
Navigator.of(context).pop();
}

widget.control.state["open"] = open;

WidgetsBinding.instance.addPostFrameCallback((_) {
showCupertinoModalPopup(
barrierDismissible: !modal,
useRootNavigator: false,
context: context,
builder: (context) => _createActionSheet()).then((value) {
lastOpen = widget.control.state["open"] ?? false;
debugPrint("Action sheet should be dismissed ($hashCode): $lastOpen");
bool shouldDismiss = lastOpen;
widget.control.state["open"] = false;

if (shouldDismiss) {
widget.backend
.updateControlState(widget.control.id, {"open": "false"});
widget.backend
.triggerControlEvent(widget.control.id, "dismiss", "");
}
});
});
} else if (open != lastOpen && lastOpen) {
Navigator.of(context).pop();
}

return widget.nextChild ?? const SizedBox.shrink();
}
}
51 changes: 51 additions & 0 deletions packages/flet/lib/src/controls/cupertino_action_sheet_action.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import 'package:flutter/cupertino.dart';

import '../flet_control_backend.dart';
import '../models/control.dart';
import 'create_control.dart';
import 'error.dart';

class CupertinoActionSheetActionControl extends StatelessWidget {
final Control? parent;
final Control control;
final List<Control> children;
final bool parentDisabled;
final bool? parentAdaptive;
final FletControlBackend backend;

const CupertinoActionSheetActionControl(
{super.key,
this.parent,
required this.control,
required this.children,
required this.parentDisabled,
required this.parentAdaptive,
required this.backend});

@override
Widget build(BuildContext context) {
debugPrint("CupertinoActionSheetActionControl build: ${control.id}");
bool disabled = control.isDisabled || parentDisabled;

var contentCtrls =
children.where((c) => c.name == "content" && c.isVisible);
if (contentCtrls.isEmpty) {
return const ErrorControl(
"CupertinoActionSheetAction must have a content control!");
}

return constrainedControl(
context,
CupertinoActionSheetAction(
isDefaultAction: control.attrBool("default", false)!,
isDestructiveAction: control.attrBool("destructive", false)!,
onPressed: () {
backend.triggerControlEvent(control.id, "click", "");
},
child: createControl(control, contentCtrls.first.id, disabled,
parentAdaptive: parentAdaptive),
),
parent,
control);
}
}
2 changes: 2 additions & 0 deletions sdk/python/packages/flet-core/src/flet_core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@
from flet_core.container import Container, ContainerTapEvent
from flet_core.control import Control, OptionalNumber
from flet_core.control_event import ControlEvent
from flet_core.cupertino_action_sheet import CupertinoActionSheet
from flet_core.cupertino_action_sheet_action import CupertinoActionSheetAction
from flet_core.cupertino_activity_indicator import CupertinoActivityIndicator
from flet_core.cupertino_alert_dialog import CupertinoAlertDialog
from flet_core.cupertino_app_bar import CupertinoAppBar
Expand Down
143 changes: 143 additions & 0 deletions sdk/python/packages/flet-core/src/flet_core/cupertino_action_sheet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
from typing import Any, List, Optional, Union

from flet_core.adaptive_control import AdaptiveControl
from flet_core.control import Control, OptionalNumber
from flet_core.ref import Ref
from flet_core.types import (
AnimationValue,
OffsetValue,
ResponsiveNumber,
RotateValue,
ScaleValue,
)


class CupertinoActionSheet(AdaptiveControl):
"""
An iOS-style action sheet.
-----
Online docs: https://flet.dev/docs/controls/cupertinoactionsheet
"""

def __init__(
self,
title: Optional[Control] = None,
message: Optional[Control] = None,
actions: Optional[List[Control]] = None,
cancel: Optional[Control] = None,
modal: bool = False,
open: bool = False,
on_dismiss=None,
#
# ConstrainedControl and AdaptiveControl
#
ref: Optional[Ref] = None,
visible: Optional[bool] = None,
disabled: Optional[bool] = None,
data: Any = None,
adaptive: Optional[bool] = None,
):
Control.__init__(
self,
ref=ref,
disabled=disabled,
visible=visible,
data=data,
)

AdaptiveControl.__init__(self, adaptive=adaptive)

self.cancel = cancel
self.title = title
self.message = message
self.actions = actions
self.modal = modal
self.open = open
self.on_dismiss = on_dismiss

def _get_control_name(self):
return "cupertinoactionsheet"

def before_update(self):
super().before_update()

def _get_children(self):
children = []
if self.__cancel:
self.__cancel._set_attr_internal("n", "cancel")
children.append(self.__cancel)
if self.__title:
self.__title._set_attr_internal("n", "title")
children.append(self.__title)
if self.__message:
self.__message._set_attr_internal("n", "message")
children.append(self.__message)
for action in self.__actions:
action._set_attr_internal("n", "action")
children.append(action)
return children

# cancel
@property
def cancel(self) -> Optional[Control]:
return self.__cancel

@cancel.setter
def cancel(self, value: Optional[Control]):
self.__cancel = value

# title
@property
def title(self) -> Optional[Control]:
return self.__title

@title.setter
def title(self, value: Optional[Control]):
self.__title = value

# message
@property
def message(self) -> Optional[Control]:
return self.__message

@message.setter
def message(self, value: Optional[Control]):
self.__message = value

# actions
@property
def actions(self):
return self.__actions

@actions.setter
def actions(self, value):
self.__actions = value if value is not None else []

# open
@property
def open(self) -> Optional[bool]:
return self._get_attr("open", data_type="bool", def_value=False)

@open.setter
def open(self, value: Optional[bool]):
self._set_attr("open", value)

# modal
@property
def modal(self) -> Optional[bool]:
return self._get_attr("modal", data_type="bool", def_value=False)

@modal.setter
def modal(self, value: Optional[bool]):
self._set_attr("modal", value)

# on_dismiss
@property
def on_dismiss(self):
return self._get_event_handler("dismiss")

@on_dismiss.setter
def on_dismiss(self, handler):
self._add_event_handler("dismiss", handler)
Loading

0 comments on commit 366770e

Please sign in to comment.