Skip to content

Commit

Permalink
CupertinoContextMenu and CupertinoContextMenuAction controls (fle…
Browse files Browse the repository at this point in the history
…t-dev#2772)

* CupertinoContextMenu: initial commit

* CupertinoContextMenuAction: initial commit

* Fix text color of CupertinoContextMenuAction in dark mode

---------

Co-authored-by: Feodor Fitsner <[email protected]>
  • Loading branch information
2 people authored and zrr1999 committed Jul 17, 2024
1 parent f26e2e2 commit 2b4fae3
Show file tree
Hide file tree
Showing 6 changed files with 349 additions and 0 deletions.
18 changes: 18 additions & 0 deletions packages/flet/lib/src/controls/create_control.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ import 'cupertino_activity_indicator.dart';
import 'cupertino_alert_dialog.dart';
import 'cupertino_button.dart';
import 'cupertino_checkbox.dart';
import 'cupertino_context_menu.dart';
import 'cupertino_context_menu_action.dart';
import 'cupertino_dialog_action.dart';
import 'cupertino_list_tile.dart';
import 'cupertino_navigation_bar.dart';
Expand Down Expand Up @@ -833,6 +835,22 @@ Widget createWidget(
parentAdaptive: parentAdaptive,
nextChild: nextChild,
backend: backend);
case "cupertinocontextmenu":
return CupertinoContextMenuControl(
parent: parent,
control: controlView.control,
children: controlView.children,
parentDisabled: parentDisabled,
parentAdaptive: parentAdaptive,
backend: backend);
case "cupertinocontextmenuaction":
return CupertinoContextMenuActionControl(
parent: parent,
control: controlView.control,
children: controlView.children,
parentDisabled: parentDisabled,
parentAdaptive: parentAdaptive,
backend: backend);
case "cupertinoalertdialog":
return CupertinoAlertDialogControl(
parent: parent,
Expand Down
65 changes: 65 additions & 0 deletions packages/flet/lib/src/controls/cupertino_context_menu.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import 'package:flutter/cupertino.dart';

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

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

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

@override
State<CupertinoContextMenuControl> createState() =>
_CupertinoContextMenuControlState();
}

class _CupertinoContextMenuControlState
extends State<CupertinoContextMenuControl> with FletStoreMixin {
@override
Widget build(BuildContext context) {
debugPrint("CupertinoContextMenu build ($hashCode): ${widget.control.id}");

bool disabled = widget.control.isDisabled || widget.parentDisabled;
bool? adaptive =
widget.control.attrBool("adaptive") ?? widget.parentAdaptive;
var contentCtrls =
widget.children.where((c) => c.name == "content" && c.isVisible);
var actionCtrls =
widget.children.where((c) => c.name == "action" && c.isVisible);

if (actionCtrls.isEmpty) {
return const ErrorControl(
"CupertinoContextMenu.actions list must have at least one action control");
}
if (contentCtrls.isEmpty) {
return const ErrorControl(
"CupertinoContextMenu.content must be provided");
}

return CupertinoContextMenu(
enableHapticFeedback:
widget.control.attrBool("enableHapticFeedback", false)!,
actions: actionCtrls.map((c) {
return createControl(widget.control, c.id, disabled,
parentAdaptive: adaptive);
}).toList(),
child: createControl(widget.control, contentCtrls.first.id, disabled,
parentAdaptive: adaptive),
);
}
}
66 changes: 66 additions & 0 deletions packages/flet/lib/src/controls/cupertino_context_menu_action.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import 'package:flutter/cupertino.dart';

import '../flet_control_backend.dart';
import '../models/control.dart';
import '../utils/icons.dart';
import 'create_control.dart';
import 'flet_store_mixin.dart';

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

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

@override
State<CupertinoContextMenuActionControl> createState() =>
_CupertinoContextMenuActionControlState();
}

class _CupertinoContextMenuActionControlState
extends State<CupertinoContextMenuActionControl> with FletStoreMixin {
@override
Widget build(BuildContext context) {
debugPrint(
"CupertinoContextMenuAction build ($hashCode): ${widget.control.id}");

bool disabled = widget.control.isDisabled || widget.parentDisabled;
bool? adaptive =
widget.control.attrBool("adaptive") ?? widget.parentAdaptive;
String text = widget.control.attrString("text", "")!;
var contentCtrls =
widget.children.where((c) => c.name == "content" && c.isVisible);
IconData? trailingIcon =
parseIcon(widget.control.attrString("trailingIcon", "")!);

Widget child = DefaultTextStyle(
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
color:
CupertinoDynamicColor.resolve(CupertinoColors.label, context)),
child: contentCtrls.isNotEmpty
? createControl(widget.control, contentCtrls.first.id, disabled,
parentAdaptive: adaptive)
: Text(text, overflow: TextOverflow.ellipsis));

return CupertinoContextMenuAction(
isDefaultAction: widget.control.attrBool("default", false)!,
isDestructiveAction: widget.control.attrBool("destructive", false)!,
onPressed: () {
widget.backend.triggerControlEvent(widget.control.id, "click", "");
},
trailingIcon: trailingIcon,
child: child,
);
}
}
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 @@ -73,6 +73,8 @@
from flet_core.cupertino_app_bar import CupertinoAppBar
from flet_core.cupertino_button import CupertinoButton
from flet_core.cupertino_checkbox import CupertinoCheckbox
from flet_core.cupertino_context_menu import CupertinoContextMenu
from flet_core.cupertino_context_menu_action import CupertinoContextMenuAction
from flet_core.cupertino_dialog_action import CupertinoDialogAction
from flet_core.cupertino_filled_button import CupertinoFilledButton
from flet_core.cupertino_list_tile import CupertinoListTile
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
from typing import Any, List, Optional

from flet_core.adaptive_control import AdaptiveControl
from flet_core.control import Control
from flet_core.ref import Ref


class CupertinoContextMenu(AdaptiveControl):
"""
A full-screen modal route that opens up when the content is long-pressed.
-----
Online docs: https://flet.dev/docs/controls/cupertinocontextmenu
"""

def __init__(
self,
content: Control,
actions: List[Control],
enable_haptic_feedback: Optional[bool] = None,
#
# Control
#
ref: Optional[Ref] = None,
disabled: Optional[bool] = None,
visible: Optional[bool] = None,
data: Any = None,
adaptive: Optional[bool] = False,
):
Control.__init__(
self,
ref=ref,
disabled=disabled,
visible=visible,
data=data,
)

AdaptiveControl.__init__(self, adaptive=adaptive)

self.enable_haptic_feedback = enable_haptic_feedback
self.content = content
self.actions = actions

def _get_control_name(self):
return "cupertinocontextmenu"

def _before_build_command(self):
super()._before_build_command()

def _get_children(self):
children = []
if self.__content:
self.__content._set_attr_internal("n", "content")
children.append(self.__content)
for action in self.__actions:
action._set_attr_internal("n", "action")
children.append(action)
return children

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

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

# content
@property
def content(self) -> Control:
return self.__content

@content.setter
def content(self, value: Control):
self.__content = value

# actions
@property
def actions(self) -> List[Control]:
return self.__actions

@actions.setter
def actions(self, value: List[Control]):
assert (
isinstance(value, list) and len(value) > 0
), "CupertinoContextMenu.actions list must have at least one action control"
self.__actions = value
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
from typing import Any, Optional

from flet_core.adaptive_control import AdaptiveControl
from flet_core.control import Control
from flet_core.ref import Ref


class CupertinoContextMenuAction(AdaptiveControl):
"""
An action that can be added to a CupertinoContextMenu.
-----
Online docs: https://flet.dev/docs/controls/cupertinocontextmenuaction
"""

def __init__(
self,
content: Optional[Control] = None,
text: Optional[str] = None,
trailing_icon: Optional[str] = None,
default: Optional[bool] = None,
destructive: Optional[bool] = None,
on_click=None,
#
# Control
#
ref: Optional[Ref] = None,
disabled: Optional[bool] = None,
visible: Optional[bool] = None,
data: Any = None,
adaptive: Optional[bool] = False,
):
Control.__init__(
self,
ref=ref,
disabled=disabled,
visible=visible,
data=data,
)

AdaptiveControl.__init__(self, adaptive=adaptive)

self.default = default
self.destructive = destructive
self.content = content
self.trailing_icon = trailing_icon
self.on_click = on_click
self.text = text

def _get_control_name(self):
return "cupertinocontextmenuaction"

def _get_children(self):
return [self.__content] if self.__content else []

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

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

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

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

# trailing_icon
@property
def trailing_icon(self) -> Optional[str]:
return self._get_attr("trailingIcon")

@trailing_icon.setter
def trailing_icon(self, value: Optional[str]):
self._set_attr("trailingIcon", value)

# text
@property
def text(self) -> Optional[str]:
return self._get_attr("text")

@text.setter
def text(self, value: Optional[str]):
self._set_attr("text", value)

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

@content.setter
def content(self, value: Optional[Control]):
self.__content = value

# on_click
@property
def on_click(self):
return self._get_event_handler("click")

@on_click.setter
def on_click(self, handler):
self._add_event_handler("click", handler)

0 comments on commit 2b4fae3

Please sign in to comment.