Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CupertinoTimerPicker and CupertinorPicker Controls #2743

Merged
merged 9 commits into from
Mar 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -38,10 +38,12 @@ import 'cupertino_checkbox.dart';
import 'cupertino_dialog_action.dart';
import 'cupertino_list_tile.dart';
import 'cupertino_navigation_bar.dart';
import 'cupertino_picker.dart';
import 'cupertino_radio.dart';
import 'cupertino_slider.dart';
import 'cupertino_switch.dart';
import 'cupertino_textfield.dart';
import 'cupertino_timer_picker.dart';
import 'datatable.dart';
import 'date_picker.dart';
import 'dismissible.dart';
Expand Down Expand Up @@ -484,6 +486,22 @@ Widget createWidget(
children: controlView.children,
parentDisabled: parentDisabled,
backend: backend);
case "cupertinotimerpicker":
return CupertinoTimerPickerControl(
parent: parent,
control: controlView.control,
parentDisabled: parentDisabled,
nextChild: nextChild,
backend: backend);
case "cupertinopicker":
return CupertinoPickerControl(
parent: parent,
control: controlView.control,
children: controlView.children,
parentAdaptive: parentAdaptive,
parentDisabled: parentDisabled,
nextChild: nextChild,
backend: backend);
case "draggable":
return DraggableControl(
key: key,
Expand Down
147 changes: 147 additions & 0 deletions packages/flet/lib/src/controls/cupertino_picker.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

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

const double _kItemExtent = 32.0;
const double _kDefaultDiameterRatio = 1.07;
const double _kSqueeze = 1.45;

class CupertinoPickerControl 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 CupertinoPickerControl(
{super.key,
this.parent,
required this.control,
required this.children,
required this.parentAdaptive,
required this.parentDisabled,
required this.nextChild,
required this.backend});

@override
State<CupertinoPickerControl> createState() => _CupertinoPickerControlState();
}

class _CupertinoPickerControlState extends State<CupertinoPickerControl> {
Widget _createPicker() {
bool disabled = widget.control.isDisabled || widget.parentDisabled;

List<Widget> ctrls = widget.children.where((c) => c.isVisible).map((c) {
return Center(
child: createControl(widget.control, c.id, disabled,
parentAdaptive: widget.parentAdaptive));
}).toList();

double itemExtent = widget.control.attrDouble("itemExtent", _kItemExtent)!;
int? selectedIndex = widget.control.attrInt("selectedIndex");
double diameterRatio =
widget.control.attrDouble("diameterRatio", _kDefaultDiameterRatio)!;
double magnification = widget.control.attrDouble("magnification", 1.0)!;
double squeeze = widget.control.attrDouble("squeeze", _kSqueeze)!;
double offAxisFraction = widget.control.attrDouble("offAxisFraction", 0.0)!;
bool useMagnifier = widget.control.attrBool("useMagnifier", false)!;
bool looping = widget.control.attrBool("looping", false)!;
Color? backgroundColor = HexColor.fromString(
Theme.of(context), widget.control.attrString("bgColor", "")!);

Widget picker = CupertinoPicker(
backgroundColor: backgroundColor,
diameterRatio: diameterRatio,
magnification: magnification,
squeeze: squeeze,
offAxisFraction: offAxisFraction,
itemExtent: itemExtent,
useMagnifier: useMagnifier,
looping: looping,
onSelectedItemChanged: (int index) {
widget.backend.updateControlState(
widget.control.id, {"selectedIndex": index.toString()});
widget.backend
.triggerControlEvent(widget.control.id, "change", index.toString());
},
scrollController: selectedIndex != null
? FixedExtentScrollController(initialItem: selectedIndex)
: null,
children: ctrls,
);

return Container(
height: 216,
padding: const EdgeInsets.only(top: 6.0),
// The Bottom margin is provided to align the popup above the system navigation bar.
margin: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom,
),
// Provide a background color for the popup.
color: CupertinoColors.systemBackground.resolveFrom(context),
// Use a SafeArea widget to avoid system overlaps.
child: SafeArea(
top: false,
child: picker,
),
);
}

@override
Widget build(BuildContext context) {
debugPrint("CupertinoPicker 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 = _createPicker();
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) => _createPicker()).then((value) {
lastOpen = widget.control.state["open"] ?? false;
debugPrint("Picker 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();
}
}
132 changes: 132 additions & 0 deletions packages/flet/lib/src/controls/cupertino_timer_picker.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

import '../flet_control_backend.dart';
import '../models/control.dart';
import '../utils/alignment.dart';
import '../utils/colors.dart';
import 'error.dart';

class CupertinoTimerPickerControl extends StatefulWidget {
final Control? parent;
final Control control;
final bool parentDisabled;
final Widget? nextChild;

final FletControlBackend backend;

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

@override
State<CupertinoTimerPickerControl> createState() =>
_CupertinoTimerPickerControlState();
}

class _CupertinoTimerPickerControlState
extends State<CupertinoTimerPickerControl> {
Widget _createPicker() {
int value = widget.control.attrInt("value", 0)!;
Duration initialTimerDuration = Duration(seconds: value);
int minuteInterval =
widget.control.attrDouble("minuteInterval", 1)!.toInt();
int secondInterval =
widget.control.attrDouble("secondInterval", 1)!.toInt();
CupertinoTimerPickerMode mode = CupertinoTimerPickerMode.values.firstWhere(
(a) =>
a.name.toLowerCase() ==
widget.control.attrString("mode", "")!.toLowerCase(),
orElse: () => CupertinoTimerPickerMode.hms);

Color? backgroundColor = HexColor.fromString(
Theme.of(context), widget.control.attrString("bgColor", "")!);

Widget picker = CupertinoTimerPicker(
mode: mode,
initialTimerDuration: initialTimerDuration,
minuteInterval: minuteInterval,
secondInterval: secondInterval,
alignment:
parseAlignment(widget.control, "alignment") ?? Alignment.center,
backgroundColor: backgroundColor,
onTimerDurationChanged: (Duration d) {
widget.backend.updateControlState(
widget.control.id, {"value": d.inSeconds.toString()});
widget.backend.triggerControlEvent(
widget.control.id, "change", d.inSeconds.toString());
},
);

return Container(
height: 216,
padding: const EdgeInsets.only(top: 6.0),
// The Bottom margin is provided to align the popup above the system navigation bar.
margin: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom,
),
// Provide a background color for the popup.
color: CupertinoColors.systemBackground.resolveFrom(context),
// Use a SafeArea widget to avoid system overlaps.
child: SafeArea(
top: false,
child: picker,
),
);
}

@override
Widget build(BuildContext context) {
debugPrint("CupertinoTimerPicker 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 = _createPicker();
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) => _createPicker()).then((value) {
lastOpen = widget.control.state["open"] ?? false;
debugPrint("Picker 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();
}
}
5 changes: 5 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 @@ -77,10 +77,15 @@
from flet_core.cupertino_filled_button import CupertinoFilledButton
from flet_core.cupertino_list_tile import CupertinoListTile
from flet_core.cupertino_navigation_bar import CupertinoNavigationBar
from flet_core.cupertino_picker import CupertinoPicker
from flet_core.cupertino_radio import CupertinoRadio
from flet_core.cupertino_slider import CupertinoSlider
from flet_core.cupertino_switch import CupertinoSwitch
from flet_core.cupertino_textfield import CupertinoTextField, VisibilityMode
from flet_core.cupertino_timer_picker import (
CupertinoTimerPicker,
CupertinoTimerPickerMode,
)
from flet_core.datatable import (
DataCell,
DataColumn,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,10 @@
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):
class CupertinoActionSheet(Control):
"""
An iOS-style action sheet.

Expand All @@ -31,13 +23,12 @@ def __init__(
open: bool = False,
on_dismiss=None,
#
# ConstrainedControl and AdaptiveControl
# Control
#
ref: Optional[Ref] = None,
visible: Optional[bool] = None,
disabled: Optional[bool] = None,
data: Any = None,
adaptive: Optional[bool] = None,
):
Control.__init__(
self,
Expand All @@ -47,8 +38,6 @@ def __init__(
data=data,
)

AdaptiveControl.__init__(self, adaptive=adaptive)

self.cancel = cancel
self.title = title
self.message = message
Expand Down
Loading